/[LeafOK_CVS]/lbbs/src/article_post.c
ViewVC logotype

Contents of /lbbs/src/article_post.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.45 - (show annotations)
Sat Jan 3 10:27:14 2026 UTC (2 months, 1 week ago) by sysadm
Branch: MAIN
CVS Tags: HEAD
Changes since 1.44: +1 -1 lines
Content type: text/x-csrc
Update copyright info

1 /* SPDX-License-Identifier: GPL-3.0-or-later */
2 /*
3 * article_post
4 * - user interactive feature to post / modify / reply article
5 *
6 * Copyright (C) 2004-2026 Leaflet <leaflet@leafok.com>
7 */
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12
13 #include "article_cache.h"
14 #include "article_post.h"
15 #include "bbs.h"
16 #include "bwf.h"
17 #include "database.h"
18 #include "editor.h"
19 #include "io.h"
20 #include "log.h"
21 #include "lml.h"
22 #include "screen.h"
23 #include "user_priv.h"
24 #include <ctype.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <time.h>
28
29 enum _article_post_constant_t
30 {
31 TITLE_INPUT_MAX_LEN = 72,
32 ARTICLE_QUOTE_DEFAULT_LINES = 20,
33 MODIFY_DT_MAX_LEN = 50,
34 };
35
36 int article_post(const SECTION_LIST *p_section, ARTICLE *p_article_new)
37 {
38 MYSQL *db = NULL;
39 MYSQL_RES *rs = NULL;
40 MYSQL_ROW row;
41 char sql[SQL_BUFFER_LEN];
42 char *sql_content = NULL;
43 EDITOR_DATA *p_editor_data = NULL;
44 char title_input[BBS_article_title_max_len + 1];
45 char title_f[BBS_article_title_max_len * 2 + 1];
46 char *content = NULL;
47 char *content_f = NULL;
48 long len_content;
49 int content_display_length;
50 char nickname_f[BBS_nickname_max_len * 2 + 1];
51 int sign_id = 0;
52 int reply_note = 1;
53 long len;
54 int ch;
55 char *p, *q;
56 long ret = 0;
57
58 if (p_section == NULL || p_article_new == NULL)
59 {
60 log_error("NULL pointer error");
61 }
62
63 if (!checkpriv(&BBS_priv, p_section->sid, S_POST))
64 {
65 clearscr();
66 moveto(1, 1);
67 prints("您没有权限在本版块发表文章\n");
68 press_any_key();
69
70 return 0;
71 }
72
73 p_article_new->title[0] = '\0';
74 title_input[0] = '\0';
75 p_article_new->transship = 0;
76
77 p_editor_data = editor_data_load("");
78 if (p_editor_data == NULL)
79 {
80 log_error("editor_data_load() error");
81 ret = -1;
82 goto cleanup;
83 }
84
85 // Set title and sign
86 for (ch = 'T'; !SYS_server_exit;)
87 {
88 clearscr();
89 moveto(21, 1);
90 prints("发表文章于 %s[%s] 讨论区,类型: %s,回复通知: %s",
91 p_section->stitle, p_section->sname,
92 (p_article_new->transship ? "转载" : "原创"),
93 (reply_note ? "开启" : "关闭"));
94 moveto(22, 1);
95 prints("标题: %s", (p_article_new->title[0] == '\0' ? "[无]" : p_article_new->title));
96 moveto(23, 1);
97 prints("使用第 %d 个签名", sign_id);
98
99 if (toupper(ch) != 'T')
100 {
101 prints(" 按0~3选签名档(0表示不使用)");
102
103 moveto(24, 1);
104 prints("T改标题, C取消, Z设为%s, N%s, Enter继续: ",
105 (p_article_new->transship ? "原创" : "转载"),
106 (reply_note ? "关闭回复通知" : "开启回复通知"));
107 iflush();
108 ch = 0;
109 }
110
111 while (!SYS_server_exit)
112 {
113 switch (toupper(ch))
114 {
115 case KEY_NULL:
116 case KEY_TIMEOUT:
117 goto cleanup;
118 case CR:
119 break;
120 case 'T':
121 len = get_data(24, 1, "标题: ", title_input, sizeof(title_input), TITLE_INPUT_MAX_LEN);
122 for (p = title_input; *p == ' '; p++)
123 ;
124 for (q = title_input + len; q > p && *(q - 1) == ' '; q--)
125 ;
126 *q = '\0';
127 len = q - p;
128 if (*p != '\0')
129 {
130 if ((ret = check_badwords(p, '*')) < 0)
131 {
132 log_error("check_badwords(title) error");
133 }
134 else if (ret > 0)
135 {
136 memcpy(title_input, p, (size_t)len + 1);
137 continue;
138 }
139 memcpy(p_article_new->title, p, (size_t)len + 1);
140 memcpy(title_input, p_article_new->title, (size_t)len + 1);
141 }
142 ch = 0;
143 break;
144 case 'C':
145 clearscr();
146 moveto(1, 1);
147 prints("取消...");
148 press_any_key();
149 goto cleanup;
150 case 'Z':
151 p_article_new->transship = (p_article_new->transship ? 0 : 1);
152 break;
153 case 'N':
154 reply_note = (reply_note ? 0 : 1);
155 break;
156 case '0':
157 case '1':
158 case '2':
159 case '3':
160 sign_id = ch - '0';
161 break;
162 default: // Invalid selection
163 ch = igetch_t(BBS_max_user_idle_time);
164 continue;
165 }
166
167 break;
168 }
169
170 if (ch != CR || p_article_new->title[0] == '\0')
171 {
172 continue;
173 }
174
175 for (ch = 'E'; !SYS_server_exit && toupper(ch) == 'E';)
176 {
177 editor_display(p_editor_data);
178
179 clearscr();
180 moveto(1, 1);
181 prints("(S)发送, (C)取消, (T)更改标题 or (E)再编辑? [S]: ");
182 iflush();
183
184 for (ch = 0; !SYS_server_exit; ch = igetch_t(BBS_max_user_idle_time))
185 {
186 switch (toupper(ch))
187 {
188 case KEY_NULL:
189 case KEY_TIMEOUT:
190 goto cleanup;
191 case CR:
192 case 'S':
193 break;
194 case 'C':
195 clearscr();
196 moveto(1, 1);
197 prints("取消...");
198 press_any_key();
199 goto cleanup;
200 case 'T':
201 break;
202 case 'E':
203 break;
204 default: // Invalid selection
205 continue;
206 }
207
208 break;
209 }
210 }
211
212 if (toupper(ch) != 'T')
213 {
214 break;
215 }
216 }
217
218 if (SYS_server_exit) // Do not save data on shutdown
219 {
220 goto cleanup;
221 }
222
223 content = malloc(ARTICLE_CONTENT_MAX_LEN);
224 if (content == NULL)
225 {
226 log_error("malloc(content) error: OOM");
227 ret = -1;
228 goto cleanup;
229 }
230
231 len_content = editor_data_save(p_editor_data, content, ARTICLE_CONTENT_MAX_LEN);
232 if (len_content < 0)
233 {
234 log_error("editor_data_save() error");
235 ret = -1;
236 goto cleanup;
237 }
238
239 if (check_badwords(content, '*') < 0)
240 {
241 log_error("check_badwords(content) error");
242 ret = -1;
243 goto cleanup;
244 }
245
246 db = db_open();
247 if (db == NULL)
248 {
249 log_error("db_open() error: %s", mysql_error(db));
250 ret = -1;
251 goto cleanup;
252 }
253
254 if (sign_id > 0)
255 {
256 snprintf(sql, sizeof(sql),
257 "SELECT sign_%d AS sign FROM user_pubinfo WHERE UID = %d",
258 sign_id, BBS_priv.uid);
259
260 if (mysql_query(db, sql) != 0)
261 {
262 log_error("Query sign error: %s", mysql_error(db));
263 ret = -1;
264 goto cleanup;
265 }
266 if ((rs = mysql_use_result(db)) == NULL)
267 {
268 log_error("Get sign data failed");
269 ret = -1;
270 goto cleanup;
271 }
272
273 if ((row = mysql_fetch_row(rs)))
274 {
275 len_content += snprintf(content + len_content,
276 ARTICLE_CONTENT_MAX_LEN - (size_t)len_content,
277 "\n\n--\n%s\n", row[0]);
278 }
279 mysql_free_result(rs);
280 rs = NULL;
281 }
282
283 // Calculate display length of content
284 content_display_length = str_length(content, 1);
285
286 // Begin transaction
287 if (mysql_query(db, "SET autocommit=0") != 0)
288 {
289 log_error("SET autocommit=0 error: %s", mysql_error(db));
290 ret = -1;
291 goto cleanup;
292 }
293
294 if (mysql_query(db, "BEGIN") != 0)
295 {
296 log_error("Begin transaction error: %s", mysql_error(db));
297 ret = -1;
298 goto cleanup;
299 }
300
301 // Secure SQL parameters
302 content_f = malloc((size_t)len_content * 2 + 1);
303 if (content_f == NULL)
304 {
305 log_error("malloc(content_f) error: OOM");
306 ret = -1;
307 goto cleanup;
308 }
309
310 mysql_real_escape_string(db, nickname_f, BBS_nickname, (unsigned long)strnlen(BBS_nickname, sizeof(BBS_nickname)));
311 mysql_real_escape_string(db, title_f, p_article_new->title, strnlen(p_article_new->title, sizeof(p_article_new->title)));
312 mysql_real_escape_string(db, content_f, content, (unsigned long)len_content);
313
314 free(content);
315 content = NULL;
316
317 // Add content
318 sql_content = malloc(SQL_BUFFER_LEN + (size_t)len_content * 2 + 1);
319 if (sql_content == NULL)
320 {
321 log_error("malloc(sql_content) error: OOM");
322 ret = -1;
323 goto cleanup;
324 }
325
326 snprintf(sql_content, SQL_BUFFER_LEN + (size_t)len_content * 2 + 1,
327 "INSERT INTO bbs_content(AID, content) values(0, '%s')",
328 content_f);
329
330 free(content_f);
331 content_f = NULL;
332
333 if (mysql_query(db, sql_content) != 0)
334 {
335 log_error("Add article content error: %s", mysql_error(db));
336 ret = -1;
337 goto cleanup;
338 }
339
340 p_article_new->cid = (int32_t)mysql_insert_id(db);
341
342 free(sql_content);
343 sql_content = NULL;
344
345 // Add article
346 snprintf(sql, sizeof(sql),
347 "INSERT INTO bbs(SID, TID, UID, username, nickname, title, CID, transship, "
348 "sub_dt, sub_ip, reply_note, exp, last_reply_dt, icon, length) "
349 "VALUES(%d, 0, %d, '%s', '%s', '%s', %d, %d, NOW(), '%s', %d, %d, NOW(), 1, %d)",
350 p_section->sid, BBS_priv.uid, BBS_username, nickname_f, title_f,
351 p_article_new->cid, p_article_new->transship, hostaddr_client,
352 reply_note, BBS_user_exp, content_display_length);
353
354 if (mysql_query(db, sql) != 0)
355 {
356 log_error("Add article error: %s", mysql_error(db));
357 ret = -1;
358 goto cleanup;
359 }
360
361 p_article_new->aid = (int32_t)mysql_insert_id(db);
362
363 // Link content to article
364 snprintf(sql, sizeof(sql),
365 "UPDATE bbs_content SET AID = %d WHERE CID = %d",
366 p_article_new->aid, p_article_new->cid);
367
368 if (mysql_query(db, sql) != 0)
369 {
370 log_error("Update content error: %s", mysql_error(db));
371 ret = -1;
372 goto cleanup;
373 }
374
375 // Add exp
376 if (checkpriv(&BBS_priv, p_section->sid, S_GETEXP)) // Except in test section
377 {
378 snprintf(sql, sizeof(sql),
379 "UPDATE user_pubinfo SET exp = exp + %d WHERE UID = %d",
380 (p_article_new->transship ? 5 : 15), BBS_priv.uid);
381
382 if (mysql_query(db, sql) != 0)
383 {
384 log_error("Update exp error: %s", mysql_error(db));
385 ret = -1;
386 goto cleanup;
387 }
388 }
389
390 // Add log
391 snprintf(sql, sizeof(sql),
392 "INSERT INTO bbs_article_op(AID, UID, type, op_dt, op_ip)"
393 "VALUES(%d, %d, 'A', NOW(), '%s')",
394 p_article_new->aid, BBS_priv.uid, hostaddr_client);
395
396 if (mysql_query(db, sql) != 0)
397 {
398 log_error("Add log error: %s", mysql_error(db));
399 ret = -1;
400 goto cleanup;
401 }
402
403 // Commit transaction
404 if (mysql_query(db, "COMMIT") != 0)
405 {
406 log_error("Commit transaction error: %s", mysql_error(db));
407 ret = -1;
408 goto cleanup;
409 }
410
411 mysql_close(db);
412 db = NULL;
413
414 clearscr();
415 moveto(1, 1);
416 prints("发送完成,新文章通常会在%d秒后可见", BBS_section_list_load_interval);
417 press_any_key();
418 ret = 1; // Success
419
420 cleanup:
421 mysql_close(db);
422
423 // Cleanup buffers
424 editor_data_cleanup(p_editor_data);
425
426 free(sql_content);
427 free(content);
428 free(content_f);
429
430 return (int)ret;
431 }
432
433 int article_modify(const SECTION_LIST *p_section, const ARTICLE *p_article, ARTICLE *p_article_new)
434 {
435 MYSQL *db = NULL;
436 MYSQL_RES *rs = NULL;
437 MYSQL_ROW row;
438 char sql[SQL_BUFFER_LEN];
439 char *sql_content = NULL;
440 char *content = NULL;
441 char *content_f = NULL;
442 long len_content;
443 int content_display_length;
444 int reply_note = 1;
445 int ch;
446 long ret = 0;
447 time_t now;
448 struct tm tm_modify_dt;
449 char str_modify_dt[MODIFY_DT_MAX_LEN + 1];
450
451 EDITOR_DATA *p_editor_data = NULL;
452
453 if (p_section == NULL || p_article == NULL)
454 {
455 log_error("NULL pointer error");
456 }
457
458 if (p_article->excerption) // Modify is not allowed
459 {
460 clearscr();
461 moveto(1, 1);
462 prints("该文章无法被编辑,请联系版主。");
463 press_any_key();
464
465 return 0;
466 }
467
468 db = db_open();
469 if (db == NULL)
470 {
471 log_error("db_open() error: %s", mysql_error(db));
472 ret = -1;
473 goto cleanup;
474 }
475
476 snprintf(sql, sizeof(sql),
477 "SELECT bbs_content.CID, bbs_content.content, reply_note "
478 "FROM bbs INNER JOIN bbs_content ON bbs.CID = bbs_content.CID "
479 "WHERE bbs.AID = %d",
480 p_article->aid);
481
482 if (mysql_query(db, sql) != 0)
483 {
484 log_error("Query article content error: %s", mysql_error(db));
485 ret = -1;
486 goto cleanup;
487 }
488 if ((rs = mysql_use_result(db)) == NULL)
489 {
490 log_error("Get article content data failed");
491 ret = -1;
492 goto cleanup;
493 }
494
495 if ((row = mysql_fetch_row(rs)))
496 {
497 content = malloc(ARTICLE_CONTENT_MAX_LEN);
498 if (content == NULL)
499 {
500 log_error("malloc(content) error: OOM");
501 ret = -1;
502 goto cleanup;
503 }
504
505 strncpy(content, row[1], ARTICLE_CONTENT_MAX_LEN - 1);
506 content[ARTICLE_CONTENT_MAX_LEN - 1] = '\0';
507
508 // Remove control sequence
509 len_content = str_filter(content, 0);
510
511 p_editor_data = editor_data_load(content);
512 if (p_editor_data == NULL)
513 {
514 log_error("editor_data_load(aid=%d, cid=%d) error", p_article->aid, atoi(row[0]));
515 ret = -1;
516 goto cleanup;
517 }
518
519 free(content);
520 content = NULL;
521
522 reply_note = atoi(row[2]);
523 }
524 mysql_free_result(rs);
525 rs = NULL;
526
527 mysql_close(db);
528 db = NULL;
529
530 for (ch = 'E'; !SYS_server_exit && toupper(ch) == 'E';)
531 {
532 editor_display(p_editor_data);
533
534 while (!SYS_server_exit)
535 {
536 clearscr();
537 moveto(1, 1);
538 prints("(S)保存, (C)取消, (N)%s回复通知 or (E)再编辑? [S]: ",
539 (reply_note ? "关闭" : "开启"));
540 iflush();
541
542 ch = igetch_t(BBS_max_user_idle_time);
543 switch (toupper(ch))
544 {
545 case KEY_NULL:
546 case KEY_TIMEOUT:
547 goto cleanup;
548 case CR:
549 case 'S':
550 break;
551 case 'C':
552 clearscr();
553 moveto(1, 1);
554 prints("取消...");
555 press_any_key();
556 goto cleanup;
557 case 'N':
558 reply_note = (reply_note ? 0 : 1);
559 continue;
560 case 'E':
561 break;
562 default: // Invalid selection
563 continue;
564 }
565
566 break;
567 }
568 }
569
570 if (SYS_server_exit) // Do not save data on shutdown
571 {
572 goto cleanup;
573 }
574
575 // Allocate buffers in big size
576 content = malloc(ARTICLE_CONTENT_MAX_LEN);
577 if (content == NULL)
578 {
579 log_error("malloc(content) error: OOM");
580 ret = -1;
581 goto cleanup;
582 }
583
584 len_content = editor_data_save(p_editor_data, content, ARTICLE_CONTENT_MAX_LEN - LINE_BUFFER_LEN);
585 if (len_content < 0)
586 {
587 log_error("editor_data_save() error");
588 ret = -1;
589 goto cleanup;
590 }
591
592 if (check_badwords(content, '*') < 0)
593 {
594 log_error("check_badwords(content) error");
595 ret = -1;
596 goto cleanup;
597 }
598
599 time(&now);
600 localtime_r(&now, &tm_modify_dt);
601 strftime(str_modify_dt, sizeof(str_modify_dt), "%Y-%m-%d %H:%M:%S (UTC %z)", &tm_modify_dt);
602
603 len_content += snprintf(content + len_content, LINE_BUFFER_LEN,
604 "\n--\n※ 作者已于 %s 修改本文※\n",
605 str_modify_dt);
606
607 // Calculate display length of content
608 content_display_length = str_length(content, 1);
609
610 db = db_open();
611 if (db == NULL)
612 {
613 log_error("db_open() error: %s", mysql_error(db));
614 ret = -1;
615 goto cleanup;
616 }
617
618 // Begin transaction
619 if (mysql_query(db, "SET autocommit=0") != 0)
620 {
621 log_error("SET autocommit=0 error: %s", mysql_error(db));
622 ret = -1;
623 goto cleanup;
624 }
625
626 if (mysql_query(db, "BEGIN") != 0)
627 {
628 log_error("Begin transaction error: %s", mysql_error(db));
629 ret = -1;
630 goto cleanup;
631 }
632
633 // Secure SQL parameters
634 content_f = malloc((size_t)len_content * 2 + 1);
635 if (content_f == NULL)
636 {
637 log_error("malloc(content_f) error: OOM");
638 ret = -1;
639 goto cleanup;
640 }
641
642 mysql_real_escape_string(db, content_f, content, (unsigned long)len_content);
643
644 free(content);
645 content = NULL;
646
647 // Add content
648 sql_content = malloc(SQL_BUFFER_LEN + (size_t)len_content * 2 + 1);
649 if (sql_content == NULL)
650 {
651 log_error("malloc(sql_content) error: OOM");
652 ret = -1;
653 goto cleanup;
654 }
655
656 snprintf(sql_content, SQL_BUFFER_LEN + (size_t)len_content * 2 + 1,
657 "INSERT INTO bbs_content(AID, content) values(%d, '%s')",
658 p_article->aid, content_f);
659
660 free(content_f);
661 content_f = NULL;
662
663 if (mysql_query(db, sql_content) != 0)
664 {
665 log_error("Add article content error: %s", mysql_error(db));
666 ret = -1;
667 goto cleanup;
668 }
669
670 p_article_new->cid = (int32_t)mysql_insert_id(db);
671
672 free(sql_content);
673 sql_content = NULL;
674
675 // Update article
676 snprintf(sql, sizeof(sql),
677 "UPDATE bbs SET CID = %d, length = %d, reply_note = %d, excerption = 0 WHERE AID = %d", // Set excerption = 0 explictly in case of rare condition
678 p_article_new->cid, content_display_length, reply_note, p_article->aid);
679
680 if (mysql_query(db, sql) != 0)
681 {
682 log_error("Add article error: %s", mysql_error(db));
683 ret = -1;
684 goto cleanup;
685 }
686
687 if (mysql_query(db, sql) != 0)
688 {
689 log_error("Update content error: %s", mysql_error(db));
690 ret = -1;
691 goto cleanup;
692 }
693
694 // Add log
695 snprintf(sql, sizeof(sql),
696 "INSERT INTO bbs_article_op(AID, UID, type, op_dt, op_ip)"
697 "VALUES(%d, %d, 'M', NOW(), '%s')",
698 p_article->aid, BBS_priv.uid, hostaddr_client);
699
700 if (mysql_query(db, sql) != 0)
701 {
702 log_error("Add log error: %s", mysql_error(db));
703 ret = -1;
704 goto cleanup;
705 }
706
707 // Commit transaction
708 if (mysql_query(db, "COMMIT") != 0)
709 {
710 log_error("Commit transaction error: %s", mysql_error(db));
711 ret = -1;
712 goto cleanup;
713 }
714
715 mysql_close(db);
716 db = NULL;
717
718 clearscr();
719 moveto(1, 1);
720 prints("修改完成,新内容通常会在%d秒后可见", BBS_section_list_load_interval);
721 press_any_key();
722 ret = 1; // Success
723
724 cleanup:
725 mysql_free_result(rs);
726 mysql_close(db);
727
728 // Cleanup buffers
729 editor_data_cleanup(p_editor_data);
730
731 free(sql_content);
732 free(content);
733 free(content_f);
734
735 return (int)ret;
736 }
737
738 int article_reply(const SECTION_LIST *p_section, const ARTICLE *p_article, ARTICLE *p_article_new)
739 {
740 MYSQL *db = NULL;
741 MYSQL_RES *rs = NULL;
742 MYSQL_ROW row;
743 long line_offsets[MAX_EDITOR_DATA_LINES + 1];
744 char sql[SQL_BUFFER_LEN];
745 char *sql_content = NULL;
746 EDITOR_DATA *p_editor_data = NULL;
747 char title_input[BBS_article_title_max_len + sizeof("Re: ")];
748 char title_f[BBS_article_title_max_len * 2 + 1];
749 char *content = NULL;
750 char *content_f = NULL;
751 long len_content;
752 int content_display_length;
753 char nickname_f[BBS_nickname_max_len * 2 + 1];
754 int sign_id = 0;
755 int reply_note = 0;
756 int full_quote = 0;
757 long len;
758 int ch;
759 char *p, *q;
760 int eol;
761 int display_len;
762 long quote_content_lines;
763 long i;
764 long ret = 0;
765 int topic_locked = 0;
766 char msg[BBS_msg_max_len];
767 char msg_f[BBS_msg_max_len * 2 + 1];
768 int len_msg;
769
770 if (p_section == NULL || p_article == NULL)
771 {
772 log_error("NULL pointer error");
773 }
774
775 if (!checkpriv(&BBS_priv, p_section->sid, S_POST))
776 {
777 clearscr();
778 moveto(1, 1);
779 prints("您没有权限在本版块发表文章\n");
780 press_any_key();
781
782 return 0;
783 }
784
785 p_article_new->title[0] = '\0';
786 snprintf(title_input, sizeof(title_input), "Re: %s", p_article->title);
787 len = split_line(title_input, TITLE_INPUT_MAX_LEN, &eol, &display_len, 0);
788 title_input[len] = '\0';
789
790 db = db_open();
791 if (db == NULL)
792 {
793 log_error("db_open() error: %s", mysql_error(db));
794 ret = -1;
795 goto cleanup;
796 }
797
798 snprintf(sql, sizeof(sql),
799 "SELECT `lock` FROM bbs WHERE AID = %d",
800 (p_article->tid == 0 ? p_article->aid : p_article->tid));
801
802 if (mysql_query(db, sql) != 0)
803 {
804 log_error("Query article status error: %s", mysql_error(db));
805 ret = -1;
806 goto cleanup;
807 }
808 if ((rs = mysql_store_result(db)) == NULL)
809 {
810 log_error("Get article status data failed");
811 ret = -1;
812 goto cleanup;
813 }
814
815 if ((row = mysql_fetch_row(rs)))
816 {
817 if (atoi(row[0]) != 0)
818 {
819 topic_locked = 1;
820 }
821 }
822 mysql_free_result(rs);
823 rs = NULL;
824
825 if (topic_locked) // Reply is not allowed
826 {
827 mysql_close(db);
828 db = NULL;
829
830 clearscr();
831 moveto(1, 1);
832 prints("该主题谢绝回复");
833 press_any_key();
834
835 goto cleanup;
836 }
837
838 snprintf(sql, sizeof(sql),
839 "SELECT bbs_content.CID, bbs_content.content "
840 "FROM bbs INNER JOIN bbs_content ON bbs.CID = bbs_content.CID "
841 "WHERE bbs.AID = %d",
842 p_article->aid);
843
844 if (mysql_query(db, sql) != 0)
845 {
846 log_error("Query article content error: %s", mysql_error(db));
847 ret = -1;
848 goto cleanup;
849 }
850 if ((rs = mysql_use_result(db)) == NULL)
851 {
852 log_error("Get article content data failed");
853 ret = -1;
854 goto cleanup;
855 }
856
857 if ((row = mysql_fetch_row(rs)))
858 {
859 content = malloc(ARTICLE_CONTENT_MAX_LEN);
860 if (content == NULL)
861 {
862 log_error("malloc(content) error: OOM");
863 ret = -1;
864 goto cleanup;
865 }
866
867 content_f = malloc(ARTICLE_CONTENT_MAX_LEN);
868 if (content_f == NULL)
869 {
870 log_error("malloc(content_f) error: OOM");
871 ret = -1;
872 goto cleanup;
873 }
874
875 // Apply LML render to content body
876 len = lml_render(row[1], content_f, ARTICLE_CONTENT_MAX_LEN, MAX_EDITOR_DATA_LINE_LENGTH - 3, 1);
877 content_f[len] = '\0';
878
879 // Remove control sequence
880 len = str_filter(content_f, 0);
881 }
882 mysql_free_result(rs);
883 rs = NULL;
884
885 mysql_close(db);
886 db = NULL;
887
888 // Set title and sign
889 for (ch = 'T'; !SYS_server_exit;)
890 {
891 clearscr();
892 moveto(21, 1);
893 prints("回复文章于 %s[%s] 讨论区, 回复通知: %s, 引用模式: %s",
894 p_section->stitle, p_section->sname,
895 (reply_note ? "开启" : "关闭"),
896 (full_quote ? "完整" : "精简"));
897 moveto(22, 1);
898 prints("标题: %s", (p_article_new->title[0] == '\0' ? "[无]" : p_article_new->title));
899 moveto(23, 1);
900 prints("使用第 %d 个签名", sign_id);
901
902 if (toupper(ch) != 'T')
903 {
904 prints(" 按0~3选签名档(0表示不使用)");
905
906 moveto(24, 1);
907 prints("T改标题, C取消, N%s, Q%s, Enter继续: ",
908 (reply_note ? "关闭回复通知" : "开启回复通知"), (full_quote ? "精简引用" : "完整引用"));
909 iflush();
910 ch = 0;
911 }
912
913 while (!SYS_server_exit)
914 {
915 switch (toupper(ch))
916 {
917 case KEY_NULL:
918 case KEY_TIMEOUT:
919 goto cleanup;
920 case CR:
921 break;
922 case 'T':
923 len = get_data(24, 1, "标题: ", title_input, sizeof(title_input), TITLE_INPUT_MAX_LEN);
924 for (p = title_input; *p == ' '; p++)
925 ;
926 for (q = title_input + len; q > p && *(q - 1) == ' '; q--)
927 ;
928 *q = '\0';
929 len = q - p;
930 if (*p != '\0')
931 {
932 if ((ret = check_badwords(p, '*')) < 0)
933 {
934 log_error("check_badwords(title) error");
935 }
936 else if (ret > 0)
937 {
938 memcpy(title_input, p, (size_t)len + 1);
939 continue;
940 }
941 memcpy(p_article_new->title, p, (size_t)len + 1);
942 memcpy(title_input, p_article_new->title, (size_t)len + 1);
943 }
944 ch = 0;
945 break;
946 case 'C':
947 clearscr();
948 moveto(1, 1);
949 prints("取消...");
950 press_any_key();
951 goto cleanup;
952 case 'N':
953 reply_note = (reply_note ? 0 : 1);
954 break;
955 case 'Q':
956 full_quote = (full_quote ? 0 : 1);
957 break;
958 case '0':
959 case '1':
960 case '2':
961 case '3':
962 sign_id = ch - '0';
963 break;
964 default: // Invalid selection
965 ch = igetch_t(BBS_max_user_idle_time);
966 continue;
967 }
968
969 break;
970 }
971
972 if (ch != CR || p_article_new->title[0] == '\0')
973 {
974 continue;
975 }
976
977 len = snprintf(content, ARTICLE_CONTENT_MAX_LEN,
978 "\n\n【 在 %s (%s) 的大作中提到: 】\n",
979 p_article->username, p_article->nickname);
980
981 quote_content_lines = split_data_lines(content_f,
982 MAX_EDITOR_DATA_LINE_LENGTH - 2, line_offsets,
983 (full_quote ? MAX_EDITOR_DATA_LINES : ARTICLE_QUOTE_DEFAULT_LINES) + 1,
984 0, NULL);
985 for (i = 0; i < quote_content_lines; i++)
986 {
987 memcpy(content + len, ": ", 2); // quote line prefix
988 len += 2;
989 memcpy(content + len, content_f + line_offsets[i], (size_t)(line_offsets[i + 1] - line_offsets[i]));
990 len += (line_offsets[i + 1] - line_offsets[i]);
991 if (content[len - 1] != '\n') // Appennd \n if not exist
992 {
993 content[len] = '\n';
994 len++;
995 }
996 }
997 if (content[len - 1] != '\n') // Appennd \n if not exist
998 {
999 content[len] = '\n';
1000 len++;
1001 }
1002 content[len] = '\0';
1003
1004 free(content_f);
1005 content_f = NULL;
1006
1007 p_editor_data = editor_data_load(content);
1008 if (p_editor_data == NULL)
1009 {
1010 log_error("editor_data_load(aid=%d, cid=%d) error", p_article->aid, atoi(row[0]));
1011 ret = -1;
1012 goto cleanup;
1013 }
1014
1015 free(content);
1016 content = NULL;
1017
1018 for (ch = 'E'; !SYS_server_exit && toupper(ch) == 'E';)
1019 {
1020 editor_display(p_editor_data);
1021
1022 clearscr();
1023 moveto(1, 1);
1024 prints("(S)发送, (C)取消, (T)更改标题 or (E)再编辑? [S]: ");
1025 iflush();
1026
1027 for (ch = 0; !SYS_server_exit; ch = igetch_t(BBS_max_user_idle_time))
1028 {
1029 switch (toupper(ch))
1030 {
1031 case KEY_NULL:
1032 case KEY_TIMEOUT:
1033 goto cleanup;
1034 case CR:
1035 case 'S':
1036 break;
1037 case 'C':
1038 clearscr();
1039 moveto(1, 1);
1040 prints("取消...");
1041 press_any_key();
1042 goto cleanup;
1043 case 'T':
1044 break;
1045 case 'E':
1046 break;
1047 default: // Invalid selection
1048 continue;
1049 }
1050
1051 break;
1052 }
1053 }
1054
1055 if (toupper(ch) != 'T')
1056 {
1057 break;
1058 }
1059 }
1060
1061 if (SYS_server_exit) // Do not save data on shutdown
1062 {
1063 goto cleanup;
1064 }
1065
1066 content = malloc(ARTICLE_CONTENT_MAX_LEN);
1067 if (content == NULL)
1068 {
1069 log_error("malloc(content) error: OOM");
1070 ret = -1;
1071 goto cleanup;
1072 }
1073
1074 len_content = editor_data_save(p_editor_data, content, ARTICLE_CONTENT_MAX_LEN);
1075 if (len_content < 0)
1076 {
1077 log_error("editor_data_save() error");
1078 ret = -1;
1079 goto cleanup;
1080 }
1081
1082 if (check_badwords(content, '*') < 0)
1083 {
1084 log_error("check_badwords(content) error");
1085 ret = -1;
1086 goto cleanup;
1087 }
1088
1089 db = db_open();
1090 if (db == NULL)
1091 {
1092 log_error("db_open() error: %s", mysql_error(db));
1093 ret = -1;
1094 goto cleanup;
1095 }
1096
1097 if (sign_id > 0)
1098 {
1099 snprintf(sql, sizeof(sql),
1100 "SELECT sign_%d AS sign FROM user_pubinfo WHERE UID = %d",
1101 sign_id, BBS_priv.uid);
1102
1103 if (mysql_query(db, sql) != 0)
1104 {
1105 log_error("Query sign error: %s", mysql_error(db));
1106 ret = -1;
1107 goto cleanup;
1108 }
1109 if ((rs = mysql_use_result(db)) == NULL)
1110 {
1111 log_error("Get sign data failed");
1112 ret = -1;
1113 goto cleanup;
1114 }
1115
1116 if ((row = mysql_fetch_row(rs)))
1117 {
1118 len_content += snprintf(content + len_content,
1119 ARTICLE_CONTENT_MAX_LEN - (size_t)len_content,
1120 "\n\n--\n%s\n", row[0]);
1121 }
1122 mysql_free_result(rs);
1123 rs = NULL;
1124 }
1125
1126 // Calculate display length of content
1127 content_display_length = str_length(content, 1);
1128
1129 // Begin transaction
1130 if (mysql_query(db, "SET autocommit=0") != 0)
1131 {
1132 log_error("SET autocommit=0 error: %s", mysql_error(db));
1133 ret = -1;
1134 goto cleanup;
1135 }
1136
1137 if (mysql_query(db, "BEGIN") != 0)
1138 {
1139 log_error("Begin transaction error: %s", mysql_error(db));
1140 ret = -1;
1141 goto cleanup;
1142 }
1143
1144 // Secure SQL parameters
1145 content_f = malloc((size_t)len_content * 2 + 1);
1146 if (content_f == NULL)
1147 {
1148 log_error("malloc(content_f) error: OOM");
1149 ret = -1;
1150 goto cleanup;
1151 }
1152
1153 mysql_real_escape_string(db, nickname_f, BBS_nickname, (unsigned long)strnlen(BBS_nickname, sizeof(BBS_nickname)));
1154 mysql_real_escape_string(db, title_f, p_article_new->title, strnlen(p_article_new->title, sizeof(p_article_new->title)));
1155 mysql_real_escape_string(db, content_f, content, (unsigned long)len_content);
1156
1157 free(content);
1158 content = NULL;
1159
1160 // Add content
1161 sql_content = malloc(SQL_BUFFER_LEN + (size_t)len_content * 2 + 1);
1162 if (sql_content == NULL)
1163 {
1164 log_error("malloc(sql_content) error: OOM");
1165 ret = -1;
1166 goto cleanup;
1167 }
1168
1169 snprintf(sql_content, SQL_BUFFER_LEN + (size_t)len_content * 2 + 1,
1170 "INSERT INTO bbs_content(AID, content) values(0, '%s')",
1171 content_f);
1172
1173 free(content_f);
1174 content_f = NULL;
1175
1176 if (mysql_query(db, sql_content) != 0)
1177 {
1178 log_error("Add article content error: %s", mysql_error(db));
1179 ret = -1;
1180 goto cleanup;
1181 }
1182
1183 p_article_new->cid = (int32_t)mysql_insert_id(db);
1184
1185 free(sql_content);
1186 sql_content = NULL;
1187
1188 // Add article
1189 snprintf(sql, sizeof(sql),
1190 "INSERT INTO bbs(SID, TID, UID, username, nickname, title, CID, transship, "
1191 "sub_dt, sub_ip, reply_note, exp, last_reply_dt, icon, length) "
1192 "VALUES(%d, %d, %d, '%s', '%s', '%s', %d, 0, NOW(), '%s', %d, %d, NOW(), 1, %d)",
1193 p_section->sid, (p_article->tid == 0 ? p_article->aid : p_article->tid),
1194 BBS_priv.uid, BBS_username, nickname_f, title_f,
1195 p_article_new->cid, hostaddr_client,
1196 reply_note, BBS_user_exp, content_display_length);
1197
1198 if (mysql_query(db, sql) != 0)
1199 {
1200 log_error("Add article error: %s", mysql_error(db));
1201 ret = -1;
1202 goto cleanup;
1203 }
1204
1205 p_article_new->aid = (int32_t)mysql_insert_id(db);
1206
1207 // Update topic article
1208 snprintf(sql, sizeof(sql),
1209 "UPDATE bbs SET reply_count = reply_count + 1, "
1210 "last_reply_dt = NOW(), last_reply_UID=%d, last_reply_username = '%s', "
1211 "last_reply_nickname = '%s' WHERE AID = %d",
1212 BBS_priv.uid, BBS_username, nickname_f,
1213 (p_article->tid == 0 ? p_article->aid : p_article->tid));
1214
1215 if (mysql_query(db, sql) != 0)
1216 {
1217 log_error("Update topic article error: %s", mysql_error(db));
1218 ret = -1;
1219 goto cleanup;
1220 }
1221
1222 // Link content to article
1223 snprintf(sql, sizeof(sql),
1224 "UPDATE bbs_content SET AID = %d WHERE CID = %d",
1225 p_article_new->aid, p_article_new->cid);
1226
1227 if (mysql_query(db, sql) != 0)
1228 {
1229 log_error("Update content error: %s", mysql_error(db));
1230 ret = -1;
1231 goto cleanup;
1232 }
1233
1234 // Notify the authors of the topic / article which is replyed.
1235 snprintf(sql, sizeof(sql),
1236 "SELECT DISTINCT UID FROM bbs WHERE (AID = %d OR AID = %d) "
1237 "AND visible AND reply_note AND UID <> %d",
1238 p_article->tid, p_article->aid, BBS_priv.uid);
1239
1240 if (mysql_query(db, sql) != 0)
1241 {
1242 log_error("Read reply info error: %s", mysql_error(db));
1243 ret = -1;
1244 goto cleanup;
1245 }
1246 if ((rs = mysql_store_result(db)) == NULL)
1247 {
1248 log_error("Get reply info failed");
1249 ret = -1;
1250 goto cleanup;
1251 }
1252
1253 while ((row = mysql_fetch_row(rs)))
1254 {
1255 // Send notification message
1256 len_msg = snprintf(msg, BBS_msg_max_len,
1257 "有人回复了您所发表/回复的文章,快来"
1258 "[article %d]看看[/article]《%s》吧!\n",
1259 p_article_new->aid, title_f);
1260
1261 mysql_real_escape_string(db, msg_f, msg, (unsigned long)len_msg);
1262
1263 snprintf(sql, sizeof(sql),
1264 "INSERT INTO bbs_msg(fromUID, toUID, content, send_dt, send_ip) "
1265 "VALUES(%d, %d, '%s', NOW(), '%s')",
1266 BBS_sys_id, atoi(row[0]), msg_f, hostaddr_client);
1267
1268 if (mysql_query(db, sql) != 0)
1269 {
1270 log_error("Insert msg error: %s", mysql_error(db));
1271 ret = -1;
1272 goto cleanup;
1273 }
1274 }
1275 mysql_free_result(rs);
1276 rs = NULL;
1277
1278 // Add exp
1279 if (checkpriv(&BBS_priv, p_section->sid, S_GETEXP)) // Except in test section
1280 {
1281 snprintf(sql, sizeof(sql),
1282 "UPDATE user_pubinfo SET exp = exp + %d WHERE UID = %d",
1283 3, BBS_priv.uid);
1284
1285 if (mysql_query(db, sql) != 0)
1286 {
1287 log_error("Update exp error: %s", mysql_error(db));
1288 ret = -1;
1289 goto cleanup;
1290 }
1291 }
1292
1293 // Add log
1294 snprintf(sql, sizeof(sql),
1295 "INSERT INTO bbs_article_op(AID, UID, type, op_dt, op_ip)"
1296 "VALUES(%d, %d, 'A', NOW(), '%s')",
1297 p_article_new->aid, BBS_priv.uid, hostaddr_client);
1298
1299 if (mysql_query(db, sql) != 0)
1300 {
1301 log_error("Add log error: %s", mysql_error(db));
1302 ret = -1;
1303 goto cleanup;
1304 }
1305
1306 // Commit transaction
1307 if (mysql_query(db, "COMMIT") != 0)
1308 {
1309 log_error("Commit transaction error: %s", mysql_error(db));
1310 ret = -1;
1311 goto cleanup;
1312 }
1313
1314 mysql_close(db);
1315 db = NULL;
1316
1317 clearscr();
1318 moveto(1, 1);
1319 prints("发送完成,新文章通常会在%d秒后可见", BBS_section_list_load_interval);
1320 press_any_key();
1321 ret = 1; // Success
1322
1323 cleanup:
1324 mysql_free_result(rs);
1325 mysql_close(db);
1326
1327 // Cleanup buffers
1328 editor_data_cleanup(p_editor_data);
1329
1330 free(sql_content);
1331 free(content);
1332 free(content_f);
1333
1334 return (int)ret;
1335 }

webmaster@leafok.com
ViewVC Help
Powered by ViewVC 1.3.0-beta1