워드프레스 폼 메일 데이터를 커스텀 포스트 타입 만들고 저장

워드프레스 폼 플러그인 중에는 폼 데이터를 메일로 보내면 그만인 것이 있고, 메일도 보내고 포스트 데이터로도 저장하는 플러그인이 있습니다. 물론, 메일만 보내는 플러그인도 애드온 등의 플러그인을 사용하면 포스트로 저장할 수 있습니다.

메일 발송과 함께 포스트로 저장하는 대표 플러그인으로 Jetpack의 Contact Form 모듈이 있습니다. 다음 그림처럼 폼 데이터 목록을 볼 수 있는데, 해당 포스트 타입 이름은 feedback(피드백)입니다. 새로운 포스트 타입에 폼 데이터를 저장한다는 뜻입니다.

젯팩 콘텍트 폼 포스트 목록

피드백 포스트 편집 화면에 접근하면 다음 그림의 형태로 폼 데이터를 저장하는 것을 볼 수 있습니다.

젯팩 폼 포스트 데이터 편집 화면

이처럼 폼으로 받은 데이터를 메일로 보내는 것과 함께 원하는 데이터 유형의 워드프레스 포스트로 저장한다면 데이터 활용 기회를 확보할 수 있습니다. 폼 데이터는 보통의 포스트 콘텐츠 유형과 메타 데이터가 아니라 하나의 필드에 시리얼 데이터로 저장하면 때로 편리할 수 있습니다. 물론, 절대적인 것은 아니며, 자유입니다.

이 포스트에서는 지난 워드프레스 폼 데이터 처리 방식으로 메일 전송 폼 직접 만들고 Contact Form 7 그만 쓰기 포스트의 완성 파일을 기준으로 폼 데이터를 저장해봅니다. 따라서, 다음 링크 페이지에서 파일을 받아 열어 놓고 확인해야 합니다.

또, 파일은 간단한 플러그인 형식으로 정의한 것이므로 업로드 방식으로 플러그인을 설치한 후 진행해야 이해를 더할 수 있습니다. 파일을 받고 설치 후 활성화한 상태를 기준으로 이어갑니다.

폼 데이터 저장

다음 그림은 Shortcode 삽입으로 출력한 폼이 있는 page 타입 포스트의 사이트 화면으로, 각 필드에 데이터를 입력하고 보내기 버튼을 클릭하면 폼 데이터를 메일로 받을 수 있습니다.

사이트 폼에서 데이터 입력

이때, 메일 발송 외에 데이터를 post 타입에 저장하려면 메일 요청 성공 후 단계에서 포스트 저장 코드를 추가하면 됩니다. 다음 코드는 메일 요청 성공 후 만료 기간이 있는 transient 데이터를 생성하여 일정 시간이 지나야 폼 데이터를 전송하도록 정의한 것입니다.

if ( false === ( $form_ticket = get_transient( $transient ) ) ) {
    $form_ticket = wp_generate_password( 24, false, false );
    set_transient( $transient, $form_ticket, $delay * MINUTE_IN_SECONDS );

}

위 코드를 찾아 위의 코드 4번 줄 위치에 다음 아래의 코드를 추가하면 폼 데이터를 formfeed 커스텀 포스트 타입에 저장할 수 있습니다. 아직 formfeed 포스트 타입은 정의하지 않았으므로 post로 변경하고 저장 후 시험하면 추가된 데이터를 관리페이지에서 볼 수 있습니다.

// Insert Post
$parent_post_id = absint( url_to_postid( $_SERVER['HTTP_REFERER'] ) ); // 폼 삽입한 싱글 포스트 ID.

$post_content = array(
    '제목'      =>  $subject,
    '이름'      =>  $name,
    '메일'      =>  $email,
    '내용'      =>  $content,
    '시각'      =>  $time,
    '아이피'    =>  $user_ip,
    '페이지'    =>  $_SERVER['HTTP_REFERER'],
    '그룹'      =>  ( $parent_post_id ) ? $parent_post_id : ''
);

$post_id = wp_insert_post(
    array(
        'post_title'    =>  $subject,
        'post_content'  =>  maybe_serialize( $post_content ),
        'post_status'   =>  'publish',
        'post_type'     =>  'formfeed',
        'post_name'     =>  wp_generate_password( 24, false, false ),
        'post_parent'   =>  $parent_post_id,
        'post_author'   =>  0,
    )
);

2번 줄은 폼을 삽입한 포스트의 ID를 URL 정보 기준으로 구하기 위해 url_to_postid 함수를 사용한 것입니다. 이 포스트는 아니지만, 서로 다른 범위(주제)의 데이터를 수집하는 폼이 많을 때 폼 삽입 포스트 페이지가 범위의 기준이라면 그 페이지 정보를 데이터로 저장하여 데이터 구분과 관리에 도움을 줄 수 있습니다. 22번 줄에서 폼 삽입한 싱글 포스트 ID를 post_parent 필드에 추가한 것도 같은 뜻으로 추가한 것입니다.

12번 줄은 아카이브 페이지 등의 싱글 포스트 페이지가 아닐 때 위젯에 출력한 폼에서 폼 데이터를 저장하는 때를 가정한 것입니다.

15번 줄의 wp_insert_post 함수는 워드프레스 포스트를 추가하는 기본이자 핵심 함수입니다. 이 함수는 워드프레스 포스트 콘텐츠 등록에 관한 많은 단계를 포함하고 있으므로 워드프레스 기본 시스템에 의한 포스트 등록이 아닐 때 꼭 사용하여 괜히 불필요한 과정으로 고생하지 않는 게 좋습니다.

18번 줄에서 maybe_serialize 함수를 사용하여 폼 데이터 전체가 있는 $post_content 변수의 배열 데이터를 시리얼 데이터로 post_content 필드에 저장합니다. 폼의 각 필드 데이터를 포스트의 메타 데이터로 저장해도 되며 방법은 만드는 사람 마음입니다.

21번 줄 post_name은 보통 포스트 슬러그로 부르는 데이터입니다. 포스트 주소에서 URL 제외한 퍼머링크로 고유한 값이어야 합니다. 폼 데이터를 저장한 포스트는 관리자만 관리페이지에서 보는 것을 기준으로 정하기에 무작위 코드로 생성하도록 정의한 것입니다. 이 포스트에서는 의미가 없는 데이터이며, 혹시 중복이라면 워드프레스가 자동으로 숫자를 붙여 추가하므로 염려하지 않아도 됩니다.

커스텀 포스트 타입 formfeed 등록

폼 데이터를 formfeed 이름의 포스트 타입에 포스트로 추가하도록 설정했으므로 해당 포스트 타입을 등록해야 합니다. 이 포스트에서 폼 데이터를 위한 formfeed 포스트는 다음의 몇 가지 주요 기준으로 정의합니다.

  • 비공개 포스트
  • WP REST API 등록 제외
  • 관리페이지에서 목록으로 내용 확인
  • 편집 화면 접근 경로 제거

위의 기준을 적당히 맞추기 위해 다음과 같이 커스텀 포스트 타입을 정의합니다. 플러그인 파일 맨 아래에 이어서 추가하면 됩니다.

add_action( 'init', 'register_formfeed_cpt', 0 );
function register_formfeed_cpt() {
    register_post_type( 'formfeed', array(
        'labels' => array(
            'name' => '문의',
            'singular_name' => '문의',
            'menu_name' => '문의',
        ),
        'capabilities' => array(
            'create_posts' => false, // 새 글 쓰기 불가
        ),
        'map_meta_cap' => true,
        'public' => false,
        'show_ui' => true,
        'show_in_menu' => true,
        'show_in_nav_menus' => false,
        'exclude_from_search' => true, // 's' query var 제외
        'has_archive' => false,
        'rewrite' => false,
        'query_var' => false,
        'capability_type' => 'post', // capability type 'post'. 포스트 타입 'post' 아님
        'menu_icon' => 'dashicons-format-aside',
        'menu_position' => 3,
        'supports' => array( 'title', 'editor' ),
        'show_in_rest' => false // WP REST API, Gutenberg edit disable
    ) );
}

커스텀 포스트 타입과 등록에 관한 내용은 워드프레스 사용자라면 한 번은 직접 학습해야 워드프레스 사용으로 얻는 이익이 늘어나므로 긴 설명은 생략하고 몇 가지만 안내합니다.

9번 줄에서 13번 줄은 정의하는 포스트 타입의 권한에 관한 설정입니다. 13번 줄은 포스트 타입 post가 아니라 Capability 타입 post로, 기본 포스트 타입 post에는 기본 Capability 타입 post가 할당되어 있습니다. 그 Capability 타입 post를 커스텀 포스트 타입 formfeed에 사용한다는 것입니다.

따라서, formfeed 커스텀 포스트 타입에 관한 권한은 기본 Role(역할 그룹)에 할당된 타입 post에 관한 권한 설정과 같습니다. 이때, 10번 줄 create_posts capability false 설정으로 formfeed 포스트를 워드프레스 기본 시스템에서 추가하는 것을 제한하였습니다.

워드프레스 기본 포스트(콘텐츠) 등록 시스템은 이 포스트처럼 사용자가 특정 애플리케이션(폼)을 작성하여 등록하는 것이 아닌 관리페이지에서 등록하는 포스트나 분류 등의 말 그대로 기본으로 제공하는 인터페이스를 통한 등록을 말합니다.

폼 데이터에서 생성하는 formfeed 포스트는 비공개 포스트 기준을 두었으므로 14, 18, 35번 줄 등에서 적절하게 정의하였습니다. 특별히 25번 줄은 주의해야 하는데, 예를 들어 14번 줄이 false라면 자동으로 false 적용이 되는 항목들이 있어 따로 정의하지 않아도 되지만, 25번 줄은 영향이 없으므로 꼭 false로 지정해야 합니다.

25번 줄은 WP REST API 포함(공개)을 결정하는 것으로 워드프레스는 이 포스트 작성일 기준에서 모든 타입의 포스트가 기본 공개로 설정되어 있습니다. 따로 REST API 설정을 변경하지 않았다면 폼 데이터를 비공개로 설정하기 위해서 false 설정을 잊지 않아야 합니다.

이 포스트 작성일 기준에서 정식 도입 상태는 아니지만, 워드프레스 5 버전에서 도입 예정된 Gutenberg 시스템을 비활성화하는 하나의 방법에 25번 설정을 false로 두는 것도 있습니다.

폼 데이터 포스트 및 시리얼 데이터

위의 코드로 커스텀 포스트 타입을 정의하면 관리페이지에서 ‘문의’라는 메뉴가 나타납니다. 사이트의 폼에서 필드에 데이터를 입력하고 보내기 버튼을 클릭하면 메일로 폼 데이터가 전송되고, 관리페이지 문의 메뉴를 클릭하면 다음 그림의 폼 데이터 포스트를 확인할 수 있습니다. 그림은 화면 옵션(Screen Options)에서 Date(일자) 항목을 해제한 것입니다.

폼 데이터를 저장한 포스트 기본 목록

제목을 클릭하면 다음 그림처럼 편집 화면에서 시리얼 형태의 데이터를 볼 수 있습니다.

시리얼 데이터로 저장한 폼 데이터 포스트 편집 화면

그림에서 보는 데이터는 워드프레스 포스트 등록 시스템에서 등록한 것이 아니라 직접 구성한 폼에서 시리얼 데이터로 추가(insert)한 것입니다. Visual 모드에서 업데이트 버튼을 클릭하거나 Text 모드에서 편집 후 업데이트하면 데이터 유형이 변하여 이 포스트에서 원하는 결과를 얻을 수 없습니다.

워드프레스 시스템에서 등록하지 않은 데이터는 끝까지 주의해서 처리해야 하는데, 사용자의 실수를 막는 데 필요한 최소한의 방법을 이어서 구성합니다.

워드프레스 포스트 리스트 테이블

이 포스트에서 구성하는 관리페이지 문의(formfeed) 포스트 목록의 최종 화면은 다음 그림과 같습니다.

편집한 폼 데이터 포스트 목록

포스트 리스트 테이블 열의 이름과 열에 해당하는 데이터를 그림처럼 구성하고, 제목에 링크를 제거하여 편집 화면 접근 경로를 제거합니다. 모든 폼 데이터는 목록에서 열람하는 것을 기준으로 정합니다.

포스트 리스트 테이블 열 이름(header)과 데이터 편집 훅

워드프레스에서 관리페이지 워드프레스 제공 훅을 사용한 포스트 목록 테이블 열 이름과 출력 데이터를 정의하는 방법으로 크게 다음 2, 3번 줄의 2가지를 생각할 수 있습니다. 또, 특정 열에 해당하는 데이터는 6번 줄 훅으로 정의할 수 있습니다.

// 특정 포스트 타입 또는 페이지의 열 이름
manage_{$this->screen->id}_columns
manage_{$post_type}_posts_columns

// 특정 포스트 타입의 열 데이터
manage_{$post_type}_posts_custom_column

이 포스트에서는 3, 6번 줄의 훅을 사용합니다.

열 이름

다음 코드를 플러그인 파일 맨 아래에 그대로 추가하고 저장합니다.

// column name
add_filter( 'manage_formfeed_posts_columns', 'set_formfeed_columns' );
function set_formfeed_columns( $columns ) {
    $columns = array(
        'cb' => $columns['cb'],
        'subject' => '제목',
        'name' => '발신자',
        'message' => '내용'
    );

    return $columns;
}

위의 코드에서 $columns 변수에 기본 제공 열을 포함하여 배열로 정의해야 순서 변경도 쉽고, 추가하지 않으면 출력되지 않으므로 제거 또한 쉽습니다. 결과 확인은 미루고 열 데이터를 추가합니다.

열 데이터

다음 코드를 플러그인 파일 맨 아래에 이어서 추가하고 저장합니다.

// column data
add_filter( 'manage_formfeed_posts_custom_column', 'new_modify_formfeed_table_row', 10, 2 );
function new_modify_formfeed_table_row( $column, $post_id ) {
    $post = get_post( $post_id );
    $post_content = maybe_unserialize( $post->post_content );
    // $post_content = maybe_unserialize( get_post_field( 'post_content', $post_id ) );

    switch ( $column ) {
        case 'subject' :
            echo $post_content['제목'];
            break;
        
        case 'name' :
            $sender = array(
                $post_content['이름'],
                $post_content['메일'],
                $post_content['아이피'],
                $post_content['시각'],
                $post_content['페이지'],
                $post_content['그룹']
            );
            echo implode( "<br />", $sender );
            break;
        case 'message' :
            echo wpautop( $post_content['내용'] );
            break;
    }
}

위의 코드 5번 줄에서 maybe_unserialize 함수를 사용하여 시리얼 데이터로 저장한 데이터를 다시 사용할 수 있도록 정의하였습니다.

지금 과정으로 관리페이지 문의 포스트 목록을 확인하면 다음 그림과 같습니다.

편집 링크가 있는 포스트 목록 화면

post_row_actions

위 그림을 보면 제목 밑에 편집 링크 등이 나옵니다. 이제는 워드프레스 시스템에서 시리얼 데이터 편집의 사용자 실수를 막아 폼 데이터를 목록에서 열람하는 데 오류가 없도록 편집 화면 접근 경로를 제거합니다. 휴지통(Trash)만 남깁니다. 다음 코드를 계속 이어서 추가합니다.

// formfeed
add_filter( 'post_row_actions', 'formfeed_remove_row_actions', 10, 2 );
function formfeed_remove_row_actions( $actions, $type ) {
    if( $type->post_type == 'formfeed' ) {
        unset( $actions['edit'] );
        unset( $actions['view'] );
        // unset( $actions['trash'] );
        unset( $actions['inline hide-if-no-js'] );
    }
    return $actions;
}

열 너비 수정

결과는 따로 확인하지 않고, 이어서 포스트 목록 테이블의 각 열 너비를 수정합니다. 다음 코드를 계속 이어서 추가합니다.

// column 너비
add_action( 'admin_head', 'formfeed_admin_head' );
function formfeed_admin_head() {
    $screen = get_current_screen();
    if ( is_admin() && 'edit-formfeed' == $screen->id ) { ?>
        <style type="text/css"> .column-message { width: 50%; } .column-title {width:30%} </style>
    <?php
    }
}

관리페이지 문의 포스트 목록을 새로 고치면 열 너비의 변화를 볼 수 있습니다.

정리

폼 데이터를 커스텀 포스트 타입의 포스트 데이터로 저장하는 내용은 지금 정도의 내용이면 됩니다. 전체 코드는 간단한 플러그인 형식의 파일로 다음 페이지에서 받을 수 있습니다.

포스트에서는 하나의 파일에 커스텀 포스트 타입, Shortcode, 나머지 액션 등을 모두 추가하여 간단한 플러그인 형식으로 만들었는데, 자신의 사용 방식으로 변경하여 사용하면 됩니다.

폼 데이터를 저장할 때 워드프레스 데이터베이스 테이블의 포스트 콘텐츠로 저장하여 워드프레스 시스템 내부에 포함하는 것이 새로운 테이블을 만들어 저장하는 방법보다 좋지 않을까 생각합니다.

폼 데이터를 저장하는 것은 간단하지만, 데이터 저장을 위한 커스텀 포스트 타입 정의와 설정, 포스트 목록 제어 등 나머지 요소가 더 많은 작업을 요구합니다. 어쩔 수 없으며, 포스트 타입과 분류, 메타 데이터, 권한 정도는 직접 템플릿을 편집하여 워드프레스 사용을 즐기는 사용자라면 한 번은 학습해야 합니다.