Skip to content

Instantly share code, notes, and snippets.

@felixarntz
Last active August 30, 2016 09:46
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 felixarntz/8707dd22e8c28eb28e1f56d006a356ab to your computer and use it in GitHub Desktop.
Save felixarntz/8707dd22e8c28eb28e1f56d006a356ab to your computer and use it in GitHub Desktop.
Plugin Main File Boilerplate
<?php
/*
Plugin Name: My Plugin
Plugin URI: https://wordpress.org/plugins/my-plugin
Description: Description of my super awesome plugin.
Version: 1.0.0
Author: My Name
Author URI: https://my-website.net
License: GNU General Public License v3
License URI: http://www.gnu.org/licenses/gpl-3.0.html
Text Domain: my-plugin
Tags:
*/
/**
* Plugin initialization file
*
* @package MyPlugin
* @since 1.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Main class for My Plugin.
*
* Takes care of initializing the plugin.
*
* @since 1.0.0
*/
class My_Plugin {
/**
* The plugin version.
*
* @since 1.0.0
* @var string
*/
const VERSION = '1.0.0';
/**
* Path to the plugin's main file.
*
* @since 1.0.0
* @access private
* @var string
*/
private $main_file;
/**
* Relative base path to the other files of this plugin.
*
* @since 1.0.0
* @access private
* @var string
*/
private $basedir_relative;
/**
* Singleton to ensure that My Plugin is only loaded once.
*
* @since 1.0.0
* @access public
* @static
*
* @see my_plugin()
*
* @return My_Plugin|WP_Error The My Plugin instance or an error object if it failed to initialize.
*/
public static function instance() {
static $instance = null;
if ( null === $instance ) {
if ( version_compare( get_bloginfo( 'version' ), '4.6', '<' ) ) {
load_plugin_textdomain( 'my-plugin' );
}
$status = self::check_requirements();
if ( is_wp_error( $status ) ) {
$instance = $status;
add_action( 'admin_notices', array( __CLASS__, 'admin_error_notice' ) );
} else {
list( $is_mu_plugin, $basedir_relative ) = self::detect_plugin_mode( __FILE__ );
$instance = new self( __FILE__, $basedir_relative );
if ( $is_mu_plugin ) {
add_action( 'muplugins_loaded', array( $instance, 'initialize' ) );
} else {
add_action( 'plugins_loaded', array( $instance, 'initialize' ) );
}
//TODO: If you don't require specific parts (installation, activation, deactivation, uninstallation),
// remove the related hook and methods (see further below).
add_action( 'admin_init', array( __CLASS__, 'installation_handler' ), 10, 0 );
register_activation_hook( __FILE__, array( __CLASS__, 'activation_handler' ) );
register_deactivation_hook( __FILE__, array( __CLASS__, 'deactivation_handler' ) );
register_uninstall_hook( __FILE__, array( __CLASS__, 'uninstallation_handler' ) );
//TODO: If you don't use installation/uninstallation, remove this hook and the related method
// (see further below).
add_filter( 'populate_network_meta', array( __CLASS__, 'migrate_versions_to_network' ), 10, 1 );
}
}
return $instance;
}
/**
* Installs the plugin on the current site if it is not installed.
*
* If installed version is outdated, it will be updated.
*
* @since 1.0.0
* @access public
* @static
*/
public static function installation_handler() {
$current_version = false;
if ( is_multisite() ) {
$site_id = get_current_blog_id();
$versions = get_network_option( null, 'my_plugin_version', array() );
if ( isset( $versions[ $site_id ] ) ) {
$current_version = $versions[ $site_id ];
}
} else {
$current_version = get_option( 'my_plugin_version' );
}
if ( ! $current_version || version_compare( $current_version, self::VERSION, '<' ) ) {
self::install( $current_version );
if ( is_multisite() ) {
$versions[ $site_id ] = self::VERSION;
update_network_option( null, 'my_plugin_version', $versions );
} else {
update_option( 'my_plugin_version', self::VERSION );
}
}
}
/**
* Activates the plugin in a multisite-compatible way.
*
* If not installed, it will be installed as well.
*
* @since 1.0.0
* @access public
* @static
*
* @param bool $network_wide Whether the plugin is being activated network-wide.
*/
public static function activation_handler( $network_wide ) {
if ( $network_wide ) {
if ( wp_is_large_network( 'sites' ) ) {
return;
}
$site_ids = array();
if ( version_compare( get_bloginfo( 'version' ), '4.6', '<' ) ) {
$site_ids = wp_list_pluck( wp_get_sites(), 'blog_id' );
} else {
$site_ids = get_sites( array(
'fields' => 'ids',
'network_id' => get_current_network_id(),
) );
}
foreach ( $site_ids as $site_id ) {
switch_to_blog( $site_id );
self::activate();
restore_current_blog();
}
} else {
self::activate();
}
}
/**
* Deactivates the plugin in a multisite-compatible way.
*
* @since 1.0.0
* @access public
* @static
*
* @param bool $network_wide Whether the plugin is being deactivated network-wide.
*/
public static function deactivation_handler( $network_wide ) {
if ( $network_wide ) {
if ( wp_is_large_network( 'sites' ) ) {
return;
}
$site_ids = array();
if ( version_compare( get_bloginfo( 'version' ), '4.6', '<' ) ) {
$site_ids = wp_list_pluck( wp_get_sites(), 'blog_id' );
} else {
$site_ids = get_sites( array(
'fields' => 'ids',
'network_id' => get_current_network_id(),
) );
}
foreach ( $site_ids as $site_id ) {
switch_to_blog( $site_id );
self::deactivate();
restore_current_blog();
}
} else {
self::deactivate();
}
}
/**
* Uninstalls the plugin in a multisite-compatible way.
*
* @since 1.0.0
* @access public
* @static
*/
public static function uninstallation_handler() {
global $wpdb;
if ( is_multisite() ) {
$network_ids = array();
if ( version_compare( get_bloginfo( 'version' ), '4.6', '<' ) ) {
$network_ids = $wpdb->get_col( "SELECT id FROM $wpdb->site" );
} else {
$network_ids = get_networks( array(
'fields' => 'ids',
) );
}
foreach ( $network_ids as $network_id ) {
$versions = get_network_option( $network_id, 'my_plugin_version', array() );
$site_ids = array_keys( $versions );
if ( count( $site_ids ) > 10000 ) {
continue;
}
foreach ( $site_ids as $site_id ) {
switch_to_blog( $site_id );
self::uninstall();
restore_current_blog();
}
delete_network_option( $network_id, 'my_plugin_version' );
}
} else {
if ( get_option( 'my_plugin_version' ) ) {
self::uninstall();
delete_option( 'my_plugin_version' );
}
}
}
/**
* Prints an admin error notice if the plugin cannot be initialized.
*
* @since 1.0.0
* @access public
* @static
*
* @see My_Plugin::instance()
*/
public static function admin_error_notice() {
$error = self::instance();
if ( ! is_wp_error( $error ) ) {
return;
}
?>
<div class="notice notice-error">
<p><?php echo $error->get_error_message(); ?></p>
</div>
<?php
}
/**
* Migrates the plugin version to use network options when the setup is switched to a Multisite.
*
* @since 1.0.0
* @access public
* @static
*
* @param array $network_options The network options to store in the network.
* @return array The adjusted network options.
*/
public static function migrate_versions_to_network( $network_options ) {
// Only migrate when switching from single to multisite.
if ( is_multisite() ) {
return $network_options;
}
$value = get_option( 'my_plugin_version' );
if ( false === $value ) {
return $network_options;
}
$network_options['my_plugin_version'] = array( 1 => $value );
delete_option( 'my_plugin_version' );
return $network_options;
}
/**
* Checks whether My Plugin can run on this setup.
*
* @since 1.0.0
* @access private
* @static
*
* @return bool|WP_Error True if My Plugin can initialize or an error object otherwise.
*/
private static function check_requirements() {
//TODO: Adjust these to what the plugin needs.
$min_php_version = '5.2';
$min_wp_version = '4.4';
if ( version_compare( phpversion(), $min_php_version, '<' ) ) {
return new WP_Error( 'my_plugin_outdated_wordpress', sprintf( __( 'My Plugin cannot be initialized because your setup uses a PHP version older than %s.', 'my-plugin' ), $min_php_version ) );
}
if ( version_compare( get_bloginfo( 'version' ), $min_wp_version, '<' ) ) {
return new WP_Error( 'my_plugin_outdated_wordpress', sprintf( __( 'My Plugin cannot be initialized because your setup uses a WordPress version older than %s.', 'my-plugin' ), $min_wp_version ) );
}
return true;
}
/**
* Installs the plugin on a site.
*
* @since 1.0.0
* @access private
* @static
*
* @param string $current_version The version of the plugin that is currently installed.
*/
private static function install( $current_version ) {
//TODO: Include steps to install the plugin on a single site.
}
/**
* Activates the plugin on a site.
*
* @since 1.0.0
* @access private
* @static
*/
private static function activate() {
//TODO: Include steps to activate the plugin on a single site.
}
/**
* Deactivates the plugin on a site.
*
* @since 1.0.0
* @access private
* @static
*/
private static function deactivate() {
//TODO: Include steps to deactivate the plugin on a single site.
}
/**
* Uninstalls the plugin on a site.
*
* @since 1.0.0
* @access private
* @static
*/
private static function uninstall() {
//TODO: Include steps to uninstall the plugin from a single site.
}
/**
* Detects whether My Plugin is used as a plugin or must-use plugin.
*
* @since 1.0.0
* @access private
* @static
*
* @param string $main_file Path to the plugin's main file.
* @return array An array with the first element being a boolean whether this is a must-use plugin
* or not and the second element being a string that contains the relative base path
* to the other files of this plugin.
*/
private static function detect_plugin_mode( $main_file ) {
$file = wp_normalize_path( $main_file );
$mu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR );
if ( preg_match( '#^' . preg_quote( $mu_plugin_dir, '#' ) . '/#', $file ) ) {
$basedir_relative = '';
if ( file_exists( $mu_plugin_dir . '/my-plugin.php' ) ) {
$basedir_relative = 'my-plugin/';
}
return array( true, $basedir_relative );
}
return array( false, '' );
}
/**
* Constructor.
*
* It is private to prevent duplicate instantiation.
*
* @since 1.0.0
* @access private
*
* @see My_Plugin::instance()
* @see my_plugin()
*
* @param string $main file Path to the plugin's main file.
* @param string $basedir_relative The relative base path to the other files of this plugin.
*/
private function __construct( $main_file, $basedir_relative = '' ) {
$this->main_file = $main_file;
$this->basedir_relative = $basedir_relative;
$this->load();
}
/**
* Dummy magic method to prevent My Plugin from being cloned.
*
* @since 1.0.0
* @access public
*/
public function __clone() {
_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'my-plugin' ), '1.0.0' );
}
/**
* Dummy magic method to prevent My Plugin from being unserialized.
*
* @since 1.0.0
* @access public
*/
public function __wakeup() {
_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'my-plugin' ), '1.0.0' );
}
/**
* Returns class instances for the plugin.
*
* This magic method allows you to call methods with the name of a class property, which will
* then return the respective instance.
*
* @since 1.0.0
* @access public
*
* @see My_Plugin::load()
*
* @param string $method_name Name of the method to call.
* @param array $args Method arguments.
* @return object|null Either the class instance denoted by the method name, or null if it doesn't exist.
*/
public function __call( $method_name, $args ) {
if ( isset( $this->$method_name ) && is_object( $this->$method_name ) ) {
return $this->$method_name;
}
return null;
}
/**
* Returns the full path to a relative path for a plugin file or directory.
*
* @since 1.0.0
* @access public
*
* @param string $rel_path Relative path.
* @return string Full path.
*/
public function path( $rel_path ) {
return plugin_dir_path( $this->main_file ) . $this->basedir_relative . ltrim( $rel_path, '/' );
}
/**
* Returns the full URL to a relative path for a plugin file or directory.
*
* @since 1.0.0
* @access public
*
* @param string $rel_path Relative path.
* @return string Full URL.
*/
public function url( $rel_path ) {
return plugin_dir_url( $this->main_file ) . $this->basedir_relative . ltrim( $rel_path, '/' );
}
/**
* Gets the whole thing running.
*
* @since 1.0.0
* @access public
*/
public function initialize() {
//TODO: Initialize your plugin hooks here.
/**
* Fires after the plugin has initialized.
*
* @since 1.0.0
*
* @param My_Plugin $my_plugin The My Plugin instance.
*/
do_action( 'my_plugin_init', $this );
}
/**
* Autoloader method.
*
* @since 1.0.0
* @access public
*
* @param string $class The class to load.
*/
public function autoload( $class ) {
//TODO: Implement an autoload method here that fits your class naming structure.
}
/**
* Loads the plugin.
*
* If autoloading is not available, all files are loaded at once.
*
* @since 1.0.0
* @access public
*/
private function load() {
if ( function_exists( 'spl_autoload_register' ) ) {
spl_autoload_register( array( $this, 'autoload' ) );
} else {
//TODO: Load all plugin files manually.
}
//TODO: Instantiate other plugin classes and store them as properties in this class.
/**
* Fires after the plugin has loaded.
*
* @since 1.0.0
*
* @param My_Plugin $my_plugin The My Plugin instance.
*/
do_action( 'my_plugin_loaded', $this );
}
}
/**
* The main function to return the My Plugin instance.
*
* Any extension can use this function to access the main plugin object or to simply check
* whether the plugin is active and running. Example:
*
* `if ( function_exists( 'my_plugin' ) && my_plugin() ) {
* // do custom extension stuff
* }`
*
* @since 1.0.0
*
* @return My_Plugin|null The My Plugin instance, or null on failure.
*/
function my_plugin() {
$my_plugin = My_Plugin::instance();
if ( is_wp_error( $my_plugin ) ) {
return null;
}
return $my_plugin;
}
my_plugin();
@felixarntz
Copy link
Author

felixarntz commented Aug 29, 2016

Overview

This is the boilerplate I usually use for my plugin main files.

Features

  • provides full support for Multisite & Multinetwork
  • makes the plugin compatible with usage as a must-use plugin
  • makes implementing installation/activation/deactivation/uninstallation methods simple
  • fails gracefully if a setup does not meet the plugin's requirements
  • includes methods to easily retrieve paths / URLs to specific plugin files
  • supports autoloading

Requirements

The boilerplate itself requires at least WordPress version 4.4 (for some Multisite functionality), and of course PHP 5.2 like WordPress does. That means that your plugin must also require at least these two things - this is also built into the class by default.

Getting Started

The first thing you need to do is some search and replace in the file:

  • replace My Plugin with your plugin's name
  • replace my-plugin with your plugin's slug
  • replace my_plugin with your plugin's prefix
  • replace My_Plugin with your plugin's main class name
  • replace MyPlugin with your plugin's package name

You probably also wanna replace the information in the plugin header comment.

After that, run through all the comments marked with //TODO, and do what is described there. You can remove the comments afterwards.

Maintaining the plugin

Before releasing a new version, make sure to change the VERSION constant in the class to the new version number.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment