커스텀 포스트 역할과 권한 옵션 집중 분석

역할과 권한에 대한 항목에는 다음 옵션들이 있다.

  • capability_type
  • capabilities
  • map_meta_cap

전체 옵션은 이 포스트에서 확인할 수 있다.

역할과 권한

워드프레스의 권한(authority) 시스템은 매우 잘 구축되어 있다. 권한은 세부적으로 역할과 권한(capability)으로 쪼개어져 문맥(context)에 따라 어떤 작업을 허가할 수도, 거부할 수도 있도록 조직되어 있다.

역할 (Roles)

사실 ‘역할’은 ‘권한’보다는 커스텀 포스트와는 덜 밀접한 관계이긴 하지만 권한과 역할이 워낙 가까운 관계라 언급은 해야 할 것 같다. 쉽게 말해 사용자의 그룹이라 볼 수 있다. 한 사용자가 사이트 내부에서 어떤 담당인지를 나타내 준다.

각 역할 별로 일정한 권한의 목록이 기본으로 주어진다. 물론 새로운 역할도 추가할 수 있고, 특정 권한을 더 추가할 수도, 덜어낼 수도 있다. 역할에 대해서는 자세한 설명을 생략하도록 하겠다. 코덱스를 참고하기 바란다.

권한 (Capabilities)

워드프레스에서 동작에 대한 문맥을 나타낸다. 예를 들어 포스트를 편집, 삭제, 발행하는 각각에 대한 동작들을 의미한다. 역할과 마찬가지로 사용자별로 권한을 더해줄 수도, 덜어낼 수도 있으며 완전히 새로운 권한을 만드는 것도 가능하다. 단, 여기서는 커스텀 포스트 옵션과 관련된 사항에서만 설명을 하도록 하겠다.

커스텀 포스트의 권한 대응

커스텀 포스트는 기본 포스트 타입을 확장하여 사용하는 것이므로, 기본 포스트가 가진 권한 시스템을 그대로 물려받게 된다.

포스트, 혹은 페이지에 대해 다수의 권한이 있고, 커스텀 포스트를 생성하면서 이 권한들을 1:1로 모두 새롭게 재정의해 주어야 코어 내부에서 올바르게 권한을 체크할 수 있다.

예를 들어 publish_posts 권한을 살펴 보자. 이 권한이 주어지지 않은 사용자는 글을 쓰고 편집할 수는 있어도 편집 화면에서 ‘공개하기(publish)’ 버튼이 나오지 않는다.

custom_post_cap_001
Contributor의 글 작성 화면. Publish 대신 Submit for Review 버튼으로 나온다.

위 그림을 보자. 어떤 기여자(Contributor) 역할의 사용자가 새 포스트를 작성 중이다. Publish 메타 박스를 보면 보통 볼 수 있는 ‘Publish’ 버튼 대신 ‘Submit for Review’라는 버튼으로 변경되어 있다. 왜냐하면 기여자는 새 글을 쓸 수는 있고 그 글을 편집할 수는 있지만 글을 최종 단계인 발행 상태로는 만들 수 없기 떄문이다.

자, 이제 커스텀 포스트를 생성해야 한다고 보자. 그럼 publish_posts 권한에 대해 두 가지 결정을 할 수 있다. 기존의 포스트(혹은 페이지)와 동일한 권한을 가질 것인가, 아니면 별도의 권한으로 나누어야 할 것인가.

동일한 권한이라면, 글(혹은 페이지)를 편집, 수정 발행할 수 있는 권한을 가진 사용자는 커스텀 포스트에 대해 동일한 작업이 될 것이다. 반면 별도의 권한을 가지도록 처리한다면, 조금 성가시기는 하겠지만, 보다 세밀하게 권한을 나누어 원하는 대로 분업을 진행할 수 있을 것이다.

그럼 이제 PHP 코드 관점에서 권한의 구조를 생각해 보자. 권한은 연관 배열(혹은 stdClass)을 사용한다. 권한의 공통적인, 즉 ‘문맥상’의 호칭은 연관 배열의 키(혹은 속성)로, 해당 실제 권한명은 값으로 기록한다. 아래는 그 예이다.

register_post_type(
  'sample_audio',
  array(
    ....
    'capabilities' => array(
      ....
      'publish_posts' => 'publish_posts',
      ....
    ),
  ....
);

register_post_type(
  'sample_video',
  array(
    ....
    'capabilities' => array(
      ....
      'publish_posts' => 'publish_videos',
      ....
    ),
  ....
); 

두 포스트 타입, ‘sample_audio’와 ‘sample_video’를 등록할 때 publish_posts라는 문맥상의 권한에서 audio는 포스트의 것을 그대로 가져오는 반면 video는 ‘publish_videos’라는 새로운 권한 이름을 지어 냈다.

$caps[] = $post_type->cap->publish_posts;

반면 코어 코드에서는 이렇게 쓰이기도 한다. 내부적으로 array를 stdClass로 타입 변환하는 과정이 생겨 키가 아닌 속성으로 불리긴 하나, 이 코드에서 audio 포스트타입이라면 $caps[]에 저장되는 문자열은 ‘publish_posts’, video 포스트 타입이라면 ‘publish_videos’가 될 것이다.

map_meta_cap: 옵션, 함수, 그리고 필터의 이름

여기저기서 map_meta_cap이라는 문자열이 언급되는데, 워드프레스 코어에서 이 것이 세 가지 의미로 쓰이고 있다. 애매모호함을 없애기 위해 우선 이것부터 명확히 정의하고 넘어가자.

첫번째로 map_meta_cap은 register_custom_post() 함수의 옵션 인자로 쓰인다. 자명하다.

두번째로 map_meta_cap은 함수 이름으로 쓰인다. map_meta_cap() 처럼 뒤에 괄호를 붙여 구분하기도 한다. 이후 메타 권한 설명에서 설명하겠지만, map_meta_cap() 함수는 권한 판별에 매우 중요한 역할을 하는 함수이다.

셋째로 map_meta_cap은 필터 이름으로 쓰인다. map_meta_cap() 함수 내부에서 가장 마지막에 함수의 연산 결과를 재정의할 때 사용된다.

이 셋이 치명적인 오해를 불러일으키는 것은 아니지만, 코어 코드를 보면, map_meta_cap이 콜백 함수로 불리기 때문에 마치 이것이 필터처럼 보이기도 해서 처음 이 쪽 코드를 볼 때 오해하고 살짝 헤맨 경험이 있어 혹시나 해서 언급한다.

// class-wp-user.php, has_cap() function
$caps = call_user_func_array( 'map_meta_cap', $args );
// capabilities.php, map_meta_cap()
return apply_filters( 'map_meta_cap', $caps, $cap, $user_id, $args );

기본 권한과 메타 권한

코덱스에서 언급되는 이해하기 쉽지 않은 개념 중에 기본 권한(primitive capabilities)와 메타 권한(meta capabilities)가 있다. 말장난 같기도 하고 좀 어렵지만, 이해해 보면 워드프레스가 권한에 매우 세심하게 디자인을 했다는 것을 알 수 있다.

메타 권한이란 고정되어 있지 않은 권한들을 의미한다. 메타 권한은 조건에 때라 각각 다른 기본 권한으로 대응될 수 있다. 쉬운 예를 들어 설멍해 보자. 에디터 A, B 둘이 있다. 모든 포스트에 대해 A, B 동일한 권한이 있다. 그러나 어느 특정 카테고리에서만 A는 편집을 허용하고, B는 허용하지 않게 해야 한다고 하자. 이렇게 권한을 설정하게 하려면 어떻게 해야 할까? 이렇게 특정한 조건에 따라 유동적으로 설정 가능한 권한이 메타 권한이다.

반면 기본 권한은 고정된 권한이다. 메타 권한은 조건에 따라 기본 권한 하나로 대응된다. 일반적으로는 어떤 개체 하나에 대한 권한은 메타 권한, 복수의 개체에 대한 권한은 기본 권한으로 설정되어 있다.

메타 권한 동작의 예

메타 권한 중의 하나인 ‘edit_post’ 권한을 예로 들어, 이 권한의 코어 내부에서 어떻게 동작하는지 간략히 설명해 본다.

사용자가 포스트의 편집 화면을 접근한다고 하자. URL은 wp-admin/post.php?post=<post_id>&action=edit&…. 와 비슷하게 구성된다. wp-admin/post.php 스크립트는 편집 화면을 구성하는데, 먼저 사용자에게 포스트의 편집 권한이 있는지를 확인한다.

// wp-admin/post.php, Line 116
  if ( ! current_user_can( 'edit_post', $post_id ) )
    wp_die( __( 'You are not allowed to edit this item.' ) );

현재 사용자가 $post_id에 대해 ‘edit_post’ 권한을 가지고 있는지 물어보고 아니면 wp_die()에 의해 중단된다. current_user_can() 함수는 현재 사용자를 파악하고, 현재 사용자에 대해 class-wp-user.php의 WP_User::has_cap() 함수를 호출한다.

이어 WP_User::has_cap() 함수는 내부에서 map_meta_cap() 함수를 호출한다.

public function has_cap( $cap ) {
  ....
  $args = array_slice( func_get_args(), 1 );
  $args = array_merge( array( $cap, $this->ID ), $args );
  $caps = call_user_func_array( 'map_meta_cap', $args );   // 여기
  ....

map_meta_cap() 내부에는 긴 switch ~ case 문이 있다. 여기서 case ‘edit_post’ 부분을 타고 들어가 보자. 해당 포스트의 작성자와 포스트의 상태에 따라 메타 권한의 결과가 변한다. if 분기를 세심하게 체크해 보면 어떤 경우에는 ‘edit_published_posts’, 또 어떤 경우에는 ‘edit_posts’나 ‘edit_others_posts’, 또 어떤 경우에는 ‘edit_private_posts’ 같은 기본 권한들로 대응되는 것을 확인할 수 있다.

// If the post author is set and the user is the author...
if ( $post->post_author && $user_id == $post->post_author ) {
  // If the post is published or scheduled...
  if ( in_array( $post->post_status, array( 'publish', 'future' ), true ) ) {
    $caps[] = $post_type->cap->edit_published_posts;
  } ...
} else {
  // The user is trying to edit someone else's post.
  $caps[] = $post_type->cap->edit_others_posts;
  ...
}

map_meta_cap() 함수는 $caps라는 배열을 리턴한다. 이 배열은 해당 문맥에서 사용자에게 요구하는 필수 요구 권한의 목록이다. 이 예에서는 $caps 배열의 길이가 아마도 1이겠지만, 조건에 따라 길이가 더 길어질 수도 있다.

$capabilities = apply_filters( 'user_has_cap', $this->allcaps, $caps, $args, $this );

// Everyone is allowed to exist.
$capabilities['exist'] = true;

// Must have ALL requested caps.
foreach ( (array) $caps as $cap ) {
  if ( empty( $capabilities[ $cap ] ) )
    return false;
}

위 코드는 WP_User::has_cap()의 마지막 부분이다. 사용자의 모든 권한을 가져와서 필수 요청 권한 목록인 $caps와 대조하는 코드이다. 전체 권한 중에서 요구된 권한 하나라도 발견되지 않는다면, 해당 사용자는 권한이 없는 것으로 간주된다.

각 옵션 설명

권한은 그 수가 많고 만큼, 쉽게 사용할 수 있어야 한다. 쉽게 쓰려고 의도했으면 쉽게 사용할 수 있도록 되어야 한다. 권한에 대해 고민하지 않고 그냥 포스트나 페이지처럼 만드려면 권한 관련 옵션을 아예 언급조차 하지 않으면 된다. 그러면 포스트나 페이지에 의거해 동일한 권한으로 자동 생성된다.

capapbility 옵션

권한 설정에 관련된 함수는 wp-includes/posts.php get_post_type_capabilities()이다. 소스 코드를 첨부한다.

function get_post_type_capabilities( $args ) {
  if ( ! is_array( $args->capability_type ) )
    $args->capability_type = array( $args->capability_type, $args->capability_type . 's' );

  // Singular base for meta capabilities, plural base for primitive capabilities.
  list( $singular_base, $plural_base ) = $args->capability_type;

  $default_capabilities = array(
    // Meta capabilities
    'edit_post'          => 'edit_'         . $singular_base,
    'read_post'          => 'read_'         . $singular_base,
    'delete_post'        => 'delete_'       . $singular_base,
    // Primitive capabilities used outside of map_meta_cap():
    'edit_posts'         => 'edit_'         . $plural_base,
    'edit_others_posts'  => 'edit_others_'  . $plural_base,
    'publish_posts'      => 'publish_'      . $plural_base,
    'read_private_posts' => 'read_private_' . $plural_base,
  );

  // Primitive capabilities used within map_meta_cap():
  if ( $args->map_meta_cap ) {
    $default_capabilities_for_mapping = array(
      'read'                   => 'read',
      'delete_posts'           => 'delete_'           . $plural_base,
      'delete_private_posts'   => 'delete_private_'   . $plural_base,
      'delete_published_posts' => 'delete_published_' . $plural_base,
      'delete_others_posts'    => 'delete_others_'    . $plural_base,
      'edit_private_posts'     => 'edit_private_'     . $plural_base,
      'edit_published_posts'   => 'edit_published_'   . $plural_base,
    );
    $default_capabilities = array_merge( $default_capabilities, $default_capabilities_for_mapping );
  }

  $capabilities = array_merge( $default_capabilities, $args->capabilities );

  // Post creation capability simply maps to edit_posts by default:
  if ( ! isset( $capabilities['create_posts'] ) )
    $capabilities['create_posts'] = $capabilities['edit_posts'];

  // Remember meta capabilities for future reference.
  if ( $args->map_meta_cap )
    _post_type_meta_capabilities( $capabilities );

  return (object) $capabilities;
}

capability 옵션은 문자열, 혹은 배열을 값으로 쓸 수 있다. 문자열은 단수형 단어(내부에서 문자 뒤에 s를 붙여 크기 2짜리 배열로 조정한다), 그리고 배열은 크기 2짜리로 단수, 복수로 권한에 대한 단어를 입력해 주면 된다. (소스코드 2~6라인 참고)

코어는 capability 옵션에 대해 다음 권한을 기본적으로 생성한다.

  • edit_post
  • read_post
  • delete_post

위 세 권한은 메타 권한이다. (소스코드 9~12라인)

  • edit_posts
  • edit_others_posts
  • publish_posts
  • read_privte_posts

이 네 권한은 기본 권한으로, map_meta_caps() 함수 내부에서는 언급되는 일이 없다. 그러나 코어 구석구석에서 권한 검사에 사용되고 있을 것이다. (소스코드 13~17라인)

만일 map_meta_caps 옵션을 적지 않거나(null), true로 설정했다면 7개의 기본 권한이 별도로 생성된다. 다음은 그 목록이다. (소스코드 20~32라인)

  • read
  • delete_posts
  • delete_private_posts
  • delete_published_posts
  • delete_others_posts
  • edit_private_posts
  • edit_published_posts
  • create_posts

capabilities 옵션

위 소스 코드 34라인이다.

$capabilities = array_merge( $default_capabilities, $args->capabilities );

$args->capabilities가 capabilities 옵션이다. 여기서는 권한의 종류를 더욱 자세하게 정의할 수 있다. 보다시피 capability 옵션으로 인해 생긴 기본 설정에 더불어 별도의 권한을 추가할 수도 있고, 기본 설정을 변경할 수도 있게 짜여 있다.

map_meta_cap

map_meta_cap 옵션 값은 true/false, 그리고 기본으로는 null을 가질 수 있다. null로 값을 주면 내부에서 true로 변환시킨다. 즉 기본값이 true.

간단히 말해 이 옵션은 해당하는 커스텀 타입이 선언한 메타 권한이 기본 권한으로 대응시키는 작업을 허용할지 말지를 결정한다. 이 작업은 map_meta_cap() 함수에 정의되어 있다. 만일 false라면 일부 기본 권한은 수동으로 생성해야 하며, 메타 권한의 기본 권한 대응 작업은 별도로 제작해야 한다.

보다 세밀한 설명은 이렇다. 이 옵션은 다음과 같은 역할을 한다.

첫째로 get_post_type_capabilities() 함수 내부에서 true(혹은 null)일 때 일곱가지 기본 권한(read, delete_posts, 등…)을 설정해주는 역할을 맡는다. 만일 이 값이 false라면 이 일곱가지 기본 권한을 map_meta_cap 필터를 이용해 별도로 보충해 줘야 한다.

둘째로 이 값이 true(혹은 null)이어야 map_meta_cap() 함수 내부에서 post, page와 관련된 메타 권한 체크 로직에서 포스트와 페이지의 여러 상태에 따라 적절한 기본 권한으로 해석되도록 하는 코어의 기본 로직이 동작한다.

map_meta_cap() 함수의 메타 권한 체크 부분 중 포스트나 페이지와 관련된 곳을 보면 다음과 비슷한 코드가 있는 것을 볼 수 있다.

if ( ! $post_type->map_meta_cap ) {
  $caps[] = $post_type->cap->$cap;
  // Prior to 3.1 we would re-call map_meta_cap here.
  if ( 'edit_post' == $cap )
    $cap = $post_type->cap->$cap;
  break;
}

map_meta_cap 옵션 값이 false가 되면 map_meta_caps() 함수 내에서 메타 권한은 더이상 메타 권한으로써 동작하지 않는다. 즉, 해당 capability를 액면 그대로 요구한다. 그러면 이렇게 반환된 capability에 대해서는 별도의 필터 콜백으로 검사해 주어야 한다.

셋째로 메타 권한인 read_post, delet_post, edit_post, 이 셋의 권한 이름을 변경했을 때 추적할 수 있도록 한다. _post_type_meta_capabilities() 함수와 map_meta_cap() 함수 switch-case 구문의 default 레이블에 구현되어 있다.

예제 코드 #1 특정 사용자의 특정 카테고리 편집 제한

메타 권한에 대한 설명에서 에디터 B에 대해 특정 카테고리의 글은 편집 제한을 거는 실제 코드를 구현해 보자.

에디터 B의 ID는 ‘5’이고, 카테고리의 택소노미 이름은 ‘wphack_taxonomy”이고, 카테고리 이름은 ‘a-only’이다. 포스트를 적당히 만들고 a-only 카테고리로 설정하고, 다음 코드를 작성한다. 접근 제한에 대한 세련된 예는 아니지만  권한에 대한 하나의 예제로써 이해하기를 바란다.

add_filter( 'map_meta_cap', 'wphack_meta_cap', 99, 4 );

function wphack_meta_cap( $caps, $cap, $user_id, $args ) {

  global $pagenow, $post_type;

  if ( $pagenow == 'post.php' && $post_type == 'wphack' && $cap == 'edit_post' ) {

    $post_id    = $args[0];
    $categories = wp_get_post_terms( $post_id, 'wphack_taxonomy', array( 'fields' => 'names' ) );

    if ( in_array( 'a-only', $categories ) ) {
      $a_blacklist = array( 5, 10, 11, 12, );
      if ( in_array( $user_id, $a_blacklist ) ) {
        $caps = array( 'do_not_allow' );
      }
    }
  }

  return $caps;
}

에디터 B가 해당 포스트의 편집 화면에 접근하면 다음과 같은 에러가 발생한다.

custom_post_cap_002

예제 코드 #2 읽기 전용 커스텀 포스트

물론 다른 웹프레임워크를 이용해 UI를 다 만들 수도 있겠지만, 워드프레스를 사용하면 나름 괜찮은 UI를 손쉽게 가져다 쓸 수 있다. 이 UI를 통해 개체 수정 및 임의 작성도 가능하니 금상첨화다.

다만, 어떤 다른 후단부(backend)가 존재하여 포스트의 데이터를 모두 일괄적으로 생성하는 시나리오가 있다고 가정하자. 이 커스텀 포스트는 워드프레스 UI 상에서 읽기, 조회만 가능하고 포스트의 생성과 수정은 별도의 스크립트에서 담당해야 한다. 단, 삭제의 경우는 UI 상에서 데이터를 체크하고 수동으로 지우는 일이 가능하다고 하자.

이러한 시나리오에서 커스텀 포스트는 포스트의 목록과 상세 화면 UI가 필요하다. 그러나 커스텀 포스트의 상세 화면에서는 포스트의 어떤 필드도 수정할 수 없도록 처리해야 한다.

먼저 커스텀 포스트 타입을 생성해 보자. ‘init’ 훅에서 register_post_type의 인자를 설정해 준다. 여기서 레이블을 바꿔치기했다. ‘edit’은 이제 편집이 아니라 세부항목을 보는 것으로 의미가 변경된다. 주석 부분을 해제하면 삭제도 불가능하게 만들 수 있다.

  $args = array(
    'labels'               => array(
      'name'          => 'RO Posts',
      'signular_name' => 'RO Post',
      'edit_item'     => 'RO Post Details',
    ),
    'capabilities'         => array(
      'create_posts' => 'do_not_allow',
//          'delete_posts'           => 'do_not_allow',
//          'delete_private_posts'   => 'do_not_allow',
//          'delete_published_posts' => 'do_not_allow',
//          'delete_others_posts'    => 'do_not_allow',
    ),
    'map_meta_cap'         => TRUE,
    'public'               => TRUE,
    'show_ui'              => TRUE,
    'supports'             => FALSE,
    'register_meta_box_cb' => 'rocp_customize_meta_boxes',
  );

  $rocp = register_post_type( 'rocp', $args );

그리고 activation 훅과 deactivation 훅에서 적절히 예제 포스트를 생성하고 삭제하는 코드를 넣는다.

register_activation_hook( __FILE__, 'rocp_activation' );
function rocp_activation() {
  rocp_register_post_type();
  for ( $i = 1; $i <= 5; ++ $i ) {
    wp_insert_post(
      array(
        'post_title'     => sprintf( 'Read Only Custom Post Sample #%02d', $i ),
        'post_status'    => 'publish',
        'post_type'      => 'rocp',
        'comment_status' => 'closed',
        'ping_status'    => 'closed',
        'post_name'      => sprintf( 'rocp-%02d', $i ),
        'meta_input'     => array(
          'rocp_meta_01' => "meta_01_$i",
          'rocp_meta_02' => "meta_02_$i",
        ),
      )
    );
  }
  flush_rewrite_rules();
}

register_deactivation_hook( __FILE__, 'rocp_clear' );
function rocp_clear() {
  $query = new WP_Query(
    array(
      'post_type' => 'rocp',
      'nopaging'  => TRUE,
      'fields'    => 'ids',
    )
  );

  $posts = $query->get_posts();
  foreach ( $posts as $post ) {
    wp_delete_post( $post, TRUE );
  }
}

그리고 나머지 UI를 조정해 문맥에 맞지 않은 행동은 삭제한다. 우선 row action 항목에서의 edit도 detail로 변경한다.

function rocp_row_actions( $actions, $post ) {

  if ( $post->post_type == 'rocp' ) {
    if ( $actions['edit'] ) {
      $actions['edit'] = sprintf(
        '<a href="%s" aria-label="%s">%s</a>',
        get_edit_post_link( $post->ID ),
        /* translators: %s: post title */
        esc_attr( sprintf( __( 'Detail &#8220;%s&#8221;', 'rocp' ), $post->post_title ) ),
        __( 'Detail', 'rocp' )
      );
    }

    if ( $actions['inline hide-if-no-js'] ) {
      unset( $actions['inline hide-if-no-js'] );
    }
  }

  return $actions;
}

그리고 목록 UI에서 벌크 작업 목록에서 편집 항목을 제거한다.

add_filter( 'bulk_actions-edit-rocp', 'rocp_remove_bulk_edit' );
function rocp_remove_bulk_edit( $actions ) {

  if ( isset( $actions['edit'] ) ) {
    unset( $actions['edit'] );
  }

//  if ( isset( $actions['trash'] ) ) {
//      unset( $actions['trash'] );
//  }

  return $actions;
}

Detail 스크린에서 어떤 메타박스도 볼 수 없도록 register_post_type의 ‘register_meta_box_cb’ 콜백 함수에서 지저분하게 남아 있는 메타 박스를 모조리 제거한다.

function rocp_customize_meta_boxes() {

  // disable slug metabox
  remove_meta_box( 'slugdiv', 'rocp', 'normal' );

  // disable post update metabox
  remove_meta_box( 'submitdiv', 'rocp', 'side' );
}

화면 상단의 스크린 옵션을 제거해 보았다. 메타 박스 등을 고려한다면 나타나게 할 수도 있을 것이다. 또한 예제에서는 어떤 메타박스도 쓰지 않기 때문에 화면을 1열로만 쓰는 것이 더 깔끔하기 때문에 화면 레이아웃도 1단으로 고정시켰다. 포스트 타입에 따라 적절히 조절할 수 있을 것이다.

add_filter( 'screen_options_show_screen', 'rocp_disable_screen_option', 10, 2 );
function rocp_disable_screen_option( $show_screen, $wp_screen ) {

  if ( $wp_screen->id == 'rocp' ) {
    $show_screen = FALSE;
  }

  return $show_screen;
}

add_filter( 'screen_layout_columns', 'rocp_screen_layout_columns', 10, 2 );
function rocp_screen_layout_columns( $columns, $screen_id ) {

  if ( $screen_id == 'rocp' ) {
    $columns['rocp'] = 1;
  }

  return $columns;
}

add_filter( 'get_user_option_screen_layout_rocp', 'rocp_user_option_screen_layout' );
function rocp_user_option_screen_layout( $result ) {
  
  return 1;
}

상세화면의 본문 구성은 ‘edit_form_after_editor’ 훅을 이용해 삽입 가능하다. 물론 메타 박스를 이용해 구성하는 것도 관계 없다.

add_action( 'edit_form_after_editor', 'rocp_display_detail' );
function rocp_display_detail( $post ) {

  $rocp_meta_01 = get_post_meta( $post->ID, 'rocp_meta_01', TRUE );
  $rocp_meta_02 = get_post_meta( $post->ID, 'rocp_meta_02', TRUE );

  echo '<table class="widefat">';
  echo '<thead><tr><th>Name</th><th>Value</th></tr></thead>';
  echo '<tbody>';
  echo '<tr><th>ROCP Meta 01</th><td>' . esc_html( $rocp_meta_01 ) . '</td></tr>';
  echo '<tr><th>ROCP Meta 02</th><td>' . esc_html( $rocp_meta_02 ) . '</td></tr>';
  echo '</tbody>';
  echo '</table>';
  echo '<p><a href="' . esc_url( admin_url( 'edit.php?post_type=rocp' ) ) . '"><button type="button" class="button button-primary">Go back</button></a></p>';

}

코드 전문은 여기서 확인할 수 있다. 이 플러그인을 활성화시키면 다음과 같은 화면을 볼 수 있다.

custom_post_cap_003
포스트 목록에서 ‘Edit’ 텍스트가 ‘Detail’ 텍스트로 변경되었다. 커스텀 포스트의 목록은 플러그인을 활성화하면 자동으로 생성되고, 비활성화하면 자동으로 삭제된다.
custom_post_cap_004
Edit 화면의 레이블이 변경되었다. 어떤 입력 위젯도 출력되지 않으며, 스크린 옵션도 삭제되었다. 커스텀 포스트에 예제로 만든 2개의 커스텀 필드만 표로 출력되게 조작되었다.
custom_post_cap_005
편집 권한이 없으므로 대량 편집 기능도 삭제하는 것이 맞다.

 

댓글 남기기