/[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.29 - (show annotations)
Fri Oct 10 03:50:22 2025 UTC (5 months ago) by sysadm
Branch: MAIN
Changes since 1.28: +42 -25 lines
Content type: text/x-csrc
Add set / unset reply_note in article_post() / article_modify() / article_reply()

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

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