* CRUD: 데이터의 Create(생성), Read(읽기), Update(갱신), Delete(삭제)의 약자.
워드프레스에 처음 입문하는 개발자라면, 워드프레스가 기본적으로 제공하는 포스트 타입의 데이터 처리가 아닌 사용자정의 데이터를 CRUD하는 방법이 궁금할 것입니다.
본 포스트에서는 간단한 애플리케이션 제작과정을 통해 워드프레스의 일반적인 CRUD를 설명합니다.
자세한 설명에 앞서 제작 과정을 미리 살펴보겠습니다.
1. 클래스파일 생성, 테마 functions.php에 포함/인스턴스 생성
2. 사용자정의 데이터 테이블 생성
3. 원하는 포스트에 애플리케이션이 나타날 수 있도록 숏코드 등록
4. 폼 구성
5. 데이터 삽입
6. 읽기 처리
7. 갱신 처리
8. 삭제 처리
1. 클래스파일 생성, 테마 functions.php에 포함/인스턴스 생성
현재 사용중인 테마 디렉토리의 어딘가에 mystatus.php 파일을 생성하여 아래 코드를 저장합니다.
class MY_Status { }
현재 사용중인 테마의 functions.php를 엽니다.
저는 현재 사용중인 테마의 inc 디렉토리에 mystatus.php를 생성하였기 때문에, 아래와 같은 경로로 클래스 파일을 포함하고 인스턴스를 생성하였습니다.
include 'inc/mystatus.php'; $GLOBALS['mystatus'] = new MY_Status();
작업준비가 완료되었습니다. functions.php는 닫고, mystatus.php를 열어 CRUD를 하나씩 구현해봅시다.
2. 사용자정의 데이터 테이블 생성
만약 애플리케이션을 플러그인으로 제작하여 배포할 계획이라면, 플러그인이 활성화될 때 테이블이 자동 생성되도록 액션을 걸어주어야 하겠지만, 저는 테마 functions.php에 클래스 파일을 include 하는 방식으로 예제를 만들었기 때문에 데이터 테이블을 직접 미리 만들어 주었습니다.
CREATE TABLE IF NOT EXISTS `wp_z_mystatus` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `content` text NOT NULL, `post_date` datetime NOT NULL, PRIMARY KEY (`ID`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1000 ;
클래스에 생성자함수(__construct)를 만들고, 편의를 위하여 테이블 이름을 변수로 만들어둡니다.
class MY_Status { function __construct(){ global $wpdb; $this->table = $wpdb->prefix . 'z_mystatus'; } }
3. 원하는 포스트에 애플리케이션이 나타날 수 있도록 숏코드 등록
애플리케이션을 화면에 나타내는 방법은 여러가지가 있겠지만, 저는 숏코드를 등록하여 포스트(또는 페이지) 내용의 원하는 위치에 나타나도록 하는 방법을 택하였습니다.
우선 포스트(또는 페이지)를 하나 생성하여 편집기에 [mystatus]
를 입력해줍니다. 이 부분이 실제 애플리케이션으로 대체될 것입니다. 숏코드가 생소하시다면 우선 여기를 먼저 살펴보세요.
숏코드는 add_shortcode를 이용하여 등록합니다.
class MY_Status { function __construct(){ global $wpdb; $this->table = $wpdb->prefix . 'z_mystatus'; add_shortcode( 'mystatus', array($this, 'shortcode') ); } function shortcode(){ ob_start(); ?> <div id="mystatus"> <?php $this->the_form(); ?> <?php $this->the_list(); ?> </div> <?php return ob_get_clean(); } function the_form(){ } function the_list(){ } }
라인 7~16. 편집기에 입력한 [mystatus]
는 shortcode 함수가 리턴하는 내용으로 대체됩니다. 리턴하지 않고 그냥 출력을 하면 편집기에 입력된 다른 내용들보다 먼저 출력되기 때문에 버퍼를 이용하여 리턴해주는 것입니다.
라인 17~20. the_form은 HTML 폼을, the_list는 등록된 데이터를 출력하게될 것입니다.
4. 폼 구성
이제 the_form에 폼을 구성해봅시다.
function the_form(){ if( ! is_user_logged_in() ) return; ?> <form id="mystatus-form" action="<?=admin_url('admin-post.php')?>" method="post"> <?php wp_nonce_field('mystatus', 'mystatus_nonce'); ?> <input type="hidden" name="action" value="mystatus-post"> <textarea name="content" rows="5"></textarea> <input type="submit" value="올리기"> </form> <?php }
라인 2. 본 애플리케이션은 로그인 유저만 작성할 수 있도록 계획하였기 때문에 로그인되지 않은 경우 폼을 출력하지 않습니다.
라인 5. 유의하여 보아야하는 부분은 폼의 action 속성입니다. 요청 데이터를 처리하는 방식은 개발자마다 스타일이 다르겠지만, 워드프레스가 제공하는 일반적인 방법은 admin-post.php를 활용하는 것입니다.
admin-post.php에서 요청 데이터를 처리하기 위해서는 액션을 걸어주어야 할텐데, 훅네임을 어떻게 만들어주어야하는지는 admin-post.php 파일을 열어보면 알 수 있습니다. 아래와 같이 훅 네임이 조합되는 것을 알 수 있습니다.
$action = 'admin_post'; $action .= '_' . $_REQUEST['action']; do_action( $action );[/php]</pre> 따라서 우리는 아래와 같이 훅 네임을 만들어 걸어주면 되겠습니다. <pre>class MY_Status { function __construct(){ global $wpdb; $this->table = $wpdb->prefix . 'z_mystatus'; add_shortcode( 'mystatus', array($this, 'shortcode') ); add_action( 'admin_post_mystatus-post', array($this, 'post_request') ); } function post_request(){ // 데이터 처리 } ...
참고로, 비로그인 유저에 의한 요청인 경우 훅네임이 ‘admin_post_mystatus-post’ 이 아니라, ‘admin_post_nopriv_mystatus-post’ 입니다. 본 애플리케이션은 로그인 유저만 작성할 수 있도록 하였기 때문에 굳이 걸지 않았지만, 비로그인 유저의 요청을 허용하는 애플리케이션은 아래와 같이 추가로 액션을 걸어주어야합니다.
add_action( 'admin_post_nopriv_mystatus-post', array($this, 'post_request') );
5. 데이터 삽입
이제 ‘admin-post.php’로 ‘action’ 변수가 ‘mystatus-post’ 인 요청이 발생하면 ‘post_request’ 함수가 실행될테니 여기서 데이터 처리를 하면 되겠습니다. 만약 워드프레스의 DB 클래스 사용법에 익숙치 않다면 반드시 여기를 미리 참고하세요.
function post_request(){ global $wpdb, $user_ID; if( ! is_user_logged_in() || ! isset($_REQUEST['mystatus_nonce']) || ! wp_verify_nonce($_REQUEST['mystatus_nonce'], 'mystatus') ) return; $content = trim($_REQUEST['content']); $content = apply_filters('pre_comment_content', $content); if( empty($content) ){ wp_die( '내용을 입력하세요.' ); } $data = array( 'content' => $content, 'user_id' => $user_ID, 'post_date' => current_time('mysql'), ); $wpdb->insert($this->table, $data); $goback = wp_get_referer(); wp_redirect( $goback ); exit; }
라인 4~7. 우선 로그인 여부와 nonce를 체크합니다.
라인 9~10. 내용을 필터링합니다. 필터링은 개발자가 원하는 수준으로 하시면 되겠습니다. 저는 워드프레스 댓글 내용 필터링 수준(사용자 역할에 따라 부분적으로 HTML허용)을 적용하였습니다.
라인 15~20. 데이터를 삽입합니다.
라인 22~24. 이전 페이지로 리다이렉트 합니다.
6. 읽기 처리
이제 입력한 데이터를 화면에 출력하기 위해서 the_list 함수 작성하겠습니다.
function the_list(){ $rows = $this->get_rows(); if( empty($rows) ) return; ?> <div id="mystatus-list"> <?php foreach($rows as $row){ ?> <div class="item"> <div class="content"> <?php echo apply_filters('comment_text', $row->content); ?> </div> <div class="date"><?php echo human_time_diff( strtotime($row->post_date), current_time('timestamp') ); ?>전</div> </div> <?php } ?> </div> <?php } function get_rows($limit=10){ global $wpdb, $user_ID; $sql = $wpdb->prepare("SELECT * FROM $this->table ORDER BY post_date DESC LIMIT %d", $limit); $rows = $wpdb->get_results( $sql ); return $rows; }
라인 2. 작성자 구분없이 최근 10개의 데이터를 가져옵니다.
라인 10. ‘내용’은 댓글 수준으로 필터링하였습니다.
라인 12. ‘날짜’는 ‘1분전’, ‘1일전’ 이런 형식으로 출력되도록 human_time_diff 헬퍼를 이용하였습니다.
7. 갱신 처리
갱신 처리를 위해서 앞서 작성한 the_list, the_form, post_request를 모두 보완 해야합니다.
우선 the_list에 수정 링크를 삽입해보겠습니다.
function the_list(){ global $user_ID; $rows = $this->get_rows(); if( empty($rows) ) return; ?> <div id="mystatus-list"> <?php foreach($rows as $row){ ?> <div class="item"> <div class="content"> <?php echo apply_filters('comment_text', $row->content); ?> </div> <div class="meta"> <span class="date"><?php echo human_time_diff( strtotime($row->post_date), current_time('timestamp') ); ?>전</span> <?php if( $row->user_id == $user_ID ){ ?> <span class="sep">|</span> <a href="<?php echo add_query_arg('mystatus-ID', $row->ID)?>" class="edit">수정</a> <?php } ?> </div> </div> <?php } ?> </div> <?php }
라인 15~18. 전역변수인 $user_ID는 현재 로그인 사용자의 아이디입니다. 이를 이용하여 작성자 아이디와 같다면 ‘수정’ 링크를 출력합니다. 수정 링크의 URL은 add_query_arg를 이용해서 현재 페이지 URL에 ‘mystatus-ID’ 변수를 붙여서 생성하였습니다. 예를들어 현재 페이지의 URL이 아래와 같다면
http://example.com/?page_id=2
수정 URL은 아래와 같이 만들어집니다.
http://example.com/?page_id=2&mystatus-ID=1001
이제, 수정 링크를 클릭하면, 데이터를 수정할 수 있도록 해당 데이터를 가져와 폼에 넣어주어야 합니다.
그러기 위해서 the_form 함수를 보완해봅시다.
function the_form(){ if( ! is_user_logged_in() ) return; $ID = isset($_GET['mystatus-ID']) ? $_GET['mystatus-ID'] : ''; $content = ''; $submit_label = '올리기'; $update = false; if( $row = $this->get_row( $ID ) ){ extract($row); $update = true; $submit_label = '수정'; } ?> <form id="mystatus-form" action="<?=admin_url('admin-post.php')?>" method="post"> <?php wp_nonce_field('mystatus', 'mystatus_nonce'); ?> <input type="hidden" name="action" value="mystatus-post"> <input type="hidden" name="ID" value="<?php echo $ID;?>"> <textarea name="content" rows="5"><?php echo esc_textarea($content); ?></textarea> <input type="submit" value="<?=$submit_label?>"> <?php if( $update ){ ?> <a href="<?php echo remove_query_arg('mystatus-ID')?>" class="cancel">취소</a> <?php } ?> </form> <?php } function get_row($ID){ global $wpdb, $user_ID; $row = null; $ID = absint($ID); if( $ID > 0 && $user_ID > 0 ){ $sql = $wpdb->prepare("SELECT * FROM $this->table WHERE user_id=%d AND ID=%d", $user_ID, $ID); $row = $wpdb->get_row( $sql, ARRAY_A ); } return $row; }
라인 5. 요청된 아이디가 있는지 확인합니다.
라인 9~13. 요청된 아이디가 있다면, 데이터를 가져옵니다.
라인 15~29. 가져온 데이터를 넣어주고, ID 필드와 취소 링크를 추가하였습니다.
라인 32~42. 데이터를 가져오는 get_row 라는 함수가 추가되었는데, 요청된 아이디로 데이터를 가져오되, 현재 로그인한 사용자로 제한하고 있습니다. 따라서 팀블로그(혹은 회원제 사이트)인 경우 다른 사용자는 편집을 할 수 없을 것입니다.
이제 post_request 함수에서 갱신 처리도 할 수 있도록 보완해보겠습니다.
function post_request(){ global $wpdb, $user_ID; if( ! is_user_logged_in() || ! isset($_REQUEST['mystatus_nonce']) || ! wp_verify_nonce($_REQUEST['mystatus_nonce'], 'mystatus') ) return; $ID = absint($_REQUEST['ID'])>0 ? $_REQUEST['ID'] : null; $content = trim($_REQUEST['content']); $content = apply_filters('pre_comment_content', $content); if( empty($content) ){ wp_die( '내용을 입력하세요.' ); } $update = false; if( $row = $this->get_row( $ID ) ) $update = true; $data = array('content' => $content); if( $update ){ $wpdb->update($this->table, $data, compact('ID')); }else{ $data['user_id'] = $user_ID; $data['post_date'] = current_time('mysql'); $wpdb->insert($this->table, $data); } $goback = wp_get_referer(); $goback = remove_query_arg('mystatus-ID', $goback); wp_redirect( $goback ); exit; }
the_form과 마찬가지로 요청된 ID로 데이터를 가져오고, 이를 통해 현재 상태가 생성인지 갱신인지를 판단하여 데이터 처리를 합니다. 여기서 user_id와 post_date는 생성시에만 처리되는 점을 유의하세요.
8. 삭제 처리
우선 삭제처리를 위한 액션을 추가로 걸어줍니다.
class MY_Status { function __construct(){ global $wpdb; $this->table = $wpdb->prefix . 'z_mystatus'; add_shortcode( 'mystatus', array($this, 'shortcode') ); add_action( 'admin_post_mystatus-post', array($this, 'post_request') ); add_action( 'admin_post_mystatus-delete', array($this, 'delete_request') ); } function delete_request(){ // 데이터 처리 } ...
the_list의 ‘수정’ 링크 옆에 ‘삭제’ 링크를 추가합니다.
... <a href="<?php echo add_query_arg('mystatus-ID', $row->ID)?>" class="edit">수정</a> <span class="sep">|</span> <a href="<?php echo admin_url('admin-post.php?action=mystatus-delete&ID='.$row->ID.'&mystatus_nonce='.wp_create_nonce('mystatus'))?>" class="delete">삭제</a> ...
삭제 링크의 URL의 쿼리 변수들을 잠시 살펴보면,
– admin-post.php에서 처리할 action
– 수정할 데이터의 ID
– 보안 목적의 nonce
로 구성되었습니다.
http://(도메인과 경로)/wp-admin/admin-post.php?action=mystatus-delete&ID=1001&mystatus_nonce=0bdde911f8
위 URL에 접근하면 delete_request 함수가 실행될 것입니다.
function delete_request(){ global $wpdb, $user_ID; if( ! is_user_logged_in() || ! isset($_REQUEST['mystatus_nonce']) || ! wp_verify_nonce($_REQUEST['mystatus_nonce'], 'mystatus') ) return; $ID = absint($_REQUEST['ID'])>0 ? $_REQUEST['ID'] : null; if( $row = $this->get_row( $ID ) ) $wpdb->query( $wpdb->prepare("DELETE FROM $this->table WHERE ID=%d", $ID) ); $goback = wp_get_referer(); $goback = remove_query_arg('mystatus-ID', $goback); wp_redirect( $goback ); exit; }
삭제를 위한 데이터 검증 및 절차는 생성/갱신과 비슷하므로 설명은 생략하겠습니다.
마치며
본 애플리케이션은 워드프레스에서의 일반적인 CRUD를 설명하기 위해 제작한 것으로, 실서비스에 적용하기엔 다소 미흡할 것입니다. 하지만 본 애플리케이션을 기본 골격으로하여 the_list의 페이징 혹은 무한스크롤, 비로그인 유저 쓰기 허용, 편집기 부착, 사용자 역할별 권한 제한, AJAX로의 전환 등을 적용하면서 절차적으로 업그레이드 해본다면 게시판 같은 애플리케이션으로 발전시킬 수 있을 것입니다.
← home