插件基础知识

插件基础知识

入门

最简单的来说,WordPress 插件是一个带有 WordPress 插件标头注释的 PHP 文件。强烈建议您创建一个目录来保存您的插件,以便将所有插件的文件整齐地组织在一个位置。

要开始创建新插件,请按照以下步骤操作。

导航到 WordPress 安装的 wp-content目录。
打开插件目录。
创建一个新目录并以插件命名(例如plugin-name)。
打开新插件的目录。
创建一个新的 PHP 文件(最好以您的插件命名该文件,例如plugin-name.php)。
该过程在 Unix 命令行上如下所示:

wordpress $ cd wp-content
wp-content $ cd plugins
plugins $ mkdir plugin-name
plugins $ cd plugin-name
plugin-name $ vi plugin-name.php

在上面的示例中,vi是文本编辑器的名称。使用您觉得舒服的编辑器。

现在您正在编辑新插件的 PHP 文件,您需要添加插件标头注释。这是一个特殊格式的 PHP 块注释,其中包含有关插件的元数据,例如名称、作者、版本、许可证等。插件标头注释必须符合标头要求,并且至少包含插件 名称插入。

插件文件夹中只有一个文件应具有标题注释 - 如果插件有多个 PHP 文件,则只有其中一个文件应具有标题注释。

保存文件后,您应该能够在 WordPress 站点中看到列出的插件。登录到您的 WordPress 站点,然后单击WordPress 管理员左侧导航窗格中的插件。此页面显示您的 WordPress 网站拥有的所有插件的列表。您的新插件现在应该在该列表中!

挂钩:操作和过滤器

WordPress 挂钩允许您在特定点接入 WordPress 来更改 WordPress 的行为方式,而无需编辑任何核心文件。

WordPress 中有两种类型的挂钩:操作过滤器。操作允许您添加或更改 WordPress 功能,而过滤器允许您在加载并向网站用户显示内容时更改内容。

Hooks 不仅仅适用于插件开发者;它也适用于插件开发者。WordPress 核心本身广泛使用 hooks 来提供默认功能。其他挂钩是未使用的占位符,当您需要更改 WordPress 的工作方式时,可以轻松使用它们。这就是 WordPress 如此灵活的原因。

基本挂钩

创建插件时需要的 3 个基本钩子是register_activation_hook()register_deactivation_hook()register_uninstall_hook()

当您激活插件时,register_activation_hook就会运行。您可以使用它来提供设置插件的功能 - 例如,在表中创建一些默认设置。options

当您停用插件时,register_deactivation_hook就会运行。您可以使用它来提供一个函数来清除插件存储的任何临时数据。

这些register_uninstall_hook方法用于在使用 WordPress 管理员删除插件后进行清理。您可以使用它来删除插件创建的所有数据,例如添加到表中的任何选项options

添加挂钩

您可以使用do_action()添加您自己的自定义挂钩,这将使开发人员能够通过挂钩传递函数来扩展您的插件。

拆除挂钩

您还可以使用调用remove_action() 来删除之前定义的函数。例如,如果您的插件是另一个插件的附加组件,则可以将remove_action() 与上一个插件通过add_action()添加的相同函数回调一起使用。在这些情况下,操作的优先级很重要,因为remove_action() 需要在初始add_action()之后运行。

从钩子中删除操作以及更改优先级时应该小心,因为很难看出这些更改将如何影响与同一钩子的其他交互。我们强烈建议经常进行测试。

您可以在本手册的“钩子”部分了解有关创建钩子以及与它们交互的更多信息。

 

WordPress API

您是否知道 WordPress 提供了许多应用程序编程接口 (API)?这些 API 可以极大地简化您需要在插件中编写的代码。您不想重新发明轮子,尤其是当这么多人为您完成了大量工作和测试时。

最常见的是Options API,它可以轻松地将数据存储在插件的数据库中。如果您正在考虑在插件中使用cURL ,那么您可能会对HTTP API感兴趣。

由于我们谈论的是插件,因此您需要研究Plugin API。它具有多种功能可以帮助您开发插件。

 

WordPress 如何加载插件

当 WordPress 在 WordPress 管理员的插件页面上加载已安装插件的列表时,它会搜索该plugins文件夹(及其子文件夹)以查找带有 WordPress 插件标头注释的 PHP 文件。如果您的整个插件仅包含一个 PHP 文件(例如Hello Dolly ),则该文件可以直接位于plugins文件夹的根目录内。但更常见的是,插件文件将驻留在自己的文件夹中,以插件命名。

 

分享你的插件

有时您创建的插件仅适用于您的网站。但许多人喜欢与 WordPress 社区的其他成员分享他们的插件。在共享您的插件之前,您需要做的一件事是选择许可证。这可以让您的插件的用户知道他们如何被允许使用您的代码。为了保持与 WordPress 核心的兼容性,建议您选择适用于 GNU 通用公共许可证 (GPLv2+) 的许可证。

标题要求

如入门中所述,主 PHP 文件应包含标头注释,告诉 WordPress 该文件是一个插件并提供有关该插件的信息。

最小字段

标头注释至少必须包含插件名称:

/*
 * Plugin Name: YOUR PLUGIN NAME
 */

标头字段

可用的标头字段:

带有标题注释的有效 PHP 文件可能如下所示:

/*
 * Plugin Name:       My Basics Plugin
 * Plugin URI:        https://example.com/plugins/the-basics/
 * Description:       Handle the basics with this plugin.
 * Version:           1.10.3
 * Requires at least: 5.2
 * Requires PHP:      7.2
 * Author:            John Smith
 * Author URI:        https://author.example.com/
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Update URI:        https://example.com/my-plugin/
 * Text Domain:       my-basics-plugin
 * Domain Path:       /languages
 */

这是另一个允许文件级 PHPDoc DocBlock 以及 WordPress 插件文件头的示例:

/**
 * Plugin Name
 *
 * @package           PluginPackage
 * @author            Your Name
 * @copyright         2019 Your Name or Company Name
 * @license           GPL-2.0-or-later
 *
 * @wordpress-plugin
 * Plugin Name:       Plugin Name
 * Plugin URI:        https://example.com/plugin-name
 * Description:       Description of the plugin.
 * Version:           1.0.0
 * Requires at least: 5.2
 * Requires PHP:      7.2
 * Author:            Your Name
 * Author URI:        https://example.com
 * Text Domain:       plugin-slug
 * License:           GPL v2 or later
 * License URI:       http://www.gnu.org/licenses/gpl-2.0.txt
 * Update URI:        https://example.com/my-plugin/
 */

笔记

为项目分配版本号时,请记住 WordPress 使用 PHP version_compare() 函数来比较插件版本号。因此,在发布新版本的插件之前,您应该确保该 PHP 函数认为新版本比旧版本“更好”。例如,1.02 实际上大于 1.1。

 

 

包括软件许可证

大多数 WordPress 插件都是在GPL下发布的,这与WordPress 本身使用的许可证相同 。但是,还有其他兼容选项可用。最好清楚地表明您的插件使用的许可证。

“标头要求”部分中,我们简要提到了如何在插件标头注释中指示插件的许可证。另一种常见且鼓励的做法是在主插件文件顶部附近放置许可证块注释(与具有插件标头注释的文件相同)。

该许可证块注释通常如下所示:

/*
{Plugin Name} is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
any later version.

{Plugin Name} is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with {Plugin Name}. If not, see {URI to Plugin License}.
*/

 

激活/停用挂钩

激活和停用挂钩提供了在激活或停用插件时执行操作的方法。

停用挂钩有时会与卸载挂钩混淆。卸载挂钩最适合永久删除所有数据,例如删除插件选项和自定义表等。

激活

要设置激活挂钩,请使用register_activation_hook() 函数:

register_activation_hook(
	__FILE__,
	'pluginprefix_function_to_run'
);

停用

要设置停用挂钩,请使用register_deactivation_hook() 函数:

register_deactivation_hook(
	__FILE__,
	'pluginprefix_function_to_run'
);

每个函数中的第一个参数指的是您的主插件文件,即您在其中放置插件标头注释的文件。通常这两个函数将从主插件文件中触发;但是,如果函数放置在任何其他文件中,则必须更新第一个参数以正确指向主插件文件。

例子

激活挂钩最常见的用途之一是当插件注册自定义帖子类型时刷新 WordPress 永久链接。这消除了令人讨厌的 404 错误。

让我们看一个如何执行此操作的示例:

/**
 * Register the "book" custom post type
 */
function pluginprefix_setup_post_type() {
	register_post_type( 'book', ['public' => true ] ); 
} 
add_action( 'init', 'pluginprefix_setup_post_type' );


/**
 * Activate the plugin.
 */
function pluginprefix_activate() { 
	// Trigger our function that registers the custom post type plugin.
	pluginprefix_setup_post_type(); 
	// Clear the permalinks after the post type has been registered.
	flush_rewrite_rules(); 
}
register_activation_hook( __FILE__, 'pluginprefix_activate' );

如果您不熟悉注册自定义帖子类型,请不要担心 - 这将在稍后介绍。使用这个例子只是因为它很常见。

使用上面的示例,以下是如何反转此过程并停用插件:

/**
 * Deactivation hook.
 */
function pluginprefix_deactivate() {
	// Unregister the post type, so the rules are no longer in memory.
	unregister_post_type( 'book' );
	// Clear the permalinks to remove our post type's rules from the database.
	flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'pluginprefix_deactivate' );

有关激活和停用挂钩的更多信息,以下是一些优秀的资源:

 

 

 

 

卸载方法

从站点卸载时,您的插件可能需要进行一些清理。

如果用户停用插件,然后单击 WordPress 管理员中的删除链接,则该插件将被视为已卸载。

卸载插件后,您需要清除特定于插件的所有插件选项和/或设置,和/或其他数据库实体(例如表)。

经验不足的开发人员有时会错误地使用停用挂钩来实现此目的。

此表说明了停用和卸载之间的差异。

设想 停用挂钩 卸载挂钩
刷新缓存/临时 是的
刷新固定链接 是的
从 {$wpdb ->prefix}_options中删除选项 是的
从wpdb中删除表 是的

方法一:register_uninstall_hook

要设置卸载挂钩,请使用register_uninstall_hook() 函数:

register_uninstall_hook(
	__FILE__,
	'pluginprefix_function_to_run'
);

方法二:uninstall.php

要使用此方法,您需要uninstall.php在插件的根文件夹中创建一个文件。当用户删除插件时,这个魔术文件会自动运行。例如:/plugin-name/uninstall.php

警报:WP_UNINSTALL_PLUGIN在做任何事情之前 一定要检查常量uninstall.php。这可以防止直接访问。
该常量将由 WordPress 在uninstall.php调用期间定义。

当执行卸载时,该常量未定义register_uninstall_hook() 。

以下是删除选项条目并删除数据库表的示例:

// if uninstall.php is not called by WordPress, die
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    die;
}

$option_name = 'wporg_option';

delete_option( $option_name );

// for site options in Multisite
delete_site_option( $option_name );

// drop a custom database table
global $wpdb;
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}mytable" );

笔记:在多站点中,循环遍历所有博客以删除选项可能会占用大量资源。

 

最佳实践

以下是一些帮助组织代码的最佳实践,使其能够与 WordPress 核心和其他 WordPress 插件一起良好运行。

避免命名冲突

当您的插件对变量、函数或类使用与另一个插件相同的名称时,就会发生命名冲突。

幸运的是,您可以使用以下方法来避免命名冲突。

程序编码方法

默认情况下,所有变量、函数和类都在全局命名空间中定义,这意味着您的插件可以覆盖另一个插件设置的变量、函数和类,反之亦然。在函数或类内部定义的变量不受此影响。

给所有东西加上前缀

所有全局可访问的代码都应以唯一标识符作为前缀。前缀可以防止其他插件覆盖您的变量并意外调用您的函数和类。它还会阻止您做同样的事情。

为了防止与其他插件冲突,您的前缀长度应至少为 5 个字母。您应该避免使用常见的英语单词,而应选择您的插件特有的单词。

应加前缀的代码包括:

检查现有实施

PHP 提供了许多函数来验证变量、函数、类和常量是否存在。如果实体存在,所有这些都将返回 true。

例子:

// Create a function called "wporg_init" if it doesn't already exist
if ( ! function_exists( 'wporg_init' ) ) {
    function wporg_init() {
        register_setting( 'wporg_settings', 'wporg_option_foo' );
    }
}

// Create a function called "wporg_get_foo" if it doesn't already exist
if ( ! function_exists( 'wporg_get_foo' ) ) {
    function wporg_get_foo() {
        return get_option( 'wporg_option_foo' );
    }
}

面向对象的编程方法

解决命名冲突问题的一种更简单的方法是使用插件代码的类

您仍然需要检查您想要的类的名称是否已被占用,但其余的将由 PHP 处理。

例子

if ( ! class_exists( 'WPOrg_Plugin' ) ) {
    class WPOrg_Plugin {
        public static function init() {
            register_setting( 'wporg_settings', 'wporg_option_foo' );
        }

        public static function get_foo() {
            return get_option( 'wporg_option_foo' );
        }
    }

    WPOrg_Plugin::init();
    WPOrg_Plugin::get_foo();
}

文件组织

插件目录的根级别应包含您的plugin-name.php文件和(可选)您的uninstall.php文件。所有其他文件应尽可能组织到子文件夹中。

文件夹结构

清晰的文件夹结构可以帮助您和其他开发插件的人将相似的文件保存在一起。

以下是供参考的示例文件夹结构:

/plugin-name
     plugin-name.php
     uninstall.php
     /languages
     /includes
     /admin
          /js
          /css
          /images
     /public
          /js
          /css
          /images

插件架构

您为插件选择的架构或代码组织可能取决于插件的大小。

对于与 WordPress 核心、主题或其他插件交互有限的小型单一用途插件,设计复杂的类几乎没有什么好处;除非你知道这个插件以后会大大扩展。

对于包含大量代码的大型插件,请从类开始。单独的样式和脚本文件,甚至与构建相关的文件。这将有助于代码组织和插件的长期维护。

条件加载

将管理代码与公共代码分开很有帮助。使用条件式is_admin()。您仍必须执行功能检查,因为这并不表明用户已通过身份验证或具有管理员级别访问权限。请参阅检查用户能力

例如:

if ( is_admin() ) {
    // we are in admin mode
    require_once __DIR__ . '/admin/plugin-name-admin.php';
}

架构模式

虽然有许多可能的架构模式,但它们大致可以分为三种变体:

架构模式解释

上述更复杂的代码组织的具体实现已经写成教程和幻灯片:

样板起点

您可能希望从样板文件开始,而不是从头开始编写您编写的每个新插件。使用样板的优点之一是在您自己的插件之间保持一致性。如果您使用其他人已经熟悉的样板文件,样板文件还可以让其他人更轻松地为您的代码做出贡献。

这些也可以作为不同但可比较的架构的进一步示例。

当然,您可以利用这些和其他方面的不同方面来创建您自己的自定义样板。

单文件包含函数

<?php
/**
 * Move Floating Social Bar in Genesis
 *
 * @package   Move_Floating_Social_Bar_In_Genesis
 * @author    Gary Jones <gary@garyjones.co.uk>
 * @license   GPL-2.0+
 * @link      https://github.com/GaryJones/move-floating-social-bar-in-genesis
 * @copyright 2013 Gary Jones, Gamajo Tech
 *
 * @wordpress-plugin
 * Plugin Name:       Move Floating Social Bar in Genesis
 * Plugin URI:        https://github.com/GaryJones/move-floating-social-bar-in-genesis
 * Description:       Moves the Floating Social Bar plugin output from just inside the entry content to just before it.
 * Version:           1.0.0
 * Author:            Gary Jones
 * Author URI:        https://github.com/GaryJones/move-floating-social-bar-in-genesis
 * Text Domain:       move-floating-social-bar-in-genesis
 * License:           GPL-2.0+
 * License URI:       http://www.gnu.org/licenses/gpl-2.0.txt
 * Domain Path:       /languages
 * GitHub Plugin URI: https://github.com/GaryJones/move-floating-social-bar-in-genesis
 * GitHub Branch:     master
 */

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

add_action( 'pre_get_posts', 'mfsbig_remove_floating_social_bar', 15 );
/**
 * Remove Floating Social Bar from outputting at the top of the content.
 *
 * FSB adds these filters at pre_get_posts, priorty 10, so we'll remove them
 * just after that.
 *
 * @since 1.0.0
 */
function mfsbig_remove_floating_social_bar() {
	if ( class_exists( 'floating_social_bar' ) ) {
		remove_filter( 'the_excerpt', array( floating_social_bar::get_instance(), 'fsb' ), apply_filters( 'fsb_social_bar_priority', 10 ) );
		remove_filter( 'the_content', array( floating_social_bar::get_instance(), 'fsb' ), apply_filters( 'fsb_social_bar_priority', 10 ) );
	}
}

add_action( 'genesis_before_post_content', 'mfsbig_add_floating_social_bar' );  // XHTML themes
add_action( 'genesis_before_entry_content', 'mfsbig_add_floating_social_bar' ); // HTML5 themes
/**
 * Echo the Floating Social Bar in the right place, just before the entry
 * content (after the header and entry meta) in Genesis child themes.
 *
 * As fsb() is really a function for filtering, it requires a single argument
 * (the content or excerpt), so we fool it by passing in an empty string instead.
 *
 * @since 1.0.0
 */
function mfsbig_add_floating_social_bar() {
	if ( class_exists( 'floating_social_bar' ) ) {
		echo floating_social_bar::get_instance()->fsb('');
	}
}

 

单个插件文件,包含类、实例化对象和可选函数

<?php
/*
Plugin Name: WP Comment Notes
Plugin URI: http://andrewnorcross.com/plugins/
Description: Add custom notes before or after the comment form.
Version: 1.0.0
Author: Andrew Norcross
Author URI: http://andrewnorcross.com

	Copyright 2013 Andrew Norcross

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License, version 2, as
	published by the Free Software Foundation.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

if( !defined( 'WPCMN_VER' ) )
	define( 'WPCMN_VER', '1.0.0' );

// Start up the engine
class WP_Comment_Notes
{

	/**
	 * Static property to hold our singleton instance
	 *
	 */
	static $instance = false;

	/**
	 * This is our constructor
	 *
	 * @return void
	 */
	private function __construct() {
		// back end
		add_action		( 'plugins_loaded', 					array( $this, 'textdomain'				) 			);
		add_action		( 'admin_enqueue_scripts',				array( $this, 'admin_scripts'			)			);
		add_action		( 'do_meta_boxes',						array( $this, 'create_metaboxes'		),	10,	2	);
		add_action		( 'save_post',							array( $this, 'save_custom_meta'		),	1		);

		// front end
		add_action		( 'wp_enqueue_scripts',					array( $this, 'front_scripts'			),	10		);
		add_filter		( 'comment_form_defaults',				array( $this, 'custom_notes_filter'		) 			);
	}

	/**
	 * If an instance exists, this returns it.  If not, it creates one and
	 * retuns it.
	 *
	 * @return WP_Comment_Notes
	 */

	public static function getInstance() {
		if ( !self::$instance )
			self::$instance = new self;
		return self::$instance;
	}

	/**
	 * load textdomain
	 *
	 * @return void
	 */

	public function textdomain() {

		load_plugin_textdomain( 'wpcmn', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );

	}

	/**
	 * Admin styles
	 *
	 * @return void
	 */

	public function admin_scripts() {

		$types = $this->get_post_types();

		$screen	= get_current_screen();

		if ( in_array( $screen->post_type , $types ) ) :

			wp_enqueue_style( 'wpcmn-admin', plugins_url('lib/css/admin.css', __FILE__), array(), WPCMN_VER, 'all' );

		endif;

	}

	/**
	 * call metabox
	 *
	 * @return void
	 */

	public function create_metaboxes( $page, $context ) {

		$types = $this->get_post_types();

		if ( in_array( $page,  $types ) )
			add_meta_box( 'wp-comment-notes', __( 'Comment Notes', 'wpcmn' ), array( $this, 'wpcmn_notes_meta' ), $page, 'advanced', 'high' );

	}

	/**
	 * display meta fields for notes meta
	 *
	 * @return void
	 */

	public function wpcmn_notes_meta( $post ) {

		// Use nonce for verification
		wp_nonce_field( 'wpcmn_meta_nonce', 'wpcmn_meta_nonce' );

		$post_id	= $post->ID;

		// get postmeta, and our initial settings
		$notes		= get_post_meta( $post_id, '_wpcmn_notes', true );

		$before_text	= isset( $notes['before-text'] )	? $notes['before-text']	: '';
		$before_type	= isset( $notes['before-type'] )	? $notes['before-type']	: 'wpcmn-notes-standard';
		$after_text		= isset( $notes['after-text'] )		? $notes['after-text']	: '';
		$after_type		= isset( $notes['after-type'] )		? $notes['after-type']	: 'wpcmn-notes-standard';

		echo '<script type="text/javascript">jQuery(document).ready(function($){$("#comment_status").click(function(){$(".wpcmn-notes-table tr").toggle();})});</script>';

		$disabled_display = comments_open( $post_id )   ? ' style="display:none;"' : '';
		$enabled_display  = ! comments_open( $post_id ) ? ' style="display:none;"' : '';

		echo '<table class="form-table wpcmn-notes-table">';

			echo '<tr class="wpcmn-notes-disabled"' . $disabled_display . '>';
				echo '<th>' . __( 'Enable comments in order to use Comment Notes', 'wpcmn' ) . '</th>';
			echo '</tr>';

			echo '<tr class="wpcmn-notes-title"' . $enabled_display . '>';
			echo '<td colspan="2"><h5>'.__( 'Before Notes Area', 'wpcmn' ) . '</h5></td>';
			echo '</tr>';

			echo '<tr class="wpcmn-notes-data wpcmn-notes-before-text"' . $enabled_display . '>';
				echo '<th>'.__( 'Message Text', 'wpcmn' ) . '</th>';
				echo '<td>';
					echo '<textarea class="widefat" name="wpcmn-notes[before-text]" id="wpcmn-before">'.esc_attr( $before_text ) . '</textarea>';
					echo '<p class="description">'.__( 'Note: This will not appear to users who are logged in.', 'wpcmn' ) . '</p>';
				echo '</td>';
			echo '</tr>';

			echo '<tr class="wpcmn-notes-data wpcmn-notes-before-type"' . $enabled_display . '>';
				echo '<th>'.__( 'Message Type', 'wpcmn' ) . '</th>';
				echo '<td>';
					echo '<select id="wpcmn-before-type" name="wpcmn-notes[before-type]">';
					echo '<option value="wpcmn-notes-standard"' . selected( $before_type, 'wpcmn-notes-standard', false ) . '>' . __( 'Standard', 'wpcmn' ) . '</option>';
					echo '<option value="wpcmn-notes-warning"' . selected( $before_type, 'wpcmn-notes-warning', false ) . '>' . __( 'Warning', 'wpcmn' ) . '</option>';
					echo '<option value="wpcmn-notes-alert"' . selected( $before_type, 'wpcmn-notes-alert', false ) . '>' . __( 'Alert', 'wpcmn' ) . '</option>';
					do_action( 'wpcmn_before_types', $before_type );
					echo '</select>';
				echo '</td>';
			echo '</tr>';

			echo '<tr class="wpcmn-notes-title"' . $enabled_display . '>';
			echo '<td colspan="2"><h5>'.__( 'After Notes Area', 'wpcmn' ) . '</h5></td>';
			echo '</tr>';

			echo '<tr class="wpcmn-notes-data wpcmn-notes-after-text"' . $enabled_display . '>';
				echo '<th>'.__( 'Message Text', 'wpcmn' ) . '</th>';
				echo '<td>';
					echo '<textarea class="widefat" name="wpcmn-notes[after-text]" id="wpcmn-after">'.esc_attr( $after_text ) . '</textarea>';
				echo '</td>';
			echo '</tr>';

			echo '<tr class="wpcmn-notes-data wpcmn-notes-after-type"' . $enabled_display . '>';
				echo '<th>'.__( 'Message Type', 'wpcmn' ) . '</th>';
				echo '<td>';
					echo '<select id="wpcmn-after-type" name="wpcmn-notes[after-type]">';
					echo '<option value="wpcmn-notes-standard"' . selected( $after_type, 'wpcmn-notes-standard', false ) . '>' . __( 'Standard', 'wpcmn' ) . '</option>';
					echo '<option value="wpcmn-notes-warning"' . selected( $after_type, 'wpcmn-notes-warning', false ) . '>' . __( 'Warning', 'wpcmn' ) . '</option>';
					echo '<option value="wpcmn-notes-alert"' . selected( $after_type, 'wpcmn-notes-alert', false ) . '>' . __( 'Alert', 'wpcmn' ) . '</option>';
					do_action( 'wpcmn_after_types', $after_type );
					echo '</select>';
				echo '</td>';
			echo '</tr>';

		echo '</table>';

	}

	/**
	 * save post metadata
	 *
	 * @return void
	 */

	public function save_custom_meta( $post_id ) {

		// make sure we aren't using autosave
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
			return $post_id;

		// do our nonce check. ALWAYS A NONCE CHECK
		if ( ! isset( $_POST['wpcmn_meta_nonce'] ) || ! wp_verify_nonce( $_POST['wpcmn_meta_nonce'], 'wpcmn_meta_nonce' ) )
			return $post_id;

		$types = $this->get_post_types();

		if ( !in_array ( $_POST['post_type'], $types ) )
			return $post_id;

		// and make sure the user has the ability to do shit
		if ( 'page' == $_POST['post_type'] ) {

			if ( ! current_user_can( 'edit_page', $post_id ) ) {
				return $post_id;
			}

		} else {

			if ( ! current_user_can( 'edit_post', $post_id ) ) {
				return $post_id;
			}

		}

		// all clear. get data via $_POST and store it
		$notes	= ! empty( $_POST['wpcmn-notes'] ) ? $_POST['wpcmn-notes'] : false;

		// update side meta data
		if ( $notes ) {

			$allowed_html = array(
				'a'         => array(
					'href'  => array(),
					'title' => array(),
					'class' => array(),
					'id'    => array()
				),
				'br'        => array(),
				'em'        => array(),
				'strong'    => array(),
				'span'      => array(
					'class' => array(),
					'id'    => array()
				)
			);

			update_post_meta( $post_id, '_wpcmn_notes', wp_kses( $notes, $allowed_html ) );

			do_action( 'wpcmn_notes_save', $post_id, $notes );

		} else {
			delete_post_meta( $post_id, '_wpcmn_notes' );
		}

	}


	/**
	 * call front-end CSS
	 *
	 * @return void
	 */

	public function front_scripts() {

		// check for killswitch first
		$killswitch	= apply_filters( 'wpcmn_killswitch', false );

		if ( $killswitch )
			return false;

		$types = $this->get_post_types();

		if ( is_singular( $types ) )
			wp_enqueue_style( 'wpcmn-notes', plugins_url( 'lib/css/wpcmn-notes.css', __FILE__ ), array(), WPCMN_VER, 'all' );

	}

	/**
	 * The actual filter for adding the notes.
	 *
	 * @return array
	 */

	public function custom_notes_filter( $fields ) {

		global $post;

		// get the possible meta fields
		$notes	= get_post_meta( $post->ID, '_wpcmn_notes', true );

		if ( empty( $notes ) )
			return $fields;

		if ( isset( $notes['before-text'] ) ) :
			// grab the variables
			$text	= $notes['before-text'];
			$class	= isset( $notes['before-type'] ) ? $notes['before-type'] : 'wpcmn-notes-standard';
			// build the string

			$before	= '<p class="wpcmn-notes wpcmn-notes-before' . esc_attr( $class ) . '">' . $text . '</p>';

			// output
			$fields['comment_notes_before'] = $before;

		endif;

		if ( isset( $notes['after-text'] ) ) :
			// grab the variables
			$text	= $notes['after-text'];
			$class	= isset( $notes['after-type'] ) ? $notes['after-type'] : 'wpcmn-notes-standard';
			// build the string

			$after	= '<p class="wpcmn-notes wpcmn-notes-after' . esc_attr( $class ) . '">' . $text . '</p>';

			// output
			$fields['comment_notes_after'] = $after;

		endif;


		return $fields;
	}


	/**
	 * Return the list of post types that support Comment Notes
	 *
	 * @return array
	 */
	public function get_post_types() {

		$types = get_post_types( array( 'public' => true, 'show_ui' => true ) );

		foreach( $types as $type ) {
			if( ! post_type_supports( $type, 'comments' ) ) {
				unset( $types[ $type ] );
			}
		}

		return apply_filters( 'wpcmn_type_support', $types );
	}

/// end class
}


// Instantiate our class
$WP_Comment_Notes = WP_Comment_Notes::getInstance();

 

主插件文件,然后是一个或多个类文件

下载网址:https://github.com/DevinVinson/WordPress-Plugin-Boilerplate

可以通过下列网址自动生成结构:https://wppb.me/

WordPress 插件样板

用于构建高质量 WordPress 插件的标准化、有组织、面向对象的基础。

内容

WordPress 插件样板包含以下文件:

特征

安装

Boilerplate 可以直接“按原样”安装到您的插件文件夹中。您将需要重命名它及其内部的类以满足您的需要。例如,如果您的插件名为“example-me”,则:

此时激活插件是安全的。由于样板没有真正的功能,因此在编写代码之前不会添加菜单项、元框或自定义帖子类型。

WordPress.org 准备

此版本样板的最初发布包括在 WordPress.org 上使用插件所需的文件夹结构。该文件夹结构已移至其自己的存储库:https ://github.com/DevinVinson/Plugin-Directory-Boilerplate

推荐工具

国际化工具

WordPress 插件样板使用变量来存储在整个样板中国际化字符串时使用的文本域。要利用此方法,建议使用一些工具来提供正确的可翻译文件:

上述任何工具都应该为您提供适当的工具来国际化插件。

许可证

WordPress 插件样板根据 GPL v2 或更高版本获得许可。

该程序是免费软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证第 2 版的条款重新分发和/或修改它。

分发此程序的目的是希望它有用,但不提供任何保证;甚至没有适销性或特定用途适用性的默示保证。有关更多详细信息,请参阅 GNU 通用公共许可证。

您应该随该程序一起收到 GNU 通用公共许可证的副本;如果没有,请写信给 Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

许可证的副本包含在插件目录的根目录中。该文件名为LICENSE.

重要笔记

许可

WordPress 插件样板已根据 GPL v2 或更高版本获得许可;但是,如果您选择使用与 v2 不兼容的第三方代码,那么您可能需要改用与 GPL v3 兼容的代码。

作为参考,这里的讨论涵盖了Bootstrap使用的 Apache 2.0 许可证。

包括

请注意,如果您包含自己的类或第三方库,则所述文件可能位于三个位置:

请注意,以前版本的样板不包含该类Plugin_Name_Loader,但该类用于向 WordPress 注册所有过滤器和操作。

提供的示例代码展示了如何向 Loader 类注册您的钩子。

其他功能怎么样?

WordPress 插件样板的早期版本包含对许多不同项目的支持,例如GitHub Updater

这些工具不是这个样板的核心部分,因为我认为它们是对样板的补充、分叉或其他贡献。

使用 Grunt、Composer 等工具也是如此。这些都是很棒的工具,但并不是每个人都使用它们。为了保持核心Boilerplate尽可能轻,这些功能已被删除,并将在其他版本中引入,并将在项目主页上列出和维护。

确定插件和内容目录

在编写 WordPress 插件时,您经常需要在整个 WordPress 安装过程中以及插件或主题中引用各种文件和文件夹。

WordPress 提供了多种函数来轻松确定给定文件或目录所在的位置。始终在插件中使用这些函数,而不是对 wp-content 目录进行硬编码引用或使用 WordPress 内部常量。

WordPress 允许用户将 wp-content 目录放置在任何他们想要的地方,并根据需要重命名它。永远不要假设插件将在 wp-content/plugins 中,上传将在 wp-content/uploads 中,或者主题将在 wp-content/themes 中。

PHP 的__FILE__magic-constant 自动解析符号链接,因此如果 wp-content 或 wp-content/plugins 什至单个插件目录被符号链接,则硬编码路径将无法正常工作。

常见用法

如果您的插件包含 JavaScript 文件、CSS 文件或其他外部文件,那么您可能需要这些文件的 URL,以便将它们加载到页面中。为此,您应该使用 plugins_url()  函数如下:

plugins_url( 'myscript.js', __FILE__ );

这将返回 myscript.js 的完整 URL,例如 example.com/wp-content/plugins/myplugin/myscript.js

要将插件的 JavaScript 或 CSS 加载到页面中,您应该分别使用 wp_enqueue_script() 或 wp_enqueue_style() ,将结果 plugins_url() 作为文件 URL 传递。

可用功能

WordPress 包含许多其他函数,用于确定插件、主题和 WordPress 本身内的文件或目录的路径和 URL。有关其使用的完整信息,请参阅每个功能的单独 DevHub 页面。

插件

plugins_url()
plugin_dir_url()
plugin_dir_path()
plugin_basename()

主题

get_template_directory_uri()
get_stylesheet_directory_uri()
get_stylesheet_uri()
get_theme_root_uri()
get_theme_root()
get_theme_roots()
get_stylesheet_directory()
get_template_directory()

 

网站首页

home_url()
get_home_path()

WordPress

admin_url()
site_url()
content_url()
includes_url()
wp_upload_dir()

常数

WordPress 在确定内容和插件目录的路径时使用以下常量。这些不应由插件或主题直接使用,但为了完整性而在此处列出。

WP_CONTENT_DIR  // no trailing slash, full paths only
WP_CONTENT_URL  // full url 
WP_PLUGIN_DIR  // full path, no trailing slash
WP_PLUGIN_URL  // full url, no trailing slash

// Available per default in MS, not set in single site install
// Can be used in single site installs (as usual: at your own risk)
UPLOADS // (If set, uploads folder, relative to ABSPATH) (for e.g.: /wp-content/uploads)

有关的

WordPress 目录

home_url() 主页网址 http://www.example.com
site_url() 站点目录 URL http://www.example.comhttp://www.example.com/wordpress
admin_url() 管理目录 URL http://www.example.com/wp-admin
includes_url() 包含目录 URL http://www.example.com/wp-includes
content_url() 内容目录 URL http://www.example.com/wp-content
plugins_url() 插件目录 URL http://www.example.com/wp-content/plugins
wp_upload_dir() 上传目录URL(返回数组) http://www.example.com/wp-content/uploads