Skip to content

Instantly share code, notes, and snippets.

@ryansechrest
Last active November 28, 2022 01:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryansechrest/9f3bcb0e08a3a59921d21609fa2e566b to your computer and use it in GitHub Desktop.
Save ryansechrest/9f3bcb0e08a3a59921d21609fa2e566b to your computer and use it in GitHub Desktop.
WordPress Plugin Skeleton

WordPress Plugin Skeleton

This plugin demonstrates how to:

  • Wrap plugin code in PHP class
  • Run code when plugin is activated, deactivated, and uninstalled
  • Register custom post type with labels, menu icon, Gutenberg, and featured thumbnail
  • Add custom admin columns and populate them with data
  • Add new meta box, render custom fields, and save custom field values in database
  • Register new settings, add new section to existing settings page, and sanitize values
  • Display posts from custom post type at end of blog post

It accomplishes this with the following functions:

  • register_activation_hook()
  • register_deactivation_hook()
  • register_uninstall_hook()
  • add_action()
  • add_filter()
  • register_post_type()
  • add_meta_box()
  • get_post_meta()
  • update_post_meta()
  • add_settings_section()
  • register_setting()
  • add_settings_field()
  • add_settings_error()
  • esc_html()
  • sanitize_text_field()
  • get_option()
  • get_posts()
  • get_the_post_thumbnail()
  • get_permalink()

And the following actions/filters:

  • init
  • manage_edit-movie_columns
  • manage_movie_posts_custom_column
  • add_meta_boxes
  • save_post
  • admin_init
  • the_content
<?php
/**
* Plugin Name: Movies
* Description: Add movies to WordPress and feature them at the end a post.
* Version: 1.0
* Author: Ryan Sechrest
* Author URI: https://ryansechrest.com/
*/
namespace RyanSechrest\Plugin\Movies;
use WP_Post;
class Main
{
/**
* Prefix
*
* Use prefix with IDs and database keys to prevent collisions.
*/
const PREFIX = 'rsm';
/************************************************************************************/
/**
* Register plugin components
*/
public function __construct()
{
$this->register_main_plugin_hooks();
$this->register_movie_post_type();
$this->register_movie_admin_columns();
$this->register_movie_details_meta_box();
$this->register_movie_admin_settings();
$this->register_movie_display();
}
/**
* Initialize plugin
*
* @return Main
*/
public static function init(): Main
{
return new Main();
}
/************************************************************************************/
/**
* Get option name with plugin prefix
*
* @param string $name
* @return string
*/
private function db_prefix(string $name): string
{
return $this->prefix($name, '_');
}
/**
* Get HTML ID with plugin prefix
*
* @param string $id
* @return string
*/
private function id_prefix(string $id): string
{
return $this->prefix($id, '-');
}
/**
* Get key with plugin prefix
*
* @param string $key
* @param string $separator
* @return string
*/
private function prefix(string $key, string $separator): string
{
return $this::PREFIX . $separator . $key;
}
/************************************************************************************/
/**
* Register main plugin hooks
*
* @uses register_activation_hook()
* @uses register_deactivation_hook()
* @uses register_uninstall_hook()
* @return void
*/
private function register_main_plugin_hooks(): void
{
register_activation_hook(__FILE__, [$this, 'run_when_activated']);
register_deactivation_hook(__FILE__, [$this, 'run_when_deactivated']);
register_uninstall_hook(__FILE__, ['RyanSechrest\Plugin\Movies\Main', 'run_when_uninstalled']);
}
/*----------------------------------------------------------------------------------*/
/**
* Run when plugin is activated
*
* @return void
*/
public function run_when_activated(): void
{
// ...
}
/**
* Run when plugin is deactivated
*
* @return void
*/
public function run_when_deactivated(): void
{
// ...
}
/**
* Run when plugin is uninstalled
*
* @return void
*/
public static function run_when_uninstalled(): void
{
delete_option(self::PREFIX . '_movie_display_total');
delete_option(self::PREFIX . '_movie_display_enabled');
}
/************************************************************************************/
/**
* Register movie post type
*
* @uses add_action()
* @return void
*/
private function register_movie_post_type(): void
{
add_action(
'init',
[$this, 'hook__register_movie_post_type']
);
}
/*----------------------------------------------------------------------------------*/
/**
* Hook: Register movie post type
*
* @uses register_post_type()
* @return void
*/
public function hook__register_movie_post_type(): void
{
register_post_type('movie',
[
'labels' => [
'name' => __('Movies', 'movies'),
'singular_name' => __('Movie', 'movies'),
'add_new' => __('Add Movie', 'movies'),
'add_new_item' => __('Add New Movie', 'movies'),
'edit_item' => __('Edit Movie', 'movies'),
'new_item' => __('New Movie', 'movies'),
'view_item' => __('View Movie', 'movies'),
'search_items' => __('Search Movies', 'movies'),
'not_found' => __('No movies found', 'movies'),
'not_found_in_trash' => __('No movies found in trash', 'movies'),
],
'menu_icon' => 'dashicons-editor-video',
'public' => true,
'show_in_rest' => true,
'supports' => ['title', 'editor', 'thumbnail'],
]
);
}
/************************************************************************************/
/**
* Register movie admin columns
*
* @uses add_filter()
* @uses add_action()
* @return void
*/
private function register_movie_admin_columns(): void
{
add_filter(
'manage_edit-movie_columns',
[$this, 'hook__add_movie_admin_columns']
);
add_action(
'manage_movie_posts_custom_column',
[$this, 'hook__render_movie_admin_column'],
10,
2
);
}
/*----------------------------------------------------------------------------------*/
/**
* Hook: Add movie admin columns
*
* @param array $columns
* @return array
*/
public function hook__add_movie_admin_columns(array $columns): array
{
$new_columns = [];
foreach ($columns as $index => $column) {
$new_columns[$index] = $column;
if ($column === 'Title') {
$new_columns['director'] = __('Director', 'movies');
$new_columns['producer'] = __('Producer', 'movies');
}
}
return $new_columns;
}
/**
* Hook: Render movie admin column
*
* @param string $column
* @param int $id
* @return void
*/
public function hook__render_movie_admin_column(string $column, int $id): void
{
if (!$value = get_post_meta($id, $column, true)) {
return;
}
echo $value;
}
/************************************************************************************/
/**
* Register movie details meta box
*
* @uses add_action()
* @return void
*/
private function register_movie_details_meta_box(): void
{
add_action(
'add_meta_boxes',
[$this, 'hook__add_movie_details_meta_box']
);
add_action(
'save_post',
[$this, 'hook__save_movie_details_fields']
);
}
/*----------------------------------------------------------------------------------*/
/**
* Hook: Add movie details meta box
*
* @uses add_meta_box()
* @return void
*/
public function hook__add_movie_details_meta_box(): void
{
add_meta_box(
$this->id_prefix('movie-details'),
__('Details', 'movies'),
[$this, 'hook__render_movie_details_fields'],
'movie',
'side'
);
}
/**
* Hook: Render movie details fields
*
* @uses get_post_meta()
* @param WP_Post $post
* @return void
*/
public function hook__render_movie_details_fields(WP_Post $post): void
{
$director = get_post_meta($post->ID, 'director', true) ?: '';
$producer = get_post_meta($post->ID, 'producer', true) ?: '';
$output = '<div class="components-base-control__field" style="margin-bottom:15px;">';
$output .= '<label for="director" class="components-base-control__label css-uc0dzf">Director</label>';
$output .= '<input type="text" name="director" value="' . $director . '" id="director" class="components-text-control__input">';
$output .= '</div>';
$output .= '<div class="components-base-control__field">';
$output .= '<label for="producer" class="components-base-control__label css-uc0dzf">Producer</label>';
$output .= '<input type="text" name="producer" value="' . $producer . '" id="producer" class="components-text-control__input">';
$output .= '</div>';
echo $output;
}
/**
* Hook: Save movie details fields
*
* @uses sanitize_text_field()
* @uses update_post_meta()
* @param int $post_id
* @return void
*/
public function hook__save_movie_details_fields(int $post_id): void
{
if (isset($_POST['director'])) {
update_post_meta(
$post_id,
'director',
sanitize_text_field($_POST['director'])
);
}
if (isset($_POST['producer'])) {
update_post_meta(
$post_id,
'producer',
sanitize_text_field($_POST['producer'])
);
}
}
/************************************************************************************/
/**
* Register movie admin settings
*
* @uses add_action()
* @return void
*/
private function register_movie_admin_settings(): void
{
add_action('admin_init', [$this, 'hook__register_movie_admin_section']);
add_action('admin_init', [$this, 'hook__register_movie_admin_settings']);
}
/*----------------------------------------------------------------------------------*/
/**
* Hook: Register movie admin section
*
* @uses add_settings_section()
* @return void
*/
public function hook__register_movie_admin_section(): void
{
add_settings_section(
$this->id_prefix('movies-section'),
'Movies',
function() {
echo '<p>Preferences to enable and customize the Movies plugin.</p>';
},
'reading'
);
}
/**
* Hook: Register movie admin settings
*
* @return void
*/
public function hook__register_movie_admin_settings(): void
{
$this->register_movie_display_enabled_field();
$this->register_movie_display_total_field();
}
/*----------------------------------------------------------------------------------*/
/**
* Register movie display enabled field
*
* @uses register_setting()
* @uses add_settings_field()
* @return void
*/
private function register_movie_display_enabled_field(): void
{
register_setting(
'reading',
$this->db_prefix('movie_display_enabled'),
[
'type' => 'string',
'sanitize_callback' => [$this, 'hook__sanitize_movie_display_enabled_field'],
'default' => 'no',
]
);
add_settings_field(
$this->id_prefix('movie-display-enabled-field'),
'Display movies',
[$this, 'hook__render_movie_display_enabled_field'],
'reading',
$this->id_prefix('movies-section')
);
}
/**
* Hook: Sanitize movie display enabled field
*
* @uses add_settings_error()
* @param string $value
* @return string
*/
public function hook__sanitize_movie_display_enabled_field(string $value): string
{
$valid_values = ['yes', 'no'];
if (!in_array($value, $valid_values)) {
add_settings_error(
'reading',
'movie',
__('Movies: Display movies was set to an invalid value. Using default: Nowhere', 'movies')
);
return 'no';
}
return $value;
}
/**
* Hook: Render movie display enabled field
*
* @uses esc_html()
* @uses get_option()
* @return void
*/
public function hook__render_movie_display_enabled_field(): void
{
$option_name = esc_html($this->db_prefix('movie_display_enabled'));
$value = esc_html(get_option($option_name));
// Start Wrapper
$output = '<fieldset>';
$output .= '<p>';
// Option 1
$output .= '<label>';
$output .= '<input name="' . $option_name . '" type="radio" value="no" ';
if ($value === 'no') {
$output .= 'checked="checked"';
}
$output .= '>';
$output .= ' Nowhere</label>';
// New Line
$output .= '<br>';
// Option 2
$output .= '<label>';
$output .= '<input name="' . $option_name . '" type="radio" value="yes" ';
if ($value === 'yes') {
$output .= 'checked="checked"';
}
$output .= '>';
$output .= ' After a blog post';
$output .= '</label>';
// End Wrapper
$output .= '</p>';
$output .= '</fieldset>';
echo $output;
}
/*----------------------------------------------------------------------------------*/
/**
* Register movie display total field
*
* @uses register_setting()
* @uses add_settings_field()
* @return void
*/
private function register_movie_display_total_field(): void
{
register_setting(
'reading',
$this->db_prefix('movie_display_total'),
[
'type' => 'integer',
'sanitize_callback' => [$this, 'hook__sanitize_movie_display_total_field'],
'default' => 1,
]
);
add_settings_field(
$this->id_prefix('movie-display-total-field'),
'Display up to',
[$this, 'hook__render_movie_display_total_field'],
'reading',
$this->id_prefix('movies-section')
);
}
/**
* Hook: Sanitize movie display total field
*
* @uses add_settings_error()
* @param string $value
* @return string
*/
public function hook__sanitize_movie_display_total_field(string $value): string
{
$value = (int) $value;
if ($value < 1 || $value > 5) {
add_settings_error(
'reading',
'movie',
__('Movies: Display up to must be between 1-5. Using default: 1', 'movies')
);
return 1;
}
return $value;
}
/**
* Hook: Render movie display total field
*
* @uses esc_html()
* @uses get_option()
* @return void
*/
public function hook__render_movie_display_total_field(): void
{
$option_name = esc_html($this->db_prefix('movie_display_total'));
$value = esc_html(get_option($option_name));
$output = '<fieldset>';
$output .= '<p>';
$output .= '<input name="' . $option_name . '" type="number" value="';
$output .= $value;
$output .= '" step="1" min="1" max="5" id="movie-display-total" class="small-text"> movie(s)';
$output .= '</p>';
$output .= '</fieldset>';
echo $output;
}
/************************************************************************************/
/**
* Register movie display
*
* @uses add_filter()
* @return void
*/
private function register_movie_display(): void
{
add_filter('the_content', [$this, 'hook__render_movies_end_of_post']);
}
/*----------------------------------------------------------------------------------*/
/**
* Hook: Render movies end of post
*
* @uses get_option()
* @uses get_permalink()
* @uses get_posts()
* @uses get_the_post_thumbnail()
* @param string $content
* @return string
*/
public function hook__render_movies_end_of_post(string $content): string
{
$enabled = get_option($this->db_prefix('movie_display_enabled'));
if ($enabled !== 'yes') {
return $content;
}
$total = get_option($this->db_prefix('movie_display_total'));
$movies = get_posts([
'post_type' => 'movie',
'post_status' => 'publish',
'posts_per_page' => $total,
'orderby' => 'date',
'order' => 'DESC',
]);
if (count($movies) === 0) {
return $content;
}
$content .= '<p>Here are our latest movies:</p>';
$content .= '<div style="display:flex;gap:15px;">';
/** @var WP_Post $movie */
foreach ($movies as $movie) {
$thumbnail = get_the_post_thumbnail($movie, [200, 300]);
$url = get_permalink($movie);
$content .= '<div>';
$content .= '<div><a href="' . $url . '">' . $thumbnail . '</a></div>';
$content .= '<div><a href="' . $url . '">' . $movie->post_title . '</a></div>';
$content .= '</div>';
}
$content .= '</div>';
return $content;
}
}
Main::init();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment