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


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

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