/[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.42 - (show annotations)
Mon Nov 10 14:22:18 2025 UTC (4 months ago) by sysadm
Branch: MAIN
Changes since 1.41: +54 -44 lines
Content type: text/x-csrc
Add selection of quote mode (Full / Abbr.)

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

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