/[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.35 - (show annotations)
Thu Oct 30 07:51:47 2025 UTC (4 months, 2 weeks ago) by sysadm
Branch: MAIN
Changes since 1.34: +2 -3 lines
Content type: text/x-csrc
Add display width limit of output lines in lml_render().
It fixed the issue that, the quoted text line in article_reply() was wrapped by split_data_lines(),
but the new sub-lines were not added the same repeated ": " leading sequence as the original line.

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

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