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

Diff of /lbbs/src/article_post.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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


Legend:
Removed lines/characters  
Changed lines/characters
  Added lines/characters

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