/[LeafOK_CVS]/lbbs/src/article_post.c
ViewVC logotype

Annotation of /lbbs/src/article_post.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.34 - (hide annotations)
Sat Oct 18 13:22:52 2025 UTC (4 months, 4 weeks ago) by sysadm
Branch: MAIN
Changes since 1.33: +1 -1 lines
Content type: text/x-csrc
Reverse meaning of LML quote_mode. It was incorrect before fix.

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

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