/[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.26 - (show annotations)
Sun Oct 5 05:00:50 2025 UTC (5 months, 1 week ago) by sysadm
Branch: MAIN
Changes since 1.25: +1 -1 lines
Content type: text/x-csrc
Rename lml_plain() to lml_render()

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, DOECHO);
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
734 if (p_section == NULL || p_article == NULL)
735 {
736 log_error("NULL pointer error\n");
737 }
738
739 if (!checkpriv(&BBS_priv, p_section->sid, S_POST))
740 {
741 clearscr();
742 moveto(1, 1);
743 prints("您没有权限在本版块发表文章\n");
744 press_any_key();
745
746 return 0;
747 }
748
749 p_article_new->title[0] = '\0';
750 snprintf(title_input, sizeof(title_input), "Re: %s", p_article->title);
751 len = split_line(title_input, TITLE_INPUT_MAX_LEN, &eol, &display_len, 0);
752 title_input[len] = '\0';
753
754 db = db_open();
755 if (db == NULL)
756 {
757 log_error("db_open() error: %s\n", mysql_error(db));
758 ret = -1;
759 goto cleanup;
760 }
761
762 snprintf(sql, sizeof(sql),
763 "SELECT `lock` FROM bbs WHERE AID = %d",
764 (p_article->tid == 0 ? p_article->aid : p_article->tid));
765
766 if (mysql_query(db, sql) != 0)
767 {
768 log_error("Query article status error: %s\n", mysql_error(db));
769 ret = -1;
770 goto cleanup;
771 }
772 if ((rs = mysql_store_result(db)) == NULL)
773 {
774 log_error("Get article status data failed\n");
775 ret = -1;
776 goto cleanup;
777 }
778
779 if ((row = mysql_fetch_row(rs)))
780 {
781 if (atoi(row[0]) != 0)
782 {
783 topic_locked = 1;
784 }
785 }
786 mysql_free_result(rs);
787 rs = NULL;
788
789 if (topic_locked) // Reply is not allowed
790 {
791 mysql_close(db);
792 db = NULL;
793
794 clearscr();
795 moveto(1, 1);
796 prints("该主题谢绝回复");
797 press_any_key();
798
799 goto cleanup;
800 }
801
802 snprintf(sql, sizeof(sql),
803 "SELECT bbs_content.CID, bbs_content.content "
804 "FROM bbs INNER JOIN bbs_content ON bbs.CID = bbs_content.CID "
805 "WHERE bbs.AID = %d",
806 p_article->aid);
807
808 if (mysql_query(db, sql) != 0)
809 {
810 log_error("Query article content error: %s\n", mysql_error(db));
811 ret = -1;
812 goto cleanup;
813 }
814 if ((rs = mysql_use_result(db)) == NULL)
815 {
816 log_error("Get article content data failed\n");
817 ret = -1;
818 goto cleanup;
819 }
820
821 if ((row = mysql_fetch_row(rs)))
822 {
823 content = malloc(ARTICLE_CONTENT_MAX_LEN);
824 if (content == NULL)
825 {
826 log_error("malloc(content) error: OOM\n");
827 ret = -1;
828 goto cleanup;
829 }
830
831 content_f = malloc(ARTICLE_CONTENT_MAX_LEN);
832 if (content_f == NULL)
833 {
834 log_error("malloc(content_f) error: OOM\n");
835 ret = -1;
836 goto cleanup;
837 }
838
839 // Apply LML render to content body
840 len = lml_render(row[1], content_f, ARTICLE_CONTENT_MAX_LEN, 0);
841 content_f[len] = '\0';
842
843 // Remove control sequence
844 len = str_filter(content_f, 0);
845
846 len = snprintf(content, ARTICLE_CONTENT_MAX_LEN,
847 "\n\n【 在 %s (%s) 的大作中提到: 】\n",
848 p_article->username, p_article->nickname);
849
850 quote_content_lines = split_data_lines(content_f, ARTICLE_QUOTE_LINE_MAX_LEN, line_offsets, ARTICLE_QUOTE_MAX_LINES + 1, 0, NULL);
851 for (i = 0; i < quote_content_lines; i++)
852 {
853 memcpy(content + len, ": ", 2); // quote line prefix
854 len += 2;
855 memcpy(content + len, content_f + line_offsets[i], (size_t)(line_offsets[i + 1] - line_offsets[i]));
856 len += (line_offsets[i + 1] - line_offsets[i]);
857 if (content[len - 1] != '\n') // Appennd \n if not exist
858 {
859 content[len] = '\n';
860 len++;
861 }
862 }
863 if (content[len - 1] != '\n') // Appennd \n if not exist
864 {
865 content[len] = '\n';
866 len++;
867 }
868 content[len] = '\0';
869
870 free(content_f);
871 content_f = NULL;
872
873 p_editor_data = editor_data_load(content);
874 if (p_editor_data == NULL)
875 {
876 log_error("editor_data_load(aid=%d, cid=%d) error\n", p_article->aid, atoi(row[0]));
877 ret = -1;
878 goto cleanup;
879 }
880
881 free(content);
882 content = NULL;
883 }
884 mysql_free_result(rs);
885 rs = NULL;
886
887 mysql_close(db);
888 db = NULL;
889
890 // Set title and sign
891 for (ch = 'T'; !SYS_server_exit;)
892 {
893 clearscr();
894 moveto(21, 1);
895 prints("回复文章于 %s[%s] 讨论区", p_section->stitle, p_section->sname);
896 moveto(22, 1);
897 prints("标题: %s", (p_article_new->title[0] == '\0' ? "[无]" : p_article_new->title));
898 moveto(23, 1);
899 prints("使用第 %d 个签名", sign_id);
900
901 if (toupper(ch) != 'T')
902 {
903 prints(" 按0~3选签名档(0表示不使用)");
904
905 moveto(24, 1);
906 prints("T改标题, C取消, Enter继续: ");
907 iflush();
908 ch = 0;
909 }
910
911 for (; !SYS_server_exit; ch = igetch_t(MAX_DELAY_TIME))
912 {
913 switch (toupper(ch))
914 {
915 case KEY_NULL:
916 case KEY_TIMEOUT:
917 goto cleanup;
918 case CR:
919 igetch_reset();
920 break;
921 case 'T':
922 moveto(24, 1);
923 clrtoeol();
924 len = get_data(24, 1, "标题: ", title_input, sizeof(title_input), TITLE_INPUT_MAX_LEN, DOECHO);
925 for (p = title_input; *p == ' '; p++)
926 ;
927 for (q = title_input + len; q > p && *(q - 1) == ' '; q--)
928 ;
929 *q = '\0';
930 len = q - p;
931 if (*p != '\0')
932 {
933 memcpy(p_article_new->title, p, (size_t)len + 1);
934 memcpy(title_input, p_article_new->title, (size_t)len + 1);
935 }
936 ch = 0;
937 break;
938 case 'C':
939 clearscr();
940 moveto(1, 1);
941 prints("取消...");
942 press_any_key();
943 goto cleanup;
944 case '0':
945 case '1':
946 case '2':
947 case '3':
948 sign_id = ch - '0';
949 break;
950 default: // Invalid selection
951 continue;
952 }
953
954 break;
955 }
956
957 if (ch != CR || p_article_new->title[0] == '\0')
958 {
959 continue;
960 }
961
962 for (ch = 'E'; !SYS_server_exit && toupper(ch) == 'E';)
963 {
964 editor_display(p_editor_data);
965
966 clearscr();
967 moveto(1, 1);
968 prints("(S)发送, (C)取消, (T)更改标题 or (E)再编辑? [S]: ");
969 iflush();
970
971 for (ch = 0; !SYS_server_exit; ch = igetch_t(MAX_DELAY_TIME))
972 {
973 switch (toupper(ch))
974 {
975 case KEY_NULL:
976 case KEY_TIMEOUT:
977 goto cleanup;
978 case CR:
979 igetch_reset();
980 case 'S':
981 break;
982 case 'C':
983 clearscr();
984 moveto(1, 1);
985 prints("取消...");
986 press_any_key();
987 goto cleanup;
988 case 'T':
989 break;
990 case 'E':
991 break;
992 default: // Invalid selection
993 continue;
994 }
995
996 break;
997 }
998 }
999
1000 if (toupper(ch) != 'T')
1001 {
1002 break;
1003 }
1004 }
1005
1006 if (SYS_server_exit) // Do not save data on shutdown
1007 {
1008 goto cleanup;
1009 }
1010
1011 content = malloc(ARTICLE_CONTENT_MAX_LEN);
1012 if (content == NULL)
1013 {
1014 log_error("malloc(content) error: OOM\n");
1015 ret = -1;
1016 goto cleanup;
1017 }
1018
1019 len_content = editor_data_save(p_editor_data, content, ARTICLE_CONTENT_MAX_LEN);
1020 if (len_content < 0)
1021 {
1022 log_error("editor_data_save() error\n");
1023 ret = -1;
1024 goto cleanup;
1025 }
1026
1027 db = db_open();
1028 if (db == NULL)
1029 {
1030 log_error("db_open() error: %s\n", mysql_error(db));
1031 ret = -1;
1032 goto cleanup;
1033 }
1034
1035 if (sign_id > 0)
1036 {
1037 snprintf(sql, sizeof(sql),
1038 "SELECT sign_%d AS sign FROM user_pubinfo WHERE UID = %d",
1039 sign_id, BBS_priv.uid);
1040
1041 if (mysql_query(db, sql) != 0)
1042 {
1043 log_error("Query sign error: %s\n", mysql_error(db));
1044 ret = -1;
1045 goto cleanup;
1046 }
1047 if ((rs = mysql_use_result(db)) == NULL)
1048 {
1049 log_error("Get sign data failed\n");
1050 ret = -1;
1051 goto cleanup;
1052 }
1053
1054 if ((row = mysql_fetch_row(rs)))
1055 {
1056 len_content += snprintf(content + len_content,
1057 ARTICLE_CONTENT_MAX_LEN - (size_t)len_content,
1058 "\n\n--\n%s\n", row[0]);
1059 }
1060 mysql_free_result(rs);
1061 rs = NULL;
1062 }
1063
1064 // Calculate display length of content
1065 content_display_length = str_length(content, 1);
1066
1067 // Begin transaction
1068 if (mysql_query(db, "SET autocommit=0") != 0)
1069 {
1070 log_error("SET autocommit=0 error: %s\n", mysql_error(db));
1071 ret = -1;
1072 goto cleanup;
1073 }
1074
1075 if (mysql_query(db, "BEGIN") != 0)
1076 {
1077 log_error("Begin transaction error: %s\n", mysql_error(db));
1078 ret = -1;
1079 goto cleanup;
1080 }
1081
1082 // Secure SQL parameters
1083 content_f = malloc((size_t)len_content * 2 + 1);
1084 if (content_f == NULL)
1085 {
1086 log_error("malloc(content_f) error: OOM\n");
1087 ret = -1;
1088 goto cleanup;
1089 }
1090
1091 mysql_real_escape_string(db, nickname_f, BBS_nickname, (unsigned long)strnlen(BBS_nickname, sizeof(BBS_nickname)));
1092 mysql_real_escape_string(db, title_f, p_article_new->title, strnlen(p_article_new->title, sizeof(p_article_new->title)));
1093 mysql_real_escape_string(db, content_f, content, (unsigned long)len_content);
1094
1095 free(content);
1096 content = NULL;
1097
1098 // Add content
1099 sql_content = malloc(SQL_BUFFER_LEN + (size_t)len_content * 2 + 1);
1100 if (sql_content == NULL)
1101 {
1102 log_error("malloc(sql_content) error: OOM\n");
1103 ret = -1;
1104 goto cleanup;
1105 }
1106
1107 snprintf(sql_content, SQL_BUFFER_LEN + (size_t)len_content * 2 + 1,
1108 "INSERT INTO bbs_content(AID, content) values(0, '%s')",
1109 content_f);
1110
1111 free(content_f);
1112 content_f = NULL;
1113
1114 if (mysql_query(db, sql_content) != 0)
1115 {
1116 log_error("Add article content error: %s\n", mysql_error(db));
1117 ret = -1;
1118 goto cleanup;
1119 }
1120
1121 p_article_new->cid = (int32_t)mysql_insert_id(db);
1122
1123 free(sql_content);
1124 sql_content = NULL;
1125
1126 // Add article
1127 snprintf(sql, sizeof(sql),
1128 "INSERT INTO bbs(SID, TID, UID, username, nickname, title, CID, transship, "
1129 "sub_dt, sub_ip, reply_note, exp, last_reply_dt, icon, length) "
1130 "VALUES(%d, %d, %d, '%s', '%s', '%s', %d, 0, NOW(), '%s', 1, %d, NOW(), 1, %d)",
1131 p_section->sid, (p_article->tid == 0 ? p_article->aid : p_article->tid),
1132 BBS_priv.uid, BBS_username, nickname_f, title_f,
1133 p_article_new->cid, hostaddr_client, BBS_user_exp, content_display_length);
1134
1135 if (mysql_query(db, sql) != 0)
1136 {
1137 log_error("Add article error: %s\n", mysql_error(db));
1138 ret = -1;
1139 goto cleanup;
1140 }
1141
1142 p_article_new->aid = (int32_t)mysql_insert_id(db);
1143
1144 // Update topic article
1145 snprintf(sql, sizeof(sql),
1146 "UPDATE bbs SET reply_count = reply_count + 1, "
1147 "last_reply_dt = NOW(), last_reply_UID=%d, last_reply_username = '%s', "
1148 "last_reply_nickname = '%s' WHERE AID = %d",
1149 BBS_priv.uid, BBS_username, nickname_f,
1150 (p_article->tid == 0 ? p_article->aid : p_article->tid));
1151
1152 if (mysql_query(db, sql) != 0)
1153 {
1154 log_error("Update topic article error: %s\n", mysql_error(db));
1155 ret = -1;
1156 goto cleanup;
1157 }
1158
1159 // Link content to article
1160 snprintf(sql, sizeof(sql),
1161 "UPDATE bbs_content SET AID = %d WHERE CID = %d",
1162 p_article_new->aid, p_article_new->cid);
1163
1164 if (mysql_query(db, sql) != 0)
1165 {
1166 log_error("Update content error: %s\n", mysql_error(db));
1167 ret = -1;
1168 goto cleanup;
1169 }
1170
1171 // Add exp
1172 if (checkpriv(&BBS_priv, p_section->sid, S_GETEXP)) // Except in test section
1173 {
1174 snprintf(sql, sizeof(sql),
1175 "UPDATE user_pubinfo SET exp = exp + %d WHERE UID = %d",
1176 3, BBS_priv.uid);
1177
1178 if (mysql_query(db, sql) != 0)
1179 {
1180 log_error("Update exp error: %s\n", mysql_error(db));
1181 ret = -1;
1182 goto cleanup;
1183 }
1184 }
1185
1186 // Add log
1187 snprintf(sql, sizeof(sql),
1188 "INSERT INTO bbs_article_op(AID, UID, type, op_dt, op_ip)"
1189 "VALUES(%d, %d, 'A', NOW(), '%s')",
1190 p_article_new->aid, BBS_priv.uid, hostaddr_client);
1191
1192 if (mysql_query(db, sql) != 0)
1193 {
1194 log_error("Add log error: %s\n", mysql_error(db));
1195 ret = -1;
1196 goto cleanup;
1197 }
1198
1199 // Commit transaction
1200 if (mysql_query(db, "COMMIT") != 0)
1201 {
1202 log_error("Commit transaction error: %s\n", mysql_error(db));
1203 ret = -1;
1204 goto cleanup;
1205 }
1206
1207 mysql_close(db);
1208 db = NULL;
1209
1210 clearscr();
1211 moveto(1, 1);
1212 prints("发送完成,新文章通常会在%d秒后可见", BBS_section_list_load_interval);
1213 press_any_key();
1214 ret = 1; // Success
1215
1216 cleanup:
1217 mysql_free_result(rs);
1218 mysql_close(db);
1219
1220 // Cleanup buffers
1221 editor_data_cleanup(p_editor_data);
1222
1223 free(sql_content);
1224 free(content);
1225 free(content_f);
1226
1227 return (int)ret;
1228 }

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