본문 바로가기
재밌는 IT 개발/그누보드 테마 제작기(記)

그누보드 테마 제작 22 - 게시글 리스트 페이지 개발

by 행복한 만수킴 2020. 7. 9.

게시판 관련 마지막 작업이네요.
바로 게시글 리스트 작업입니다.

그누의 기본 테마를 살펴보고, 이전에 준비해둔 제작할 테마의 모양을 보겠습니다.

그누보드 기본 테마의 게시판 리스트 모습
하... 심란한 모습이네요 ^^
게시판 리스트에 적용하려는 Metronic Admin UI

 

지금까지 게시글에 적용하는 UI는
사실 Metronic Admin에서 제공하는 Mail UI 였습니다.
여러가지로 비슷해보여서 작업을 하고 있습니다만, 
고쳐야 할 부분이 너무 많아서 시간이 많이 걸리고 있네요.
(사실 그만 할까 라는 생각도 굴뚝같습니다. 그러나 나와의 약속이니 어떻게든 마무리하려 합니다.)

Metronic Admin UI를 개발 중인 테마 화면에 붙여봅니다.

잘해서 이쁜 화면 만들어보리라는 다짐을 해봅니다!

 

오늘은 순서를 정해보고 하겠습니다.
1. 카테고리 영역
2. 게시글 상단 영역
3. 게시글 리스트 영역
4. 페이징 영역
5. 검색 영역
이 정도 업무로 나뉘는것으로 보여집니다.

이제 달려봅니다!!!

 

1. 카테고리 영역

카테고리 영역

카테고리 영역은 준비된 UI에 없네요. 상단 영역을 복사하여 하나 더 만들어야겠습니다.
또, 카테고리영역은 관리자에서 설정을 안하면 안보이는 부분이니까요.
Bootstrap의 버튼 UI를 이용해서 작성을 하겠습니다.
얼마전까지 Bootstrap1이나 2를 썼었습니다.
그때는 많이 어렵다고 느꼈었는데...
지금은 4를 쓰고 있는데 많이 쉬워진 느낌이네요.
(작업하면서 많이 배우는 중입니다.)

Bootstrap의 UI를 이용하여 위와 같이 만들어 보았습니다.

$category_option이라는 변수가 나오는데요.
아마도 /bbs/list.php 에서 미리 만들어서 넘어오는 변수네요.
이런 경우에는 테마에서 다시 만들어야 합니다.
list.php에서도 만들고, list.skin.php에서도 만들면 서버 리소스 측면에서도 안좋긴 하지만
그누보드의 보안 패치, 다른 테마 적용등을 생각하면
테마 폴더 이외에는 건들면 안되겠습니다.

/bbs/list.php에서 만들어져 list.skin.php로 전달되는 변수이다.

/bbs/list.php에서 $category_option을 만드는 부분을
list.skin.php로 옮겨와 수정을 합니다.

Bootstrap 형식으로 완료된 카테고리 영역

// ===== Category HTML 생성 로직 ===================================
<?php
// Category 영역의 Html이 바뀌었기에, 테마에서 새로 만들어준다. by tank. at 200702.
$arr_category_button_html = array();
if ( $is_category ) {
    $category_href = get_pretty_url($bo_table);

    $tmpHtml = '<button type="button" onclick="location.href=\''.$category_href.'\';"';
    if ($sca=='')
        $tmpHtml .= ' id="bo_cate_on" class="btn btn-primary mr-3"';
    else
        $tmpHtml .= ' class="btn btn-outline-secondary mr-3"';
    $tmpHtml .= '>전체</button>';
    array_push($arr_category_button_html, $tmpHtml);

    $categories = explode('|', $board['bo_category_list']); // 구분자가 , 로 되어 있음
    for ($i=0; $i<count($categories); $i++) {
        $category = trim($categories[$i]);
        if ($category=='') continue;
        $tmpHtml = '<button type="button" onclick="location.href=\''.(get_pretty_url($bo_table,'','sca='.urlencode($category))).'\';"';
        $category_msg = '';
        if ($category==$sca) { // 현재 선택된 카테고리라면
            $tmpHtml .= ' id="bo_cate_on" class="btn btn-primary mr-3"';
            $category_msg = '<span class="sound_only">열린 분류 </span>';
        } else {
            $tmpHtml .= ' class="btn btn-outline-secondary mr-3"';
        }
        $tmpHtml .= '>'.$category_msg.$category.'</button>';
        array_push($arr_category_button_html, $tmpHtml);
    }
}                                           //tLog("arr_category_button_html", $arr_category_button_html);
?>

// ===== Category HTML 출력하는 부분 ===================================
<!-- 게시판 카테고리 시작 { -->
<?php if ($is_category) { ?>
<!--begin::Category-->
<div class="col-12 col-sm-12 align-items-start bo_cate">
    <?php
    for ( $i=0; $i<count($arr_category_button_html); $i++ )
        echo $arr_category_button_html[$i];
    ?>
</div>
<!--end::Category-->
<?php } ?>
<!-- } 게시판 카테고리 끝 -->

 

 

2. 게시글 상단 영역

게시글 상단 영역

검색 영역을 제외하고 나머지 부분에 대한 작업을 진행합니다.
이 부분은 Bootstrap의 다양한 Class 옵션을 이해하기 위해 많은 시간이 소요되었네요.

// 뭔놈의 클래스가 이리도 많이 들어있는건지...
<div class="col-12 col-sm-6 col-xxl-4 order-2 order-xxl-3 d-flex align-items-center justify-content-sm-end text-right my-2 btn_bo_user">

게시글 상단 영역이 완료되었습니다.

 

3. 게시글 리스트 영역

게시글 리스트 영역

리스트만 하면 거의 다 끝나가는거겠죠?
얼른 하고 놀러 가고 싶네요.

제목줄은 새로 만들어야 하네요.
메일의 UI를 가져오니 제목이 없네요. 흠냐...

역시 화면 그리는게 제일 어렵네요.

이제 그누보드의 코딩을 옮겨옵니다.
먼저 제목부터!!
조회, 추천, 비추천, 날짜에는 정렬을 위한 <a>Tag가 존재하네요.
이를 위해 common.lib.php에 subject_sort_link() 함수가 존재합니다.
이 역시 HTML 문자열을 돌려주는 함수기에 수정이 필요하네요.

항상 이런 경우에 제가 잘하는 건지 의문이 남아서 
SIR.KR의 Q&A에 질문을 남겨보았습니다.
이런 경우 경험을 알려주세요~ 라구요~
sir.kr/qa/366925

 

테마 제작시 함수 재정의 또는 테마 이외의 파일을 수정해야 하는 경우에는 어떻게 하시나요? > S

테마를 제작하면서 느낀 점 중에 하나가

common.lib.php에 있는 함수들을 그대로 쓸 수 없는 경우가 발생할때입니다.

함수내에서 HTML 코드를 만들어 리턴하는것들은 거의 그럴 거라�

sir.kr

일반적으로 저와 비슷한 방식으로 작업을 한다고 하네요.
더욱 자신감을 가지고 진행해볼게요~

지난 번처럼(여지껏 해오던데로) 테마내에 미리 만들어둔 mt703.lib.php 파일에
subject_sort_link_mt703() 함수를 다시 정의합니다.

// 날짜, 조회수의 경우 높은 순서대로 보여져야 하므로 $flag 를 추가
// $flag : asc 낮은 순서 , desc 높은 순서
// 제목별로 컬럼 정렬하는 QUERY STRING
function subject_sort_link_mt703($col, $query_string='', $flag='asc')
{
    global $sst, $sod, $sfl, $stx, $page, $sca;

    $q1 = "sst=$col";
    if ($flag == 'asc')
    {
        $q2 = 'sod=asc';
        if ($sst == $col)
        {
            if ($sod == 'asc')
            {
                $q2 = 'sod=desc';
            }
        }
    }
    else
    {
        $q2 = 'sod=desc';
        if ($sst == $col)
        {
            if ($sod == 'desc')
            {
                $q2 = 'sod=asc';
            }
        }
    }

    $arr_query = array();
    $arr_query[] = $query_string;
    $arr_query[] = $q1;
    $arr_query[] = $q2;
    $arr_query[] = 'sfl='.$sfl;
    $arr_query[] = 'stx='.$stx;
    $arr_query[] = 'sca='.$sca;
    $arr_query[] = 'page='.$page;
    $qstr = implode("&amp;", $arr_query);

    $sort = "fa-sort";
    if ( $sst == $col ) {
        if ( $sod=="asc" )      $sort = "fa-sort-down";
        else if ( $sod=="desc" ) $sort = "fa-sort-up";
    }

    return "<a href=\"{$_SERVER['SCRIPT_NAME']}?{$qstr}\" class=\"text-dark text-hover-primary\"><i class=\"fa ". $sort ."\" style=\"color:red;\"></i>";
}

눈치채셨나요?
넵 기존과는 조금 다르게 했어요.
제목줄의 조회, 추천 등을 눌렀을때
현재 정렬상태가 무엇인지 알 수 있도록 아이콘을 추가하였습니다.

클릭할 수 있는 버튼이라는 느낌도 주고, 정렬 상태를 표시

 

이제 리스트 코딩 작업 들어갑니다.
공지일때의 BG Color와 짝수, 홀수일때 BG Color...
첫 줄부터 혼란스럽게 하네요. ㅎㅎ

또 위와 같은 문제가 발생했네요.
바로 get_list() 함수입니다.
/bbs/list.php에서 호출하는 함수로 게시글 리스트를 불러오는 함수죠.
역시 HTML을 포함해서 가져오네요.
또 이 놈은 /bbs/list.php에서 호출되는 함수이기에
어쩔 수 없이 테마 이외의 파일을 건드려야 하는 상황이 되었습니다.
다른 테마 제작자분들은 어떻게 처리하시는지 다시 또 궁금해집니다.

어... 함수의 소스를 보니 어쩔 수 없는건 아니었나봅니다.
일단 로그부터 찍어봅니다.

<pre>=[ 2020-07-03 16:08:50.474148 ]=====================

게시글 리스트 => Array
(
    [0] => Array
        (
            [wr_id] => 34
            [wr_num] => -10
            [wr_reply] => 
            [wr_parent] => 34
            [wr_is_comment] => 0
            [wr_comment] => 0
            [wr_comment_reply] => 
            [ca_name] => 카테고리-2
            [wr_option] => html1,mail
            [wr_subject] => 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지
            [wr_content] => 공지 공지 공지 공지 공지 공지 [[[중간생략]]]</p>
            [wr_seo_title] => 공지-공지-공지-공지-공지-공지-공지-공지
            [wr_link1] => sir.kr
            [wr_link2] => mansu.kim
            [wr_link1_hit] => 0
            [wr_link2_hit] => 0
            [wr_hit] => 2
            [wr_good] => 0
            [wr_nogood] => 0
            [mb_id] => tankla
            [wr_password] => 
            [wr_name] => 최고관리자
            [wr_email] => me@mansu.kim
            [wr_homepage] => http://mansu.kim
            [wr_datetime] => 2020-07-02 22:21:02
            [wr_file] => 2
            [wr_last] => 2020-07-02 22:21:02
            [wr_ip] => 127.0.0.1
            [wr_facebook_user] => 
            [wr_twitter_user] => 
            [wr_1] => 
            [wr_2] => 
            [wr_3] => 
            [wr_4] => 
            [wr_5] => 
            [wr_6] => 
            [wr_7] => 
            [wr_8] => 
            [wr_9] => 
            [wr_10] => 
            [is_notice] => 1
            [subject] => 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지
            [content] => 공지 공지 공지 공지 공지 공지 [[[중간생략]]]</p>
            [comment_cnt] => 
            [datetime] => 2020-07-02
            [datetime2] => 07-02
            [last] => 2020-07-02
            [last2] => 07-02
            [name] => <span class="sv_wrap">ttp://hunn[[[중간생략]]]p;stx=hunnovsi" target="_blank">포인트내역</a>pan>
            [reply] => 0
            [icon_reply] => 
            [icon_link] => <i class="fa fa-link" aria-hidden="true"></i> 
            [ca_name_href] => http://hunk.com/bbs/board.php?bo_table=notice&amp;sca=%EC%B9%B4%ED%85%8C%EA%B3%A0%EB%A6%AC-2
            [href] => http://hm/bbs/board.php?bo_table=notice&amp;wr_id=34&amp;sst=wr_good&amp;sod=desc&amp;sop=and
            [comment_href] => http://hunom/bbs/board.php?bo_table=notice&amp;wr_id=34&amp;sst=wr_good&amp;sod=desc&amp;sop=and
            [icon_new] => <img src="http://huom/theme/mt703/skin/board/basic/img/icon_new.gif" class="title_icon" alt="새글"> 
            [icon_hot] => <i class="fa fa-heart" aria-hidden="true"></i> 
            [icon_secret] => 
            [link] => Array
                (
                    [1] => http://sir.kr
                    [2] => http://mansu.kim
                )

            [link_href] => Array
                (
                    [1] => http://hunnom/bbs/link.php?bo_table=notice&amp;wr_id=34&amp;no=1&amp;sst=wr_good&amp;sod=desc&amp;sop=and
                    [2] => http://hunom/bbs/link.php?bo_table=notice&amp;wr_id=34&amp;no=2&amp;sst=wr_good&amp;sod=desc&amp;sop=and
                )

            [link_hit] => Array
                (
                    [1] => 0
                    [2] => 0
                )

            [file] => Array
                (
                    [count] => 2
                    [0] => Array
                        (
                            [href] => http://hom/bbs/download.php?bo_table=notice&amp;wr_id=34&amp;no=0&amp;sst=wr_good&amp;sod=desc&amp;sop=and
                            [download] => 0
                            [path] => http://hunm/data/file/notice
                            [size] => 294byte
                            [datetime] => 2020-07-02 22:21:02
                            [source] => attach.txt
                            [bf_content] => 첨부 파일 테스트
                            [content] => 첨부 파일 테스트
                            [view] => 
                            [file] => 2130706433_RudQAr86_0fa47b4cacca5173fd797494179cbdf6a34157f8.txt
                            [image_width] => 640
                            [image_height] => 480
                            [image_type] => 0
                            [bf_fileurl] => 
                            [bf_thumburl] => 
                            [bf_storage] => 
                        )

                    [1] => Array
                        (
                            [href] => http://hunom/bbs/download.php?bo_table=notice&amp;wr_id=34&amp;no=1&amp;sst=wr_good&amp;sod=desc&amp;sop=and
                            [download] => 0
                            [path] => http://huom/data/file/notice
                            [size] => 609byte
                            [datetime] => 2020-07-02 22:21:02
                            [source] => 첨부.txt
                            [bf_content] => 첨부 파일 테스트 2
                            [content] => 첨부 파일 테스트 2
                            [view] => 
                            [file] => 2130706433_bY8GHygx_4ecf1e1e0e34f1e87ad0e99125234f790d5f7067.txt
                            [image_width] => 640
                            [image_height] => 480
                            [image_type] => 0
                            [bf_fileurl] => 
                            [bf_thumburl] => 
                            [bf_storage] => 
                        )

                )

            [icon_file] => <i class="fa fa-download" aria-hidden="true"></i> 
        )

    [1] => Array
        (
            [wr_id] => 29
            [wr_num] => -5

DB 조회 결과를 그대로 포함하고 있고, 추가로 필요한 문자열들만 배열에 추가시킨거네요.
DB에서 조회한 bo_hot 필드는 그대로 있고,
icon_hot 필드라는 HTML을 포함한 문자열 필드를 추가하는 방식으로요.
함수 재정의 필요 없이, 바로 사용해도 되겠네요. ㅎㅎ

바로 NEW 아이콘 적용을 위해서 icon 파일을 찾고 있는데
fontawesome, flaticon, lineawesome, SVG icon ....
다 찾아봐도 "NEW"를 대체할 아이콘이 없네요.
해외 게시판은 NEW를 표시하지 않는건가?
(그래서 그누보드도 New 아이콘은 fontawesome이 아닌 만들어서 쓰는가봅니다. ㅠㅠ)
걍 아무거나 써야겠어요 ㅠㅠ (결국 그냥 Pencil을 사용했네요)

아... 망했어요 ㅠㅠ
그냥 Table로 했어야 했어요...

bootstrap의 d-flex와 flex-grow-1을 사용한 UI... 삐뚤 빼뚤...

테이블로의 변경을 고민하다가...
글쓴이부터 날짜까지를 DIV로 묶어버린다면 나쁘지 않을 것 같다는 생각이 불현듯...
바로 시도합니다. (잘 되었으면... 쪼금은 삐뚤어져도 되니까...)
오... 딱 맞는것 같네요. 다행입니다.

생각대로 잘 맞춰진 UI. 

앞에 번호부분도 틀어지네요...
이 부분도 DIV로 묶어서 고정시킵니다.

앞으로 비스한 상황이 발생하면,
bootstrap의 d-flex와 flow-grow-1 클래스는 영역을 3~4개 정도로 나누고
flow-grow-1을 제외한 영역은 사이즈를 정확히 주면 큰 문제 없겠네요.

 

 

4. 페이징 영역

페이징 영역

페이징 영역은 지난 스크랩에서 했었으니 무난히 진행되리라 봅니다.
상단영역의 HTML 구조를 그대로 가져와 작업합니다.

이젠 넘나 쉬운 페이징!~

 

5. 검색 영역

검색 영역

고민이네요. 뭘 어떻게 이용을 할지요...
Metronic Admin Template에 있는 상단 검색창을 이용을 할지...
Modal 창을 이용해서 해야 할지...
그누보드 방식으로 걍 빠르게 처리해버릴지...
음.. 인생은 결정의 연속이라더니...

모달창을 이용하는게 제일 빠르고 낫겠다고 판단했어요.
역시 한 번 해보았다고 금방 끝났어요 ^^

완료된 검색 모달 팝업창의 모습

 

그누 기본은 검색된 단어에 표시를 해주는데, 그게 빠져있습니다.

 

뭘까요... 다시 찾아봐야죠 뭐...

그누의 기본테마는 검색된 단어에 빨간색으로 Background를 주어 표시한다.
sch_word라는 클래스를 주어 표시되게 하고 있다.

음... 이런 원리로군요...
제목 부분에 있는 검색어를 치환하는 방식.
저도 두가지 방법을 선택할 수 있겠네요.
1. sch_word를 gnuboard.css에 넣어서 간단히 처리
2. Bootstrap의 BG-RED 클래스를 이용하여 직접 치환.

그누보드 기본 테마에서 처리하는 방식을 확인해보겠습니다.
/bbs/list.php에서 검색어가 있을 경우 search_font() 함수를 호출하여 처리하고 있습니다.
그누보드 개발자의 고뇌가 느껴지는 주석들이 존재하네요 ^^
"태그는 포함하지 않아야 하는데 잘 안되는군. ㅡㅡa" 라구요~ ㅋㅋ

/* ******************************************************************/
/* =========== /bbs/list.php에서 검색어를 표시하는 부분 =========== */
/* ******************************************************************/
// 페이지의 공지개수가 목록수 보다 작을 때만 실행
if($page_rows > 0) {
    $result = sql_query($sql);

    $k = 0;

    while ($row = sql_fetch_array($result))
    {
        // 검색일 경우 wr_id만 얻었으므로 다시 한행을 얻는다
        if ($is_search_bbs)
            $row = sql_fetch(" select * from {$write_table} where wr_id = '{$row['wr_parent']}' ");

        $list[$i] = get_list($row, $board, $board_skin_url, G5_IS_MOBILE ? $board['bo_mobile_subject_len'] : $board['bo_subject_len']);
        if (strstr($sfl, 'subject')) {
            $list[$i]['subject'] = search_font($stx, $list[$i]['subject']);
        }
        $list[$i]['is_notice'] = false;
        $list_num = $total_count - ($page - 1) * $list_page_rows - $notice_count;
        $list[$i]['num'] = $list_num - $k;

        $i++;
        $k++;
    }
}

/* ***********************************************************************/
/* =========== /lib/common.lib.php에 있는 search_font() 함수 =========== */
/* ***********************************************************************/
// set_search_font(), get_search_font() 함수를 search_font() 함수로 대체
function search_font($stx, $str)
{
    global $config;

    // 문자앞에 \ 를 붙입니다.
    $src = array('/', '|');
    $dst = array('\/', '\|');

    if (!trim($stx) && $stx !== '0') return $str;

    // 검색어 전체를 공란으로 나눈다
    $s = explode(' ', $stx);

    // "/(검색1|검색2)/i" 와 같은 패턴을 만듬
    $pattern = '';
    $bar = '';
    for ($m=0; $m<count($s); $m++) {
        if (trim($s[$m]) == '') continue;
        // 태그는 포함하지 않아야 하는데 잘 안되는군. ㅡㅡa
        //$pattern .= $bar . '([^<])(' . quotemeta($s[$m]) . ')';
        //$pattern .= $bar . quotemeta($s[$m]);
        //$pattern .= $bar . str_replace("/", "\/", quotemeta($s[$m]));
        $tmp_str = quotemeta($s[$m]);
        $tmp_str = str_replace($src, $dst, $tmp_str);
        $pattern .= $bar . $tmp_str . "(?![^<]*>)";
        $bar = "|";
    }

    // 지정된 검색 폰트의 색상, 배경색상으로 대체
    $replace = "<b class=\"sch_word\">\\1</b>";

    return preg_replace("/($pattern)/i", $replace, $str);
}


저는 bootstrap의 bg-danger 클래스를 이용하기로 합니다.
<b class="bg-danger">검색어</b>

브라우저에서 bg-danger 클래스를 적용하여 테스트한 모습

search_font() 함수는 그누 기본 함수이니 
전 search_font_mt703() 함수를 그대로 복사하여 mt703.lib.php에 옮깁니다.
그리고 sch_world 문자열을 bg-danger 로 바꾸어 준비해둡니다.

위에서 보여준 $list 변수의 내용은 검색을 하지 않은 결과였습니다.
검색을 한 $list 변수를 살펴보고,
sch_word가 포함된 제목과 원문 그대로 담긴 제목 배열을 찾아봅니다.

// $list 변수에 담긴 제목 배열을 확인 해보면 원문이 담긴 배열을 찾을 수 있다.
<pre>
=[ 2020-07-04 12:16:02.869199 ]=====================

list => Array
(
    [0] => Array
        (
            [wr_id] => 34
			===== 중간생략 ===
			[wr_subject] => 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지 공지
			[subject] => <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b> <b class="sch_word">공지</b>
			===== 중간생략 ===
            [icon_file] => <i class="fa fa-download" aria-hidden="true"></i> 
            [num] => 1
        )
)
===============================
</pre>

// 참고를 위해 /bbs/list.php의 관련 코드 확인 (196라인)
		if (strstr($sfl, 'subject')) {
            $list[$i]['subject'] = search_font($stx, $list[$i]['subject']);
        }
        
// list.skin.php에서 제목을 출력하는 코드 확인
        <?php echo $list[$i]['subject'] ?>

// 위 라인을 search_font_mt703() 함수에 연결
		<?php echo search_font_mt703($stx, $list[$i]['subject']); ?>

잘 처리되었네요 ^^

bootstrap의 bg-ground 클래스를 이용하여 검색어가 표시된 화면

 

이제 거의 끝났습니다.
마지막으로 전체적으로 기능 테스트를 진행해보았는데,
큰 문제가 발견되지 않네요.

 

이렇게 완료되었네요.
예상대로 게시판이 가장 오래 걸리는 작업이었네요.

다음은, 우측 영역에 뭘 넣어야 좋을지 고민해보고 작업 진행하겠습니다.

 

 

댓글4