상세 컨텐츠

본문 제목

네이버XE(제로보드xe) XE템플릿 코드로 게시판 수정

PHP(Class)

by 김일국 2017. 5. 23. 14:49

본문

이번에는 xe템플릿 코드로 게시판 스킨을 수정하는 방법을 공부 합니다.

수정할 페이지가 꽤 됩니다.(목록, 읽기, 쓰기, 댓글쓰기, 댓글삭제, 엮인글삭제, 권한안내페이지 등)

참고) XE는 게시판 모듈이 기본으로 설치 되어 있지 않기 때문에, 별도로 설치 해야 합니다.

저는 쉬원설치(설정한 웹FTP로 설치)를 사용하지 않고, 다운로드 사이트로 이동해서 게시판 모듈을 다운로드 후 호스팅서버에 개별 업로드 방식으로 설치 했습니다.(아래 참조)

보드모듈을 업로드 한 후 호스팅 폴더의 모습은 아래와 같습니다.

위 게시판 스킨폴더 내에서 새로 작성할 스킨폴더를 지정하거나, 업로드 한 후 파일을 살펴보면 아래와 같이 정리됩니다.

- skin.xml (게시판 스킨정보-스킨명,제작자,버전정보 등)

*참고) 여기서 잠깐 xml파일에서 변수를 지정하고, 아래 html파일에서 사용가능한 XE템플릿 형식을 알아보고 넘어 갑니다.

 * xml파일에서 변수지정 : <var name="board_image" type="image"><title>내용</title></var>

 * html에서 변수 사용 : {$module_info->board_image("title")} 변수->변수("title") 결과 적으론 내용 화면에 출력 됩니다.

- list.html (게시물 페이지 인클루드내용 - 아래참조

              : 지난 레이아웃에서는 <load... /> 라는 태그를 사용했는데, 여긴 다릅니다. 조건문도<p cond="..></p> 를 사용하지 않고, 다른 방식입니다. )

<include target="_header.html" />
<!--@if($oDocument->isExists())-->
 <include target="_read.html" />
<!--@else-->
 <include target="_list.html" />
<!--@end-->
<include target="_footer.html" />

- _header.html (게시판 헤더 레이아웃 -  게시판 공통사용)

*참고) 게시판 헤더에 <div class="user_board"> 로 시작하고, 닫는 태그는 아래 푸터 파일에 존재 합니다.

- _footer.html (게시판 푸터 레이아웃 - 게시판 공통사용)

*참고) 게시판 푸터에 </div> 로 게시판 내용 끝을 지정 합니다.

- _list.html (게시물 목록 페이지 레이아웃)

- _read.html (게시물 읽기 레이아웃)

- _comment.html (댓글 쓰기 레이아웃)

- _trackback.html (트랙백 쓰기 레이아웃)

- wtire_form.html (게시물 쓰기폼 레이아웃)

- comment_form.html (댓글 쓰기폼 레이아웃)

- delete_form.html (게시물 삭제폼 레이아웃)

- delete_commnet_form.html ( 댓글 삭제폼 레이아웃)

- delete_trackback_form.html (엮인글 삭제폼 레이아웃)

- message.html (알림 메세지 레이아웃)

- 사용자지정파일명.css (게시판 스타일시트 파일)

만약 반응형으로 작업하게 되면 작업량이 상당 하게 됩니다.


*리스트 페이지 예) XE템플릿으로 만들기 전 퍼블리싱된 게시판 html은 아래와 같은 기본 골격을 같습니다.(너무 길어질것 같아서 헤더,푸터는 생략했습니다. 실제 퍼블리싱에서는 헤더,푸터를  포함한 풀코딩으로 만든 후 분해하는 절차를 거치게 됩니다. )

<include target="_header.html" />
<div class="list">

<form action="./" method="get" class="list-body">

<div class="list-container">

<ul><li>반복내용</li></ul>

</div>

</form>

<div class="fbox list-foot">

<form action="./" method="get" class="pagination">

<ul><li>페이징번호</li></ul>

</form>

</div>

<div class="list_footer">

<div class="btnArea"><a class="btn">버튼</a></div>

</div>

</div>
<include target="_footer.html" />

간단해 보이죠!!!, 하지만 실제로는  <div class="list"></div> 사이에 엄청나게 XE템플릿 코드가 많습니다. 퍼블리싱한 결과를 아래 형식에 맞게 분해 해야 합니다. 쉬운 작업은 아닙니다.(아래)

{@
 $mi = $module_info;
 if (!$mi->thumbnail_width)  $mi->thumbnail_width  = 130;
 if (!$mi->thumbnail_height) $mi->thumbnail_height = 100;
 if (!$mi->content_cut_size) $mi->content_cut_size = 200;

 $list_idx = 1;
}
<load target="js/jquery.easing.1.3.js" />
<load target="js/list.xe.js" />

<div class="list">

 <form action="./" method="get" class="list-body">
  <fieldset>
   <legend>List of Articles</legend>
 
   <div class="list-container hide">
   <ul>
    <li class="fbox article">
     <a href="#" class="title"><span class="iefix"></span></a>
     <span class="thumb">
      <img src="./img/noimage.gif" width="{$mi->thumbnail_width}" height="{$mi->thumbnail_height}" alt="" /></a>
     </span>
 
     <ul class="flat meta">
      <li class="reply"></li>
      <li class="author"><a href="#popup_menu_area" class="member_0" onclick="return false"></a></li>
      <li class="date"></li>
      <li class="summary"></li>
     </ul>
    </li>
   </ul>
   </div>
 
   <div class="list-container">
   <ul>
    <li class="fbox article {($list_idx++%2)?'odd':'even'}" loop="$document_list=>$doc">
     {@
      $post_link     = getUrl('document_srl',$doc->document_srl);
      $perm_link     = $doc->getPermanentUrl();
      $comment_count = $doc->getCommentCount();
      $has_thumbnail = $doc->thumbnailExists($mi->thumbnail_width, $mi->thumbnail_height, $mi->thumbnail_type);
     }
     <a href="{$post_link}" class="title">{$doc->getTitle($mi->subject_cut_size)} {$doc->printExtraImages(60*60*$module_info->duration_new)}<span class="iefix"></span></a>
     <span class="thumb">
     <!--@if($has_thumbnail)-->
      <img src="{$doc->getThumbnail($mi->thumbnail_width, $mi->thumbnail_height, $mi->thumbnail_type)}" width="{$mi->thumbnail_width}" height="{$mi->thumbnail_height}" alt="" />
     <!--@else-->
      <img src="./img/noimage.gif" width="{$mi->thumbnail_width}" height="{$mi->thumbnail_height}" alt="" />
     <!--@end-->
     </span>
     <ul class="flat meta">
      <li class="reply">{$comment_count}</li>
      <li class="author"><a href="#popup_menu_area" class="member_{$doc->get('member_srl')}" onclick="return false">{$doc->getNickName()}</a></li>
      <li class="date">{$doc->getRegdate('Y.m.d')}</li>
      <li class="summary">{$doc->getSummary($mi->content_cut_size)}</li>
     </ul>
    </li>
   </ul>
   </div>
  </fieldset>
 </form>

 <div class="fbox list-foot">
  <form action="./" method="get" class="pagination">
  <fieldset>
   <legend>Board Pagination</legend>
   <input type="hidden" name="vid" value="{$vid}" />
   <input type="hidden" name="mid" value="{$mid}" />
   <input type="hidden" name="category" value="{$category}" />
   <input type="hidden" name="search_keyword" value="{htmlspecialchars($search_keyword)}" />
   <input type="hidden" name="search_target" value="{$search_target}" />
   {@ $prev_page = max($page-1, 1)}
   {@ $next_page = min($page+1, $page_navigation->last_page)}
   <ul>
    <li class="first"><a href="{getUrl('page','','document_srl','','division',$division,'last_division',$last_division)}" class="active"|cond="$page!=1"><span>{$lang->first_page}</span></a></li>
    <li class="prev"><a href="{getUrl('page',$prev_page,'document_srl','','division',$division,'last_division',$last_division)}" class="active"|cond="$prev_page<$page"><span>PREV {$mi->list_count}</span></a></li>
    <li class="pages"><span><input type="text" name="page" value="{$page}" /> of <em>{$total_page}</em></span></li>
    <li class="next"><a href="{getUrl('page',$next_page,'document_srl','','division',$division,'last_division',$last_division)}" class="active"|cond="$next_page>$page"><span>NEXT {$mi->list_count}</span></a></li>
    <li class="last"><a href="{getUrl('page',$total_page,'document_srl','','division',$division,'last_division',$last_division)}" class="active"|cond="$total_page>$page"><span>{$lang->last_page}</span></a></li>
   </ul>
  </fieldset>
  </form>
 </div>
 <div class="list_footer">
  <div class="btnArea">
   <a class="btn" href="{getUrl('act','dispBoardWrite','document_srl','')}">{$lang->cmd_write}</a>
  </div>
  <button type="button" class="bsToggle" title="{$lang->cmd_search}">{$lang->cmd_search}</button>
  <form cond="$grant->view" action="{getUrl()}" method="get" onsubmit="return procFilter(this, search)" id="board_search" class="board_search" no-error-return-url="true">
   <input type="hidden" name="vid" value="{$vid}" />
   <input type="hidden" name="mid" value="{$mid}" />
   <input type="hidden" name="category" value="{$category}" />
   <input type="text" name="search_keyword" value="{htmlspecialchars($search_keyword)}" title="{$lang->cmd_search}" class="iText" />
   <select name="search_target">
    <option loop="$search_option=>$key,$val" value="{$key}" selected="selected"|cond="$search_target==$key">{$val}</option>
   </select>
   <button class="btn" type="submit">{$lang->cmd_search}</button>
   <a cond="$last_division" class="btn" href="{getUrl('page',1,'document_srl','','division',$last_division,'last_division','')}">{$lang->cmd_search_next}</a>
  </form>
  <a href="{getUrl('act','dispBoardTagList')}" class="tagSearch" title="{$lang->tag}">{$lang->tag}</a>
 </div>
</div>

<script>
if (typeof window.xe_v3 == 'undefined') window.xe_v3 = {};
jQuery.extend(xe_v3, {
 page : '{$page}',
 list_count : '{$mi->list_count}',
 last_page  : '{$total_page}',
 content_cut_size : '{$mi->content_cut_size}',
 thumbnail_width  : '{$mi->thumbnail_width}',
 thumbnail_height : '{$mi->thumbnail_height}',
 thumbnail_type   : '{$mi->thumbnail_type}',
 search_keyword   : '{addslashes($search_keyword)}',
 search_target    : '{$search_target}'
});
</script>


*쓰기 페이지 예) XE템플릿으로 만들기 전 퍼블리싱된 게시판 html은 아래와 같은 기본 골격을 같습니다.(너무 길어질것 같아서 헤더,푸터는 생략했습니다. 실제 퍼블리싱에서는 헤더,푸터를  포함한 풀코딩으로 만든 후 분해하는 절차를 거치게 됩니다. )

<include target="_header.html" />

<form action="./" method="post" class="board_write">

<div class="write_header">옵션항목</div>

<div class="exForm">사용자 추가항목</div>

<div class="write_editor">입력에디터폼</div>

<div class="write_footer">글쓴이정보입력창+쓰기버튼</div>

</form>

<include target="_footer.html" />

간단해 보이죠!!! 하지만, 실제로는 <form></form> 사이에 엄청나게 XE템플릿 코드가 많습니다. 퍼블리싱한 결과를 아래 형식에 맞게 분해 해야 합니다.(아래)

<include target="_header.html" />
<form action="./" method="post" onsubmit="return procFilter(this, window.insert)" class="board_write">
 <input type="hidden" name="mid" value="{$mid}" />
 <input type="hidden" name="content" value="{$oDocument->getContentText()}" />
 <input type="hidden" name="document_srl" value="{$document_srl}" />
 <div class="write_header">
  <select name="category_srl" cond="$module_info->use_category=='Y'">
   <option value="">{$lang->category}</option>
   <option loop="$category_list => $val" disabled="disabled"|cond="!$val->grant" value="{$val->category_srl}" selected="selected"|cond="$val->grant&&$val->selected||$val->category_srl==$oDocument->get('category_srl')">
    {str_repeat("  ",$val->depth)} {$val->title} ({$val->document_count})
   </option>
  </select>
  <input cond="$oDocument->getTitleText()" type="text" name="title" class="iText" title="{$lang->title}" value="{htmlspecialchars($oDocument->getTitleText())}" />
  <input cond="!$oDocument->getTitleText()" type="text" name="title" class="iText" title="{$lang->title}" />
  <input cond="$grant->manager" type="checkbox" name="is_notice" value="Y" class="iCheck" checked="checked"|cond="$oDocument->isNotice()" id="is_notice" />
  <label cond="$grant->manager" for="is_notice">{$lang->notice}</label>
 </div>
    <div class="exForm" cond="count($extra_keys)">
  <table cond="count($extra_keys)" border="1" cellspacing="0" summary="Extra Form">
   <caption><em>*</em> : {$lang->is_required}</caption>
   <tr loop="$extra_keys=>$key,$val">
    <th scope="row"><em cond="$val->is_required=='Y'">*</em> {$val->name}</th>
    <td>{$val->getFormHTML()}</td>
   </tr>
  </table>
 </div>
    <div class="write_editor">
  {$oDocument->getEditor()}
 </div>
 <div class="write_footer">
  <div class="write_option">
   <block cond="$grant->manager">
    <input type="checkbox" name="title_bold" id="title_bold" class="iCheck" value="Y" checked="checked"|cond="$oDocument->get('title_bold')=='Y'" />
    <label for="title_bold">{$lang->title_bold}</label>
   </block>
   <input cond="$module_info->secret=='Y'" type="checkbox" name="is_secret" class="iCheck" value="Y" checked="checked"|cond="$oDocument->isSecret()" id="is_secret" />
   <label cond="$module_info->secret=='Y'" for="is_secret">{$lang->secret}</label>
            <input type="checkbox" name="comment_status" class="iCheck" value="ALLOW" checked="checked"|cond="$oDocument->allowComment()" id="comment_status" />
            <label for="comment_status">{$lang->allow_comment}</label>
            <input type="checkbox" name="allow_trackback" class="iCheck" value="Y" checked="checked"|cond="$oDocument->allowTrackback()" id="allow_trackback" />
            <label for="allow_trackback">{$lang->allow_trackback}</label>
   <block cond="$is_logged">
    <input type="checkbox" name="notify_message" class="iCheck" value="Y" checked="checked"|cond="$oDocument->useNotify()" id="notify_message" />
    <label for="notify_message">{$lang->notify}</label>
   </block>
  <!--@if(is_array($status_list))-->
   <!--@foreach($status_list AS $key=>$value)-->
   <input type="radio" name="status" value="{$key}" id="{$key}" <!--@if($oDocument->get('status') == $key || ($key == 'PUBLIC' && !$document_srl))-->checked="checked"<!--@end--> />
   <label for="{$key}">{$value}</label>
   <!--@end-->
  <!--@end-->
  </div>
  <div class="write_author">
   <span class="item" cond="!$is_logged">
    <label for="userName" class="iLabel">{$lang->writer}</label>
    <input type="text" name="nick_name" id="userName" class="iText userName" style="width:80px" value="{$oDocument->getNickName()}" />
   </span>
   <span class="item" cond="!$is_logged">
    <label for="userPw" class="iLabel">{$lang->password}</label>
    <input type="password" name="password" id="userPw" class="iText userPw" style="width:80px" />
   </span>
   <span class="item" cond="!$is_logged">
    <label for="homePage" class="iLabel">{$lang->homepage}</label>
    <input type="text" name="homepage" id="homePage" class="iText homePage"  style="width:140px"value="{htmlspecialchars($oDocument->get('homepage'))}" />
   </span>
   <span class="item">
    <label for="tags" class="iLabel">{$lang->tag} : {$lang->about_tag}</label>
    <input type="text" name="tags" id="tags" value="{htmlspecialchars($oDocument->get('tags'))}" class="iText" style="width:260px" title="Tag" />
   </span>  
  </div>
  <div class="btnArea">
   <input class="btn" type="submit" value="{$lang->cmd_registration}" />
   <!--@if(!$oDocument->isExists() || $oDocument->get('status') == 'TEMP')-->
   <button cond="$is_logged" class="btn" type="button" onclick="doDocumentSave(this);">{$lang->cmd_temp_save}</button>
   <button cond="$is_logged" class="btn" type="button" onclick="doDocumentLoad(this);">{$lang->cmd_load}</button>
   <!--@end-->
  </div>
 </div>
</form>
<include target="_footer.html" />


*읽기 페이지 예) XE템플릿으로 만들기 전 퍼블리싱된 게시판 html은 아래와 같은 기본 골격을 같습니다.(너무 길어질것 같아서 트랙백,댓글은 생략했습니다. 실제 퍼블리싱에서는 헤더,푸터를  포함한 풀코딩으로 만든 후 분해하는 절차를 거치게 됩니다. )

<div class="board_read">

<div class="read_header">제목,글쓴이,조회 수,추천 수,날짜</div>

<div class="exOut">사용자 추가항목</div>

<div class="read_body">게시물 본문</div>

<div class="read_footer">첨부파일,버튼</div>

</div>

<include target="_trackback.html" />

<include target="_comment.html" />

간단해 보이죠!!! 하지만, 실제로는 <div class="board_read"></div> 사이에 엄청나게 XE템플릿 코드가 많습니다. 퍼블리싱한 결과를 아래 형식에 맞게 분해 해야 합니다.(아래)

<div class="board_read">
 <!-- READ HEADER -->
 <div class="read_header">
  <h1>
   <a href="{getUrl('category',$oDocument->get('category_srl'), 'document_srl', '')}" class="category" cond="$module_info->use_category=='Y'">{$category_list[$oDocument->get('category_srl')]->title}</a>
   <a href="{$oDocument->getPermanentUrl()}">{$oDocument->getTitle()}</a>
  </h1>
  <p class="time">
   {$oDocument->getRegdate('Y.m.d H:i')}
  </p>
  <p class="meta">
   <a cond="$module_info->display_author!='N' && !$oDocument->getMemberSrl() && $oDocument->isExistsHomepage()" href="{$oDocument->getHomepageUrl()}" onclick="window.open(this.href);return false;" class="author">{$oDocument->getNickName()}</a>
   <block cond="$module_info->display_author!='N' && !$oDocument->getMemberSrl() && !$oDocument->isExistsHomepage()">{$oDocument->getNickName()}</block>
   <a cond="$module_info->display_author!='N' && $oDocument->getMemberSrl()" href="#popup_menu_area" class="member_{$oDocument->get('member_srl')} author" onclick="return false">{$oDocument->getNickName()}</a>
   <span class="sum">
    <span class="read">{$lang->readed_count}:{$oDocument->get('readed_count')}</span>
    <span class="vote" cond="$oDocument->get('voted_count')!=0">{$lang->cmd_vote}:{$oDocument->get('voted_count')}</span>
   </span>
  </p>
 </div>
 <!-- /READ HEADER -->
 <!-- Extra Output -->
 <div class="exOut" cond="$oDocument->isExtraVarsExists() && (!$oDocument->isSecret() || $oDocument->isGranted())">
  <table border="1" cellspacing="0" summary="Extra Form Output">
   <tr loop="$oDocument->getExtraVars() => $key,$val">
    <th scope="row">{$val->name}</th>
    <td>{$val->getValueHTML()} </td>
   </tr>
  </table>
 </div>
 <!-- /Extra Output -->
 <!-- READ BODY -->
 <div class="read_body">
  <!--@if($oDocument->isSecret() && !$oDocument->isGranted())-->
  <form action="./" method="get" onsubmit="return procFilter(this, input_password)">
   <input type="hidden" name="mid" value="{$mid}" />
   <input type="hidden" name="page" value="{$page}" />
   <input type="hidden" name="document_srl" value="{$oDocument->document_srl}" />
   <p><label for="cpw">{$lang->msg_is_secret} {$lang->msg_input_password}</label></p>
   <p><input type="password" name="password" id="cpw" class="iText" /><input class="btn" type="submit" value="{$lang->cmd_input}" /></p>
  </form>
  <!--@else-->
  {$oDocument->getContent(false)}
  <!--@end-->
 </div>
 <!-- /READ BODY -->
 <!-- READ FOOTER -->
 <div class="read_footer">
  <div cond="$oDocument->hasUploadedFiles()" class="fileList">
   <button type="button" class="toggleFile" onclick="jQuery(this).next('ul.files').toggle();">{$lang->uploaded_file} [<strong>{$oDocument->get('uploaded_count')}</strong>]</button>
   <ul class="files">
    <li loop="$oDocument->getUploadedFiles()=>$key,$file"><a href="{getUrl('')}{$file->download_url}">{$file->source_filename} <span class="fileSize">[File Size:{FileHandler::filesize($file->file_size)}/Download:{number_format($file->download_count)}]</span></a></li>
   </ul>
  </div>
  <div class="tns">
   {@ $tag_list = $oDocument->get('tag_list') }
   <span class="tags" cond="count($tag_list)">
    <!--@for($i=0;$i<count($tag_list);$i++)-->
     {@ $tag = $tag_list[$i]; }
     <a href="{getUrl('search_target','tag','search_keyword',$tag,'document_srl','')}" class="tag" rel="tag">{htmlspecialchars($tag)}</a><span>,</span>
    <!--@end-->
   </span>
   <a class="document_{$oDocument->document_srl} action" href="#popup_menu_area" onclick="return false">{$lang->cmd_document_do}</a>
   <ul class="sns">
    <li class="twitter link"><a href="http://twitter.com/">Twitter</a></li>
    <li class="me2day link"><a href="http://me2day.net/">Me2day</a></li>
    <li class="facebook link"><a href="http://facebook.com/">Facebook</a></li>
    <li class="delicious link"><a href="http://delicious.com/">Delicious</a></li>
   </ul>
   <script>
    jQuery(function($){
     $('.twitter>a').snspost({
      type : 'twitter',
      content : '{$oDocument->getTitle()} {$oDocument->getPermanentUrl()}'
     });
     $('.me2day>a').snspost({
      type : 'me2day',
      content : '\"{$oDocument->getTitle()}\":{$oDocument->getPermanentUrl()}'
     });
     $('.facebook>a').snspost({
      type : 'facebook',
      content : '{$oDocument->getTitle()}'
     });
     $('.delicious>a').snspost({
      type : 'delicious',
      content : '{$oDocument->getTitle()}'
     });
    });
   </script>
  </div>
  <div class="sign" cond="$module_info->display_sign!='N'&&($oDocument->getProfileImage()||$oDocument->getSignature())">
   <img cond="$oDocument->getProfileImage()" src="{$oDocument->getProfileImage()}" alt="Profile" class="pf" />
   <div cond="$oDocument->getSignature()" class="tx">{$oDocument->getSignature()}</div>
  </div>
  <div class="btnArea">
   <a class="btn" cond="$oDocument->isEditable()" href="{getUrl('act','dispBoardWrite','document_srl',$oDocument->document_srl,'comment_srl','')}">{$lang->cmd_modify}</a>
   <a class="btn" cond="$oDocument->isEditable()" href="{getUrl('act','dispBoardDelete','document_srl',$oDocument->document_srl,'comment_srl','')}">{$lang->cmd_delete}</a>
   <span class="etc">
    <a class="btn" href="{getUrl('document_srl','')}">{$lang->cmd_list}</a>
   </span>
  </div>
 </div>
 <!-- /READ FOOTER -->
</div>
<block cond="$oDocument->allowTrackback()">
 <include target="_trackback.html" />
</block>
<include target="_comment.html" />

관련글 더보기

댓글 영역