Bootstrapping WordPress pluginů

Dnes bych vám chtěl popsat, jak řeším zavádění (bootstrapping mých WordPress pluginů. Proč používat bootstrapping? Obyčejně proto, že váš plugin něco vyžaduje ke svému běhu – buď specifickou vlastnost/verzi PHP nebo WP… prostě je lepší ukázat zprávu „Funkce curl_init není definována…“ nebo „Tento plugin vyžaduje aktivovaný WooCommerce plugin…“ namísto nějaké chyby či (což je nejhůře) ničeho.

Rozlišuji dva typy chyb, které se můžou přihodit, když spouštíte váš plugin na cizím serveru:

  • systémová úroveň – je buď špatná verze PHP nebo chybí nějaké vyžadované rozšíření PHP, nebo je špatná verze WordPressu nebo chybí nějaký plugin atd.
  • aplikační úroveň – nemůžeme vytvořit dočasné adresáře, databázi nebo podobné chyby.

Because of core level I have bootstrap file which check these basic requirements and raise admin notices if something is wrong. This file is always named as the plugin self (e.g. for plugin Debug Log Viewer I have odwp-debug_log folder and odwp-debug_log.php main PHP file).

Nejprve si ukážeme základní strukturu pluginu:

$cd wp-content/plugins/odwp-debug_log
$ls
...
src/DL_Plugin.php
odwp-debug_log.php
...

Hlavní soubor odwp-debug_log.php obsahuje vše, co je potřeba k otestování prostředí, ve kterém plugin běží a zobrazení chybových zpráv, pokud je zapotřebí – tato akce začíná na řádku 191, kde je definovaná proměnná $odwpdl_errs. Jak můžete vidět, funkce odwpdl_check_requirements, vyžaduje pole závislostí pluginu jako parametr a toto pole může obsahovat definici závislostí jak pro PHP tak i pro WordPress. Funkce samotná vrací pole s chybovými hláškami, které byly při kontrole prostředí nalezeny – prázdné pole znamená, že vše je v pořádku:

<?php
/**
* Plugin Name: Debug Log Viewer
* Plugin URI: https://github.com/ondrejd/odwp-debug_log
* Description: Small <a href="https://wordpress.org/" target="blank">WordPress</a> plugin especially for developers that allows better work with <code>debug.log</code> file.
* Version: 1.0.0
* Author: Ondrej Donek
* Author URI: https://ondrejd.com/
* License: GPLv3
* Requires at least: 4.7
* Tested up to: 4.8.1
* Tags: debug,log,development
* Donate link: https://www.paypal.me/ondrejd
*
* Text Domain: odwp-debug_log
* Domain Path: /languages/
*
* @author Ondrej Donek <ondrejd@gmail.com>
* @link https://github.com/ondrejd/odwp-debug_log for the canonical source repository
* @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License 3.0
* @package odwp-debug_log
* @since 1.0.0
*/
/**
* This file is just a bootstrap. It checks if requirements of plugins are met
* and accordingly either initializes the plugin or halts the process.
*
* Requirements can be specified for PHP and the WordPress self - version
* for both, required extensions for PHP and requireds plugins for WP.
*
* If you are using copy of original file in your plugin you shoud change
* prefix "odwpdl" and name "odwp-debug_log" to your own values.
*
* To set the requirements go down to line 133 and define array that
* is used as a parameter for `odwpdl_check_requirements` function.
*/
if( ! defined( 'ABSPATH' ) ) {
exit;
}
// Some widely used constants
defined( 'DL_SLUG' ) || define( 'DL_SLUG', 'odwpdl' );
defined( 'DL_NAME' ) || define( 'DL_NAME', 'odwp-debug_log' );
defined( 'DL_PATH' ) || define( 'DL_PATH', dirname( __FILE__ ) . '/' );
defined( 'DL_FILE' ) || define( 'DL_FILE', __FILE__ );
defined( 'DL_LOG' ) || define( 'DL_LOG', WP_CONTENT_DIR . '/debug.log' );
if( ! function_exists( 'odwpdl_check_requirements' ) ) :
/**
* Checks requirements of our plugin.
* @param array $requirements
* @return array
* @since 1.0.0
*/
function odwpdl_check_requirements( array $requirements ) {
global $wp_version;
// Initialize locales
load_plugin_textdomain( DL_SLUG, false, dirname( __FILE__ ) . '/languages' );
/**
* @var array Hold requirement errors
*/
$errors = [];
// Check PHP version
if( ! empty( $requirements['php']['version'] ) ) {
if( version_compare( phpversion(), $requirements['php']['version'], '<' ) ) {
$errors[] = sprintf(
__( 'PHP interpreter does not meet version requirements (required at least <b>%s</b>)!', DL_SLUG ),
$requirements['php']['version']
);
}
}
// Check PHP extensions
if( count( $requirements['php']['extensions'] ) > 0 ) {
foreach( $requirements['php']['extensions'] as $req_ext ) {
if( ! extension_loaded( $req_ext ) ) {
$errors[] = sprintf(
__( 'Is required PHP extension <b>%s</b> but it is not installed!', DL_SLUG ),
$req_ext
);
}
}
}
// Check WP version
if( ! empty( $requirements['wp']['version'] ) ) {
if( version_compare( $wp_version, $requirements['wp']['version'], '<' ) ) {
$errors[] = sprintf(
__( 'This plugin requires higher version of <b>WordPress</b> (at least <b>%s</b>)!', DL_SLUG ),
$requirements['wp']['version']
);
}
}
// Check WP plugins
if( count( $requirements['wp']['plugins'] ) > 0 ) {
$active_plugins = (array) get_option( 'active_plugins', [] );
foreach( $requirements['wp']['plugins'] as $req_plugin ) {
if( ! in_array( $req_plugin, $active_plugins ) ) {
$errors[] = sprintf(
__( 'Is required plugin <b>%s</b> but it is not installed!', DL_SLUG ),
$req_plugin
);
}
}
}
return $errors;
}
endif;
if( ! function_exists( 'odwpdl_deactivate_raw' ) ) :
/**
* Deactivate plugin by the raw way.
* @return void
* @since 1.0.0
*/
function odwpdl_deactivate_raw() {
$active_plugins = get_option( 'active_plugins' );
$out = [];
foreach( $active_plugins as $key => $val ) {
if( $val != DL_NAME . '/' . DL_NAME . '.php' ) {
$out[$key] = $val;
}
}
update_option( 'active_plugins', $out );
}
endif;
if( ! function_exists( 'odwpdl_error_log' ) ) :
/**
* @internal Write message to the `wp-content/debug.log` file.
* @param string $message
* @param integer $message_type (Optional.)
* @param string $destination (Optional.)
* @param string $extra_headers (Optional.)
* @return void
* @since 1.0.0
*/
function odwpdl_error_log( string $message, int $message_type = 0, string $destination = null, string $extra_headers = '' ) {
if( ! file_exists( DL_LOG ) || ! is_writable( DL_LOG ) ) {
return;
}
$record = '[' . date( 'd-M-Y H:i:s', time() ) . ' UTC] ' . $message;
file_put_contents( DL_LOG, PHP_EOL . $record, FILE_APPEND );
}
endif;
if( ! function_exists( 'odwpdl_write_log' ) ) :
/**
* Write record to the `wp-content/debug.log` file.
* @param mixed $log
* @return void
* @since 1.0.0
*/
function odwpdl_write_log( $log ) {
if( is_array( $log ) || is_object( $log ) ) {
odwpdl_error_log( print_r( $log, true ) );
} else {
odwpdl_error_log( $log );
}
}
endif;
if( ! function_exists( 'readonly' ) ) :
/**
* Prints HTML readonly attribute. It's an addition to WP original
* functions {@see disabled()} and {@see checked()}.
* @param mixed $value
* @param mixed $current (Optional.) Defaultly TRUE.
* @return string
* @since 1.0.0
*/
function readonly( $current, $value = true ) {
if( $current == $value ) {
echo ' readonly';
}
}
endif;
/**
* Errors from the requirements check
* @var array
*/
$odwpdl_errs = odwpdl_check_requirements( [
'php' => [
// Enter minimum PHP version you needs.
'version' => '5.6',
// Enter extensions that your plugin needs
'extensions' => [
//'gd',
],
],
'wp' => [
// Enter minimum WP version you need
'version' => '4.7',
// Enter WP plugins that your plugin needs
'plugins' => [
//'woocommerce/woocommerce.php',
],
],
] );
// Check if requirements are met or not
if( count( $odwpdl_errs ) > 0 ) {
// Requirements are not met
odwpdl_deactivate_raw();
// In administration print errors
if( is_admin() ) {
$err_head = __( '<b>Debug Log Viewer</b>: ', DL_SLUG );
foreach( $odwpdl_errs as $err ) {
printf( '<div class="error"><p>%s</p></div>', $err_head . $err );
}
}
} else {
// Requirements are met so initialize the plugin.
include( DL_PATH . 'src/DL_Screen_Prototype.php' );
include( DL_PATH . 'src/DL_Plugin.php' );
DL_Plugin::initialize();
}
view raw odwp-debug_log.php hosted with ❤ by GitHub

Po tomto souboru přichází na řadu hlavní PHP soubor pluginu – v tomto případě soubor src/DL_Plugin.php. Tento soubor obsahuje veškerou hlavní funkčnost a také (pokud jsou vyžadovány) testy prostředí ve kterém plugin běží – ty pak zobrazují upozornění pro administrátory, že některé z podmínek pro správný běh pluginu nebyly dodrženy.

<?php
/**
* @author Ondrej Donek <ondrejd@gmail.com>
* @link https://github.com/ondrejd/odwp-debug_log for the canonical source repository
* @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License 3.0
* @package odwp-debug_log
* @since 1.0.0
*/
if( ! defined( 'ABSPATH' ) ) {
exit;
}
if( ! class_exists( 'DL_Plugin' ) ) :
/**
* Main class.
* @since 1.0.0
*/
class DL_Plugin {
// ...
/**
* Hook for "admin_init" action.
* @return void
* @since 1.0.0
*/
public static function admin_init() {
// ...
// Check environment
self::check_environment();
// ...
}
// ...
/**
* Checks environment we're running and prints admin messages if needed.
* @return void
* @since 1.0.0
*/
public static function check_environment() {
if( ! file_exists( DL_LOG ) || ! is_writable( DL_LOG ) ) {
add_action( 'admin_notices', function() {
$msg = sprintf(
__( '<strong>Debug Log Viewer</strong>: Soubor (<code>%s</code>) k zapisu ladicich informaci neni vytvoren nebo neni zapisovatelny. Pro vice informací prejdete na <a href="%s">nastaveni tohoto pluginu</a>.', DL_SLUG ),
DL_LOG,
admin_url( 'options-general.php?page=' . DL_SLUG . '-plugin_options' )
);
DL_Plugin::print_admin_notice( $msg );
} );
}
/**
* @var string $err_msg Error message about setting WP_DEBUG and WP_DEBUG_LOG constants.
*/
$err_msg = sprintf(
__( 'Pro umozneni zapisu ladicich informaci do logovaciho souboru (<code>%s</code>) musi být konstanty <code>%s</code> a <code>%s</code> nastaveny na hodnotu <code>TRUE</code>. Pro vice informaci prejdete na <a href="%s">nastaveni tohoto pluginu</a>.', DL_SLUG ),
DL_LOG,
'WP_DEBUG',
'WP_DEBUG_LOG',
admin_url( 'options-general.php?page=' . DL_SLUG . '-plugin_options' )
);
if( ! defined( 'WP_DEBUG' ) || ! defined( 'WP_DEBUG_LOG' ) ) {
add_action( 'admin_notices', function() use ( $err_msg ) {
self::print_admin_notice( $err_msg, 'error' );
} );
}
if( ! defined( 'WP_DEBUG' ) || ! defined( 'WP_DEBUG_LOG' ) ) {
add_action( 'admin_notices', function() use ( $err_msg ) {
self::print_admin_notice( $err_msg, 'error' );
} );
}
}
// ...
/**
* @internal Prints error message in correct WP amin style.
* @param string $msg Error message.
* @param string $type (Optional.) One of ['error','info','success','warning'].
* @param boolean $dismissible (Optional.) Is notice dismissible?
* @return void
* @since 1.0.0
*/
public static function print_admin_notice( $msg, $type = 'info', $dismissible = true ) {
$class = 'notice';
if( in_array( $type, ['error','info','success','warning'] ) ) {
$class .= ' notice-' . $type;
} else {
$class .= ' notice-info';
}
if( $dismissible === true) {
$class .= ' s-dismissible';
}
printf( '<div class="%s"><p>%s</p></div>', $class, $msg );
}
// ...
}
endif;
view raw DL_Plugin.php hosted with ❤ by GitHub

Další použití tohoto řešení si můžete vidět i v mých jiných pluginech:

Napsat komentář