create a mostly read only CPT

I have a CPT that gets its data by reading from an external server. The external server is the master of the data and my plugin stores a replication without an ability to update the master, therefor the ability to edit the content will just confuse the user, might even make him think that the master will be updated by the edits.

I basically wish to reuse the CPT admin framework but leave only the delete link (maybe not even that), even for administrators.

I can probably do it with CSS but looking for other options that feel less of a hack.

This is for esthetics of UI and not security, so I don’t care that it is possible to work around this.

Solutions Collecting From Web of "create a mostly read only CPT"

I did something similar recently for a simple email log (for diagnosing some problems). I don’t have time to dissect the code, but hopefully it’ll be clear enough for you.

It creates a CPT for storing data that can be viewed in the admin (only), and allows the user to view and delete each post. NB: delete, not trash — no need for resurrection of deleted posts. Edit is changed to View, and metaboxes are replaced with a view template. This code is obviously missing the bits that write to the CPT and auto-purge old ones, but should be enough for your question.

/**
* custom post type for email log
*/
class LogEmailsPostTypeLog {

    /**
    * hooks
    */
    public function __construct() {
        add_action('admin_init', array($this, 'init'));
        add_action('init', array($this, 'register'));
    }

    /**
    * admin_init action
    */
    public function init() {
        global $typenow;

        if (empty($typenow)) {
            // try to pick it up from the query string
            if (!empty($_GET['post'])) {
                $post = get_post($_GET['post']);
                $typenow = $post->post_type;
            }
        }

        if ($typenow == 'log_emails_log') {
            add_filter('display_post_states', '__return_false');
            add_action('edit_form_after_title', array($this, 'adminEditAfterTitle'), 100);
            add_filter('post_row_actions', array($this, 'adminPostRowActions'), 10, 2);
            add_filter('bulk_actions-edit-log_emails_log', array($this, 'adminBulkActionsEdit'));
            add_filter('manage_log_emails_log_posts_columns', array($this, 'adminManageColumns'));
            add_action('manage_log_emails_log_posts_custom_column', array($this, 'adminManageCustomColumn'), 10, 2);
            add_action('admin_print_footer_scripts', array($this, 'adminPrintFooterScripts'));

            add_action('in_admin_header', array($this, 'adminScreenLayout'));
            add_filter('views_edit-log_emails_log', array($this, 'adminViewsEdit'));

            if (is_admin()) {
                add_filter('gettext', array($this, 'adminGetText'), 10, 3);
            }

            wp_enqueue_script('jquery');
        }
    }

    /**
    * register Custom Post Type
    */
    public function register() {
        // register the post type
        register_post_type('log_emails_log', array(
            'labels' => array (
                'name' => __('Email Logs', 'log-emails'),
                'singular_name' => __('Email Log', 'log-emails'),
                'add_new_item' => __('Add New Email Log', 'log-emails'),
                'edit_item' => __('View Email Log', 'log-emails'),
                'new_item' => __('New Email Log', 'log-emails'),
                'view_item' => __('View Email Log', 'log-emails'),
                'search_items' => __('Search Email Log', 'log-emails'),
                'not_found' => __('No email logs found', 'log-emails'),
                'not_found_in_trash' => __('No email logs found in Trash', 'log-emails'),
                'parent_item_colon' => __('Parent email logs', 'log-emails'),
            ),
            'description' => __('Email Logs, as a custom post type', 'log-emails'),
            'exclude_from_search' => true,
            'publicly_queryable' => false,
            'public' => false,
            'show_ui' => true,
            'show_in_admin_bar' => false,
            'menu_position' => 75,
            'hierarchical' => false,
            'has_archive' => false,
            'supports' => array('nada'),
            'rewrite' => false,
            'can_export' => false,
            'capabilities' => array (
                'create_posts' => false,
                'edit_post' => 'manage_options',
                'read_post' => 'manage_options',
                'delete_post' => 'manage_options',
                'edit_posts' => 'manage_options',
                'edit_others_posts' => 'manage_options',
                'publish_posts' => 'manage_options',
                'read_private_posts' => 'manage_options',
            ),
        ));
    }

    /**
    * change some text on admin pages
    * @param string $translation
    * @param string $text
    * @param string $domain
    * @return string
    */
    public function adminGetText($translation, $text, $domain) {
        if ($domain == 'default') {
            if ($text == 'Edit “%s”') {
                $translation = 'View “%s”';
            }
        }

        return $translation;
    }

    /**
    * remove views we don't need from post list
    * @param array $views
    * @return array
    */
    public function adminViewsEdit($views) {
        unset($views['publish']);
        unset($views['draft']);

        return $views;
    }

    /**
    * remove unwanted actions from post list
    * @param array $actions
    * @param WP_Post $post
    * @return array
    */
    public function adminPostRowActions($actions, $post) {
        unset($actions['inline hide-if-no-js']);        // "quick edit"
        unset($actions['trash']);
        unset($actions['edit']);

        if ($post && $post->ID) {
            // add View link
            $actions['view'] = sprintf('<a href="%s" title="%s">%s</a>',
                get_edit_post_link($post->ID),
                __('View', 'log-emails'), __('View', 'log-emails'));

            // add Delete link
            $actions['delete'] = sprintf('<a href="%s" title="%s" class="submitdelete">%s</a>',
                get_delete_post_link($post->ID, '', true),
                __('Delete', 'log-emails'), __('Delete', 'log-emails'));
        }

        return $actions;
    }

    /**
    * change the list of available bulk actions
    * @param array $actions
    * @return array
    */
    public function adminBulkActionsEdit($actions) {
        unset($actions['edit']);

        return $actions;
    }

    /**
    * filter to add columns to post list
    * @param array $posts_columns
    * @return array
    */
    public function adminManageColumns($posts_columns) {
        $posts_columns['title'] = 'Subject';
        $posts_columns['_log_emails_log_to'] = 'Recipients';

        return $posts_columns;
    }

    /**
    * action to add custom columns to post list
    * @param string $column_name
    * @param int $post_id
    */
    public function adminManageCustomColumn($column_name, $post_id) {
        switch ($column_name) {
            case '_log_emails_log_to':
                $post = get_post($post_id);
                if ($post) {
                    echo htmlspecialchars(get_post_meta($post_id, '_log_emails_log_to', true));
                }
                break;
        }
    }

    /**
    * change the screen layout
    */
    public function adminScreenLayout() {
        // set max / default layout as single column
        add_screen_option('layout_columns', array('max' => 1, 'default' => 1));
    }

    /**
    * drop all the metaboxes and output what we want to show
    */
    public function adminEditAfterTitle($post) {
        global $wp_meta_boxes;

        // remove all meta boxes
        $wp_meta_boxes = array('log_emails_log' => array(
            'advanced' => array(),
            'side' => array(),
            'normal' => array(),
        ));

        // show my admin form
        require LOG_EMAILS_PLUGIN_ROOT . 'views/log-detail.php';
    }

    /**
    * replace Trash bulk actions with Delete
    * NB: WP admin already handles the delete action, it just doesn't expose it as a bulk action
    */
    public function adminPrintFooterScripts() {
        ?>

        <script>
        jQuery("select[name='action'],select[name='action2']").find("option[value='trash']").each(function() {
            this.value = 'delete';
            jQuery(this).text("<?php esc_attr_e('Delete', 'log-emails'); ?>");
        });
        </script>

        <?php
    }

}

You can replace the editor with a read-only meta box.

// use the action to create a place for your meta box
function add_before_editor($post) {
  if ('post' == $post->post_type) {
    remove_post_type_support('post','editor');
    do_meta_boxes('post', 'read_only_content', $post);
  }
}
add_action('edit_form_after_title','add_before_editor');

// add a box the location just created
function read_only_content_box() {
  add_meta_box(
    'read_only_content_box', // id, used as the html id att
    __( 'Post Content (Read Only)' ), // meta box title
    'read_only_cb', // callback function, spits out the content
    'post', // post type or page. This adds to posts only
    'read_only_content', // context, where on the screen
    'low' // priority, where should this go in the context
  );
}
function read_only_cb($post) {
  echo apply_filters('the_content',$post->post_content);
}
add_action( 'add_meta_boxes', 'read_only_content_box' );

I am assuming that you are disabling “save” functionality for this post type. If not– if you need some parts to save just not the main content– you may need to hook into the form processing to prevent someone from hacking around this. As is, the post content is not overwritten but it would not be that hard to hack around that.

To completely remove edit capability, use the capability argument when you register your post type. Set that argument to read_post and the whole type become “read only”. You will also want 'show_in_admin_bar' => false,. Sample argument array:

  $args = array(
    'labels'             => $labels,
    'public'             => true,
    'publicly_queryable' => true,
    'show_ui'            => true,
    'show_in_menu'       => true,
    'query_var'          => true,
    'rewrite'            => array( 'slug' => 'book' ),
    'capability_type'    => 'post',
    'capabilities'       => array('read_post'),
    'show_in_admin_bar'  => false,
    'has_archive'        => true,
    'hierarchical'       => false,
  );