/[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.33 - (show annotations)
Fri Oct 17 01:25:08 2025 UTC (4 months, 4 weeks ago) by sysadm
Branch: MAIN
Changes since 1.32: +0 -5 lines
Content type: text/x-csrc
No longer use igetch_reset() to skip remaining \n after \r
\r\n -> \r and \n -> \r conversions have already been implemented in igetch()

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

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