/[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.40 - (show annotations)
Wed Nov 5 03:17:12 2025 UTC (4 months, 1 week ago) by sysadm
Branch: MAIN
Changes since 1.39: +6 -5 lines
Content type: text/x-csrc
Use enum / const int instead of macro define constant integers

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

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