← home

워드프레스 – CRUD

wp-crud

 

* 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