--- lbbs/src/article_post.c 2025/06/15 00:02:40 1.13 +++ lbbs/src/article_post.c 2025/11/11 00:28:05 1.43 @@ -1,42 +1,37 @@ -/*************************************************************************** - article_post.c - description - ------------------- - copyright : (C) 2004-2025 by Leaflet - email : leaflet@leafok.com - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 3 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * article_post + * - user interactive feature to post / modify / reply article + * + * Copyright (C) 2004-2025 Leaflet + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif -#define _POSIX_C_SOURCE 200809L - -#include "article_post.h" #include "article_cache.h" -#include "editor.h" -#include "screen.h" +#include "article_post.h" #include "bbs.h" -#include "log.h" +#include "bwf.h" +#include "database.h" +#include "editor.h" #include "io.h" +#include "log.h" #include "lml.h" -#include "database.h" +#include "screen.h" #include "user_priv.h" #include #include #include #include -#define TITLE_INPUT_MAX_LEN 74 -#define ARTICLE_CONTENT_MAX_LEN 1024 * 1024 * 4 // 4MB -#define ARTICLE_QUOTE_MAX_LINES 20 -#define ARTICLE_QUOTE_LINE_MAX_LEN 76 - -#define MODIFY_DT_MAX_LEN 50 +enum _article_post_constant_t +{ + TITLE_INPUT_MAX_LEN = 72, + ARTICLE_QUOTE_DEFAULT_LINES = 20, + MODIFY_DT_MAX_LEN = 50, +}; int article_post(const SECTION_LIST *p_section, ARTICLE *p_article_new) { @@ -46,13 +41,15 @@ int article_post(const SECTION_LIST *p_s char sql[SQL_BUFFER_LEN]; char *sql_content = NULL; EDITOR_DATA *p_editor_data = NULL; - char title_input[TITLE_INPUT_MAX_LEN + 1]; + char title_input[BBS_article_title_max_len + 1]; char title_f[BBS_article_title_max_len * 2 + 1]; char *content = NULL; char *content_f = NULL; long len_content; + int content_display_length; char nickname_f[BBS_nickname_max_len * 2 + 1]; int sign_id = 0; + int reply_note = 1; long len; int ch; char *p, *q; @@ -67,7 +64,7 @@ int article_post(const SECTION_LIST *p_s { clearscr(); moveto(1, 1); - prints("您没有权限在本版块发表文章\n"); + prints("鎮ㄦ病鏈夋潈闄愬湪鏈増鍧楀彂琛ㄦ枃绔燶n"); press_any_key(); return 0; @@ -81,7 +78,8 @@ int article_post(const SECTION_LIST *p_s if (p_editor_data == NULL) { log_error("editor_data_load() error\n"); - return -2; + ret = -1; + goto cleanup; } // Set title and sign @@ -89,33 +87,38 @@ int article_post(const SECTION_LIST *p_s { clearscr(); moveto(21, 1); - prints("发表文章于 %s[%s] 讨论区,类型: %s", p_section->stitle, p_section->sname, (p_article_new->transship ? "转载" : "原创")); + prints("鍙戣〃鏂囩珷浜 %s[%s] 璁ㄨ鍖猴紝绫诲瀷: %s锛屽洖澶嶉氱煡: %s", + p_section->stitle, p_section->sname, + (p_article_new->transship ? "杞浇" : "鍘熷垱"), + (reply_note ? "寮鍚" : "鍏抽棴")); moveto(22, 1); - prints("标题: %s", (p_article_new->title[0] == '\0' ? "[无]" : p_article_new->title)); + prints("鏍囬: %s", (p_article_new->title[0] == '\0' ? "[鏃燷" : p_article_new->title)); moveto(23, 1); - prints("使用第 %d 个签名", sign_id); + prints("浣跨敤绗 %d 涓鍚", sign_id); if (toupper(ch) != 'T') { - prints(" 按0~3选签名档(0表示不使用)"); + prints(" 鎸0~3閫夌鍚嶆。(0琛ㄧず涓嶄娇鐢)"); moveto(24, 1); - prints("T改标题, C取消, Z设为转载, Y设为原创, Enter继续: "); + prints("T鏀规爣棰, C鍙栨秷, Z璁句负%s, N%s, Enter缁х画: ", + (p_article_new->transship ? "鍘熷垱" : "杞浇"), + (reply_note ? "鍏抽棴鍥炲閫氱煡" : "寮鍚洖澶嶉氱煡")); iflush(); ch = 0; } - for (; !SYS_server_exit; ch = igetch_t(MAX_DELAY_TIME)) + while (!SYS_server_exit) { switch (toupper(ch)) { + case KEY_NULL: + case KEY_TIMEOUT: + goto cleanup; case CR: - igetch_reset(); break; case 'T': - moveto(24, 1); - clrtoeol(); - len = get_data(24, 1, "标题: ", title_input, TITLE_INPUT_MAX_LEN, 1); + len = get_data(24, 1, "鏍囬: ", title_input, sizeof(title_input), TITLE_INPUT_MAX_LEN); for (p = title_input; *p == ' '; p++) ; for (q = title_input + len; q > p && *(q - 1) == ' '; q--) @@ -124,6 +127,15 @@ int article_post(const SECTION_LIST *p_s len = q - p; if (*p != '\0') { + if ((ret = check_badwords(p, '*')) < 0) + { + log_error("check_badwords(title) error\n"); + } + else if (ret > 0) + { + memcpy(title_input, p, (size_t)len + 1); + continue; + } memcpy(p_article_new->title, p, (size_t)len + 1); memcpy(title_input, p_article_new->title, (size_t)len + 1); } @@ -132,14 +144,14 @@ int article_post(const SECTION_LIST *p_s case 'C': clearscr(); moveto(1, 1); - prints("取消..."); + prints("鍙栨秷..."); press_any_key(); goto cleanup; - case 'Y': - p_article_new->transship = 0; - break; case 'Z': - p_article_new->transship = 1; + p_article_new->transship = (p_article_new->transship ? 0 : 1); + break; + case 'N': + reply_note = (reply_note ? 0 : 1); break; case '0': case '1': @@ -148,6 +160,7 @@ int article_post(const SECTION_LIST *p_s sign_id = ch - '0'; break; default: // Invalid selection + ch = igetch_t(BBS_max_user_idle_time); continue; } @@ -165,21 +178,23 @@ int article_post(const SECTION_LIST *p_s clearscr(); moveto(1, 1); - prints("(S)发送, (C)取消, (T)更改标题 or (E)再编辑? [S]: "); + prints("(S)鍙戦, (C)鍙栨秷, (T)鏇存敼鏍囬 or (E)鍐嶇紪杈? [S]: "); iflush(); - for (ch = 0; !SYS_server_exit; ch = igetch_t(MAX_DELAY_TIME)) + for (ch = 0; !SYS_server_exit; ch = igetch_t(BBS_max_user_idle_time)) { switch (toupper(ch)) { + case KEY_NULL: + case KEY_TIMEOUT: + goto cleanup; case CR: - igetch_reset(); case 'S': break; case 'C': clearscr(); moveto(1, 1); - prints("取消..."); + prints("鍙栨秷..."); press_any_key(); goto cleanup; case 'T': @@ -200,6 +215,11 @@ int article_post(const SECTION_LIST *p_s } } + if (SYS_server_exit) // Do not save data on shutdown + { + goto cleanup; + } + content = malloc(ARTICLE_CONTENT_MAX_LEN); if (content == NULL) { @@ -212,7 +232,14 @@ int article_post(const SECTION_LIST *p_s if (len_content < 0) { log_error("editor_data_save() error\n"); - ret = -2; + ret = -1; + goto cleanup; + } + + if (check_badwords(content, '*') < 0) + { + log_error("check_badwords(content) error\n"); + ret = -1; goto cleanup; } @@ -253,6 +280,9 @@ int article_post(const SECTION_LIST *p_s rs = NULL; } + // Calculate display length of content + content_display_length = str_length(content, 1); + // Begin transaction if (mysql_query(db, "SET autocommit=0") != 0) { @@ -316,9 +346,10 @@ int article_post(const SECTION_LIST *p_s snprintf(sql, sizeof(sql), "INSERT INTO bbs(SID, TID, UID, username, nickname, title, CID, transship, " "sub_dt, sub_ip, reply_note, exp, last_reply_dt, icon, length) " - "VALUES(%d, 0, %d, '%s', '%s', '%s', %d, %d, NOW(), '%s', 1, %d, NOW(), 1, %ld)", + "VALUES(%d, 0, %d, '%s', '%s', '%s', %d, %d, NOW(), '%s', %d, %d, NOW(), 1, %d)", p_section->sid, BBS_priv.uid, BBS_username, nickname_f, title_f, - p_article_new->cid, p_article_new->transship, hostaddr_client, BBS_user_exp, len_content); + p_article_new->cid, p_article_new->transship, hostaddr_client, + reply_note, BBS_user_exp, content_display_length); if (mysql_query(db, sql) != 0) { @@ -377,9 +408,12 @@ int article_post(const SECTION_LIST *p_s goto cleanup; } + mysql_close(db); + db = NULL; + clearscr(); moveto(1, 1); - prints("发送完成,新文章通常会在%d秒后可见", BBS_section_list_load_interval); + prints("鍙戦佸畬鎴愶紝鏂版枃绔犻氬父浼氬湪%d绉掑悗鍙", BBS_section_list_load_interval); press_any_key(); ret = 1; // Success @@ -406,6 +440,8 @@ int article_modify(const SECTION_LIST *p char *content = NULL; char *content_f = NULL; long len_content; + int content_display_length; + int reply_note = 1; int ch; long ret = 0; time_t now; @@ -423,17 +459,7 @@ int article_modify(const SECTION_LIST *p { clearscr(); moveto(1, 1); - prints("该文章无法被编辑,请联系版主。"); - press_any_key(); - - return 0; - } - - if (!checkpriv(&BBS_priv, p_section->sid, S_POST)) - { - clearscr(); - moveto(1, 1); - prints("您没有权限在本版块发表文章\n"); + prints("璇ユ枃绔犳棤娉曡缂栬緫锛岃鑱旂郴鐗堜富銆"); press_any_key(); return 0; @@ -443,11 +469,12 @@ int article_modify(const SECTION_LIST *p if (db == NULL) { log_error("db_open() error: %s\n", mysql_error(db)); - return -1; + ret = -1; + goto cleanup; } snprintf(sql, sizeof(sql), - "SELECT bbs_content.CID, bbs_content.content " + "SELECT bbs_content.CID, bbs_content.content, reply_note " "FROM bbs INNER JOIN bbs_content ON bbs.CID = bbs_content.CID " "WHERE bbs.AID = %d", p_article->aid); @@ -455,13 +482,13 @@ int article_modify(const SECTION_LIST *p if (mysql_query(db, sql) != 0) { log_error("Query article content error: %s\n", mysql_error(db)); - ret = -2; + ret = -1; goto cleanup; } if ((rs = mysql_use_result(db)) == NULL) { log_error("Get article content data failed\n"); - ret = -2; + ret = -1; goto cleanup; } @@ -479,18 +506,20 @@ int article_modify(const SECTION_LIST *p content[ARTICLE_CONTENT_MAX_LEN - 1] = '\0'; // Remove control sequence - len_content = ctrl_seq_filter(content); + len_content = str_filter(content, 0); p_editor_data = editor_data_load(content); if (p_editor_data == NULL) { log_error("editor_data_load(aid=%d, cid=%d) error\n", p_article->aid, atoi(row[0])); - ret = -3; + ret = -1; goto cleanup; } free(content); content = NULL; + + reply_note = atoi(row[2]); } mysql_free_result(rs); rs = NULL; @@ -502,25 +531,32 @@ int article_modify(const SECTION_LIST *p { editor_display(p_editor_data); - clearscr(); - moveto(1, 1); - prints("(S)保存, (C)取消 or (E)再编辑? [S]: "); - iflush(); - - for (ch = 0; !SYS_server_exit; ch = igetch_t(MAX_DELAY_TIME)) + while (!SYS_server_exit) { + clearscr(); + moveto(1, 1); + prints("(S)淇濆瓨, (C)鍙栨秷, (N)%s鍥炲閫氱煡 or (E)鍐嶇紪杈? [S]: ", + (reply_note ? "鍏抽棴" : "寮鍚")); + iflush(); + + ch = igetch_t(BBS_max_user_idle_time); switch (toupper(ch)) { + case KEY_NULL: + case KEY_TIMEOUT: + goto cleanup; case CR: - igetch_reset(); case 'S': break; case 'C': clearscr(); moveto(1, 1); - prints("取消..."); + prints("鍙栨秷..."); press_any_key(); goto cleanup; + case 'N': + reply_note = (reply_note ? 0 : 1); + continue; case 'E': break; default: // Invalid selection @@ -531,6 +567,11 @@ int article_modify(const SECTION_LIST *p } } + if (SYS_server_exit) // Do not save data on shutdown + { + goto cleanup; + } + // Allocate buffers in big size content = malloc(ARTICLE_CONTENT_MAX_LEN); if (content == NULL) @@ -544,7 +585,14 @@ int article_modify(const SECTION_LIST *p if (len_content < 0) { log_error("editor_data_save() error\n"); - ret = -2; + ret = -1; + goto cleanup; + } + + if (check_badwords(content, '*') < 0) + { + log_error("check_badwords(content) error\n"); + ret = -1; goto cleanup; } @@ -553,9 +601,12 @@ int article_modify(const SECTION_LIST *p strftime(str_modify_dt, sizeof(str_modify_dt), "%Y-%m-%d %H:%M:%S (UTC %z)", &tm_modify_dt); len_content += snprintf(content + len_content, LINE_BUFFER_LEN, - "\n--\n※ 作者已于 %s 修改本文※\n", + "\n--\n鈥 浣滆呭凡浜 %s 淇敼鏈枃鈥籠n", str_modify_dt); + // Calculate display length of content + content_display_length = str_length(content, 1); + db = db_open(); if (db == NULL) { @@ -623,8 +674,8 @@ int article_modify(const SECTION_LIST *p // Update article snprintf(sql, sizeof(sql), - "UPDATE bbs SET CID = %d, length = %ld WHERE AID = %d", - p_article_new->cid, len_content, p_article->aid); + "UPDATE bbs SET CID = %d, length = %d, reply_note = %d, excerption = 0 WHERE AID = %d", // Set excerption = 0 explictly in case of rare condition + p_article_new->cid, content_display_length, reply_note, p_article->aid); if (mysql_query(db, sql) != 0) { @@ -661,9 +712,12 @@ int article_modify(const SECTION_LIST *p goto cleanup; } + mysql_close(db); + db = NULL; + clearscr(); moveto(1, 1); - prints("修改完成,新内容通常会在%d秒后可见", BBS_section_list_load_interval); + prints("淇敼瀹屾垚锛屾柊鍐呭閫氬父浼氬湪%d绉掑悗鍙", BBS_section_list_load_interval); press_any_key(); ret = 1; // Success @@ -686,7 +740,7 @@ int article_reply(const SECTION_LIST *p_ MYSQL *db = NULL; MYSQL_RES *rs = NULL; MYSQL_ROW row; - long line_offsets[ARTICLE_QUOTE_MAX_LINES + 1]; + long line_offsets[MAX_EDITOR_DATA_LINES + 1]; char sql[SQL_BUFFER_LEN]; char *sql_content = NULL; EDITOR_DATA *p_editor_data = NULL; @@ -695,8 +749,11 @@ int article_reply(const SECTION_LIST *p_ char *content = NULL; char *content_f = NULL; long len_content; + int content_display_length; char nickname_f[BBS_nickname_max_len * 2 + 1]; int sign_id = 0; + int reply_note = 0; + int full_quote = 0; long len; int ch; char *p, *q; @@ -705,6 +762,10 @@ int article_reply(const SECTION_LIST *p_ long quote_content_lines; long i; long ret = 0; + int topic_locked = 0; + char msg[BBS_msg_max_len]; + char msg_f[BBS_msg_max_len * 2 + 1]; + int len_msg; if (p_section == NULL || p_article == NULL) { @@ -715,17 +776,7 @@ int article_reply(const SECTION_LIST *p_ { clearscr(); moveto(1, 1); - prints("您没有权限在本版块发表文章\n"); - press_any_key(); - - return 0; - } - - if (p_article->lock) // Reply is not allowed - { - clearscr(); - moveto(1, 1); - prints("该文章谢绝回复"); + prints("鎮ㄦ病鏈夋潈闄愬湪鏈増鍧楀彂琛ㄦ枃绔燶n"); press_any_key(); return 0; @@ -733,14 +784,55 @@ int article_reply(const SECTION_LIST *p_ p_article_new->title[0] = '\0'; snprintf(title_input, sizeof(title_input), "Re: %s", p_article->title); - len = split_line(title_input, TITLE_INPUT_MAX_LEN, &eol, &display_len); + len = split_line(title_input, TITLE_INPUT_MAX_LEN, &eol, &display_len, 0); title_input[len] = '\0'; db = db_open(); if (db == NULL) { log_error("db_open() error: %s\n", mysql_error(db)); - return -1; + ret = -1; + goto cleanup; + } + + snprintf(sql, sizeof(sql), + "SELECT `lock` FROM bbs WHERE AID = %d", + (p_article->tid == 0 ? p_article->aid : p_article->tid)); + + if (mysql_query(db, sql) != 0) + { + log_error("Query article status error: %s\n", mysql_error(db)); + ret = -1; + goto cleanup; + } + if ((rs = mysql_store_result(db)) == NULL) + { + log_error("Get article status data failed\n"); + ret = -1; + goto cleanup; + } + + if ((row = mysql_fetch_row(rs))) + { + if (atoi(row[0]) != 0) + { + topic_locked = 1; + } + } + mysql_free_result(rs); + rs = NULL; + + if (topic_locked) // Reply is not allowed + { + mysql_close(db); + db = NULL; + + clearscr(); + moveto(1, 1); + prints("璇ヤ富棰樿阿缁濆洖澶"); + press_any_key(); + + goto cleanup; } snprintf(sql, sizeof(sql), @@ -752,12 +844,14 @@ int article_reply(const SECTION_LIST *p_ if (mysql_query(db, sql) != 0) { log_error("Query article content error: %s\n", mysql_error(db)); - return -2; + ret = -1; + goto cleanup; } if ((rs = mysql_use_result(db)) == NULL) { log_error("Get article content data failed\n"); - return -2; + ret = -1; + goto cleanup; } if ((row = mysql_fetch_row(rs))) @@ -779,44 +873,11 @@ int article_reply(const SECTION_LIST *p_ } // Apply LML render to content body - len = lml_plain(row[1], content_f, ARTICLE_CONTENT_MAX_LEN); + len = lml_render(row[1], content_f, ARTICLE_CONTENT_MAX_LEN, MAX_EDITOR_DATA_LINE_LENGTH - 3, 1); content_f[len] = '\0'; // Remove control sequence - len = ctrl_seq_filter(content_f); - - len = snprintf(content, ARTICLE_CONTENT_MAX_LEN, - "\n\n【 在 %s (%s) 的大作中提到: 】\n", - p_article->username, p_article->nickname); - - quote_content_lines = split_data_lines(content_f, ARTICLE_QUOTE_LINE_MAX_LEN, line_offsets, ARTICLE_QUOTE_MAX_LINES + 1); - for (i = 0; i < quote_content_lines; i++) - { - memcpy(content + len, ": ", 2); // quote line prefix - len += 2; - memcpy(content + len, content_f + line_offsets[i], (size_t)(line_offsets[i + 1] - line_offsets[i])); - len += (line_offsets[i + 1] - line_offsets[i]); - } - if (content[len - 1] != '\n') // Appennd \n if not exist - { - content[len] = '\n'; - len++; - } - content[len] = '\0'; - - free(content_f); - content_f = NULL; - - p_editor_data = editor_data_load(content); - if (p_editor_data == NULL) - { - log_error("editor_data_load(aid=%d, cid=%d) error\n", p_article->aid, atoi(row[0])); - ret = -3; - goto cleanup; - } - - free(content); - content = NULL; + len = str_filter(content_f, 0); } mysql_free_result(rs); rs = NULL; @@ -829,33 +890,37 @@ int article_reply(const SECTION_LIST *p_ { clearscr(); moveto(21, 1); - prints("回复文章于 %s[%s] 讨论区", p_section->stitle, p_section->sname); + prints("鍥炲鏂囩珷浜 %s[%s] 璁ㄨ鍖, 鍥炲閫氱煡: %s, 寮曠敤妯″紡: %s", + p_section->stitle, p_section->sname, + (reply_note ? "寮鍚" : "鍏抽棴"), + (full_quote ? "瀹屾暣" : "绮剧畝")); moveto(22, 1); - prints("标题: %s", (p_article_new->title[0] == '\0' ? "[无]" : p_article_new->title)); + prints("鏍囬: %s", (p_article_new->title[0] == '\0' ? "[鏃燷" : p_article_new->title)); moveto(23, 1); - prints("使用第 %d 个签名", sign_id); + prints("浣跨敤绗 %d 涓鍚", sign_id); if (toupper(ch) != 'T') { - prints(" 按0~3选签名档(0表示不使用)"); + prints(" 鎸0~3閫夌鍚嶆。(0琛ㄧず涓嶄娇鐢)"); moveto(24, 1); - prints("T改标题, C取消, Enter继续: "); + prints("T鏀规爣棰, C鍙栨秷, N%s, Q%s, Enter缁х画: ", + (reply_note ? "鍏抽棴鍥炲閫氱煡" : "寮鍚洖澶嶉氱煡"), (full_quote ? "绮剧畝寮曠敤" : "瀹屾暣寮曠敤")); iflush(); ch = 0; } - for (; !SYS_server_exit; ch = igetch_t(MAX_DELAY_TIME)) + while (!SYS_server_exit) { switch (toupper(ch)) { + case KEY_NULL: + case KEY_TIMEOUT: + goto cleanup; case CR: - igetch_reset(); break; case 'T': - moveto(24, 1); - clrtoeol(); - len = get_data(24, 1, "标题: ", title_input, TITLE_INPUT_MAX_LEN, 1); + len = get_data(24, 1, "鏍囬: ", title_input, sizeof(title_input), TITLE_INPUT_MAX_LEN); for (p = title_input; *p == ' '; p++) ; for (q = title_input + len; q > p && *(q - 1) == ' '; q--) @@ -864,6 +929,15 @@ int article_reply(const SECTION_LIST *p_ len = q - p; if (*p != '\0') { + if ((ret = check_badwords(p, '*')) < 0) + { + log_error("check_badwords(title) error\n"); + } + else if (ret > 0) + { + memcpy(title_input, p, (size_t)len + 1); + continue; + } memcpy(p_article_new->title, p, (size_t)len + 1); memcpy(title_input, p_article_new->title, (size_t)len + 1); } @@ -872,9 +946,15 @@ int article_reply(const SECTION_LIST *p_ case 'C': clearscr(); moveto(1, 1); - prints("取消..."); + prints("鍙栨秷..."); press_any_key(); goto cleanup; + case 'N': + reply_note = (reply_note ? 0 : 1); + break; + case 'Q': + full_quote = (full_quote ? 0 : 1); + break; case '0': case '1': case '2': @@ -882,6 +962,7 @@ int article_reply(const SECTION_LIST *p_ sign_id = ch - '0'; break; default: // Invalid selection + ch = igetch_t(BBS_max_user_idle_time); continue; } @@ -893,27 +974,70 @@ int article_reply(const SECTION_LIST *p_ continue; } + len = snprintf(content, ARTICLE_CONTENT_MAX_LEN, + "\n\n銆 鍦 %s (%s) 鐨勫ぇ浣滀腑鎻愬埌: 銆慭n", + p_article->username, p_article->nickname); + + quote_content_lines = split_data_lines(content_f, + MAX_EDITOR_DATA_LINE_LENGTH - 2, line_offsets, + (full_quote ? MAX_EDITOR_DATA_LINES : ARTICLE_QUOTE_DEFAULT_LINES) + 1, + 0, NULL); + for (i = 0; i < quote_content_lines; i++) + { + memcpy(content + len, ": ", 2); // quote line prefix + len += 2; + memcpy(content + len, content_f + line_offsets[i], (size_t)(line_offsets[i + 1] - line_offsets[i])); + len += (line_offsets[i + 1] - line_offsets[i]); + if (content[len - 1] != '\n') // Appennd \n if not exist + { + content[len] = '\n'; + len++; + } + } + if (content[len - 1] != '\n') // Appennd \n if not exist + { + content[len] = '\n'; + len++; + } + content[len] = '\0'; + + free(content_f); + content_f = NULL; + + p_editor_data = editor_data_load(content); + if (p_editor_data == NULL) + { + log_error("editor_data_load(aid=%d, cid=%d) error\n", p_article->aid, atoi(row[0])); + ret = -1; + goto cleanup; + } + + free(content); + content = NULL; + for (ch = 'E'; !SYS_server_exit && toupper(ch) == 'E';) { editor_display(p_editor_data); clearscr(); moveto(1, 1); - prints("(S)发送, (C)取消, (T)更改标题 or (E)再编辑? [S]: "); + prints("(S)鍙戦, (C)鍙栨秷, (T)鏇存敼鏍囬 or (E)鍐嶇紪杈? [S]: "); iflush(); - for (ch = 0; !SYS_server_exit; ch = igetch_t(MAX_DELAY_TIME)) + for (ch = 0; !SYS_server_exit; ch = igetch_t(BBS_max_user_idle_time)) { switch (toupper(ch)) { + case KEY_NULL: + case KEY_TIMEOUT: + goto cleanup; case CR: - igetch_reset(); case 'S': break; case 'C': clearscr(); moveto(1, 1); - prints("取消..."); + prints("鍙栨秷..."); press_any_key(); goto cleanup; case 'T': @@ -934,6 +1058,11 @@ int article_reply(const SECTION_LIST *p_ } } + if (SYS_server_exit) // Do not save data on shutdown + { + goto cleanup; + } + content = malloc(ARTICLE_CONTENT_MAX_LEN); if (content == NULL) { @@ -946,7 +1075,14 @@ int article_reply(const SECTION_LIST *p_ if (len_content < 0) { log_error("editor_data_save() error\n"); - ret = -2; + ret = -1; + goto cleanup; + } + + if (check_badwords(content, '*') < 0) + { + log_error("check_badwords(content) error\n"); + ret = -1; goto cleanup; } @@ -987,6 +1123,9 @@ int article_reply(const SECTION_LIST *p_ rs = NULL; } + // Calculate display length of content + content_display_length = str_length(content, 1); + // Begin transaction if (mysql_query(db, "SET autocommit=0") != 0) { @@ -1050,10 +1189,11 @@ int article_reply(const SECTION_LIST *p_ snprintf(sql, sizeof(sql), "INSERT INTO bbs(SID, TID, UID, username, nickname, title, CID, transship, " "sub_dt, sub_ip, reply_note, exp, last_reply_dt, icon, length) " - "VALUES(%d, %d, %d, '%s', '%s', '%s', %d, 0, NOW(), '%s', 1, %d, NOW(), 1, %ld)", + "VALUES(%d, %d, %d, '%s', '%s', '%s', %d, 0, NOW(), '%s', %d, %d, NOW(), 1, %d)", p_section->sid, (p_article->tid == 0 ? p_article->aid : p_article->tid), BBS_priv.uid, BBS_username, nickname_f, title_f, - p_article_new->cid, hostaddr_client, BBS_user_exp, len_content); + p_article_new->cid, hostaddr_client, + reply_note, BBS_user_exp, content_display_length); if (mysql_query(db, sql) != 0) { @@ -1091,6 +1231,50 @@ int article_reply(const SECTION_LIST *p_ goto cleanup; } + // Notify the authors of the topic / article which is replyed. + snprintf(sql, sizeof(sql), + "SELECT DISTINCT UID FROM bbs WHERE (AID = %d OR AID = %d) " + "AND visible AND reply_note AND UID <> %d", + p_article->tid, p_article->aid, BBS_priv.uid); + + if (mysql_query(db, sql) != 0) + { + log_error("Read reply info error: %s\n", mysql_error(db)); + ret = -1; + goto cleanup; + } + if ((rs = mysql_store_result(db)) == NULL) + { + log_error("Get reply info failed\n"); + ret = -1; + goto cleanup; + } + + while ((row = mysql_fetch_row(rs))) + { + // Send notification message + len_msg = snprintf(msg, BBS_msg_max_len, + "鏈変汉鍥炲浜嗘偍鎵鍙戣〃/鍥炲鐨勬枃绔狅紝蹇潵" + "[article %d]鐪嬬湅[/article]銆%s銆嬪惂锛乗n", + p_article_new->aid, title_f); + + mysql_real_escape_string(db, msg_f, msg, (unsigned long)len_msg); + + snprintf(sql, sizeof(sql), + "INSERT INTO bbs_msg(fromUID, toUID, content, send_dt, send_ip) " + "VALUES(%d, %d, '%s', NOW(), '%s')", + BBS_sys_id, atoi(row[0]), msg_f, hostaddr_client); + + if (mysql_query(db, sql) != 0) + { + log_error("Insert msg error: %s\n", mysql_error(db)); + ret = -1; + goto cleanup; + } + } + mysql_free_result(rs); + rs = NULL; + // Add exp if (checkpriv(&BBS_priv, p_section->sid, S_GETEXP)) // Except in test section { @@ -1127,9 +1311,12 @@ int article_reply(const SECTION_LIST *p_ goto cleanup; } + mysql_close(db); + db = NULL; + clearscr(); moveto(1, 1); - prints("发送完成,新文章通常会在%d秒后可见", BBS_section_list_load_interval); + prints("鍙戦佸畬鎴愶紝鏂版枃绔犻氬父浼氬湪%d绉掑悗鍙", BBS_section_list_load_interval); press_any_key(); ret = 1; // Success