/[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.28 - (show annotations)
Fri Oct 10 01:55:06 2025 UTC (5 months ago) by sysadm
Branch: MAIN
Changes since 1.27: +47 -0 lines
Content type: text/x-csrc
Notify the author(s) of replied of topic / article in article_reply()

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

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