/[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.37 - (show annotations)
Tue Nov 4 13:49:51 2025 UTC (4 months, 1 week ago) by sysadm
Branch: MAIN
Changes since 1.36: +7 -15 lines
Content type: text/x-csrc
Update file header information comments

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

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