在我们的 first part of our tutorial series,我们介绍了创建WordPress插件、使用 WordPress Plugin Boilerplate、使用其非常方便的 generator的基础知识,并学习了如何添加、消毒和保存基本输入类型。
如果你错过了那部分,一定要去检查一下。- How to build a WordPress Plugin part 1
在第2部分中,我们将介绍:
- 巩固我们的插件;
- 添加不同的输入类型,如颜色选择器和文件上传;
- 将整个页面划分为多个选项卡;
- 创建实际将对前端/后端执行某些操作的函数;
- 我们插件的最终测试;
- 然后将我们的插件发送到 WordPress存储库小组进行审查.
我们将处理第1部分中的代码基,所以如果您还没有完成,请确保 grab the code from part 1 on GitHub.
使您的插件可翻译(国际化或il18n)
在深入研究代码之前,让我解释一下为什么让插件可以翻译是很重要的。
将插件添加到WordPress存储库将使您成为WordPress社区的一部分-这是巨大的!所以,你可能会明白,很多来自不同国家的人可能会使用它。其中一些人可能英语或你的主要语言不流利。让您的插件可以轻松地被翻译,而不需要触及其核心代码,这是非常有意义的。
plugin boilerplate附带一个 languages
文件夹,您可能还记得在第一部分中。这就是你的插件语言文件所在的地方。我不会在这里深入研究翻译过程,但我只知道一些应用程序(如 poedit)就是在这里提供帮助的。
让我们对我们现有的代码做一些修改,这样 poedit就能够找到所有可翻译的字符串了:
基本上,我们在这里没有做太多改变,我们只是用以下方法包装了所有硬编码的字符串:
1 | <?php _e('our string', $this->plugin_name);?> |
所有这一切只是 echo(_e
) 我们的字符串,并将它分配给我们的插件 ($this->plugin_name
)。
有关完整的代码更改,请参阅下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | <?php /** * * admin/partials/wp-cbf-admin-display.php * **/ /** * Provide a admin area view for the plugin * * This file is used to markup the admin-facing aspects of the plugin. * * @link http://lostwebdesigns.com * @since 1.0.0 * * @package Wp_Cbf * @subpackage Wp_Cbf/admin/partials */ ?> <!-- This file should primarily consist of HTML with a little bit of PHP. --> <div class="wrap"> <h2><?php echo esc_html( get_admin_page_title() ); ?></h2> <h2 class="nav-tab-wrapper">Clean up</h2> <form method="post" name="cleanup_options" action="options.php"> <?php //Grab all options $options = get_option($this->plugin_name); // Cleanup $cleanup = $options['cleanup']; $comments_css_cleanup = $options['comments_css_cleanup']; $gallery_css_cleanup = $options['gallery_css_cleanup']; $body_class_slug = $options['body_class_slug']; $jquery_cdn = $options['jquery_cdn']; $cdn_provider = $options['cdn_provider']; ?> <?php settings_fields( $this->plugin_name ); do_settings_sections( $this->plugin_name ); ?> <!-- remove some meta and generators from the <head> --> <fieldset> <legend class="screen-reader-text"><span><?php _e('Clean WordPress head section', $this->plugin_name);?></span></legend> <label for="<?php echo $this->plugin_name;?>-cleanup"> <input type="checkbox" id="<?php echo $this->plugin_name;?>-cleanup" name="<?php echo $this->plugin_name;?>[cleanup]" value="1" <?php checked( $cleanup, 1 ); ?> /> <span><?php esc_attr_e( 'Clean up the head section', $this->plugin_name ); ?></span> </label> </fieldset> <!-- remove injected CSS from comments widgets --> <fieldset> <legend class="screen-reader-text"><span>Remove Injected CSS for comment widget</span></legend> <label for="<?php echo $this->plugin_name;?>-comments_css_cleanup"> <input type="checkbox" id="<?php echo $this->plugin_name;?>-comments_css_cleanup" name="<?php echo $this->plugin_name;?>[comments_css_cleanup]" value="1" <?php checked( $comments_css_cleanup, 1 ); ?> /> <span><?php esc_attr_e( 'Remove Injected CSS for comment widget', $this->plugin_name ); ?></span> </label> </fieldset> <!-- remove injected CSS from gallery --> <fieldset> <legend class="screen-reader-text"><span>Remove Injected CSS for galleries</span></legend> <label for="<?php echo $this->plugin_name;?>-gallery_css_cleanup"> <input type="checkbox" id="<?php echo $this->plugin_name;?>-gallery_css_cleanup" name="<?php echo $this->plugin_name;?>[gallery_css_cleanup]" value="1" <?php checked( $gallery_css_cleanup, 1 ); ?> /> <span><?php esc_attr_e( 'Remove Injected CSS for galleries', $this->plugin_name ); ?></span> </label> </fieldset> <!-- add post,page or product slug class to body class --> <fieldset> <legend class="screen-reader-text"><span><?php _e('Add Post, page or product slug to body class', $this->plugin_name);?></span></legend> <label for="<?php echo $this->plugin_name;?>-body_class_slug"> <input type="checkbox" id="<?php echo $this->plugin_name;?>-body_class_slug" name="<?php echo $this->plugin_name;?>[body_class_slug]" value="1" <?php checked( $body_class_slug, 1 ); ?> /> <span><?php esc_attr_e('Add Post slug to body class', $this->plugin_name);?></span> </label> </fieldset> <!-- load jQuery from CDN --> <fieldset> <legend class="screen-reader-text"><span><?php _e('Load jQuery from CDN instead of the basic wordpress script', $this->plugin_name);?></span></legend> <label for="<?php echo $this->plugin_name;?>-jquery_cdn"> <input type="checkbox" id="<?php echo $this->plugin_name;?>-jquery_cdn" name="<?php echo $this->plugin_name;?>[jquery_cdn]" value="1" <?php checked($jquery_cdn,1);?>/> <span><?php esc_attr_e('Load jQuery from CDN', $this->plugin_name);?></span> </label> <fieldset class="<?php if(1 != $jquery_cdn) echo 'hidden';?>"> <p>You can choose your own cdn provider and jQuery version(default will be Google Cdn and version 1.11.1)-Recommended CDN are <a href="https://cdnjs.com/libraries/jquery">CDNjs</a>, <a href="https://code.jquery.com/jquery/">jQuery official CDN</a>, <a href="https://developers.google.com/speed/libraries/#jquery">Google CDN</a> and <a href="http://www.asp.net/ajax/cdn#jQuery_Releases_on_the_CDN_0">Microsoft CDN</a></p> <legend class="screen-reader-text"><span><?php _e('Choose your prefered cdn provider', $this->plugin_name);?></span></legend> <input type="url" class="regular-text" id="<?php echo $this->plugin_name;?>-cdn_provider" name="<?php echo $this->plugin_name;?>[cdn_provider]" value="<?php if(!empty($cdn_provider)) echo $cdn_provider;?>"/> </fieldset> </fieldset> <?php submit_button(__('Save all changes', $this->plugin_name), 'primary','submit', TRUE); ?> </form> </div> |
这里对GIF的唯一更改是 submit_button()
。您可以看到第一个参数(即按钮文本)将其文本包装在 __()
中。这与 _e()
相同,只是返回这个文本而不是回显。
所以现在,所有包装好的字符串都将被引用到我们的插件中, poedit将能够自动获取这些字符串并将其插入到 .pot文件中。
你现在有了一个完全可译的插件,太棒了!
添加更复杂的输入类型
我们已经在插件中添加了许多文件,但是只有两种类型的输入:
- Checkboxes
- Text inputs
现在我们将添加两个不同的和更复杂的输入:
- Color pickers
- File/Media uploads
要做到这一点,让我们添加一个新的部分 login page customization
我们在表单中的插件。在我们已经使用的 html之后插入这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | /** * * admin/partials/wp-cbf-admin-display.php * **/ ... <form method="post" name="cleanup_options" action="options.php"> <?php //Grab all options $options = get_option($this->plugin_name); // Cleanup $cleanup = $options['cleanup']; $comments_css_cleanup = $options['comments_css_cleanup']; $gallery_css_cleanup = $options['gallery_css_cleanup']; $body_class_slug = $options['body_class_slug']; $jquery_cdn = $options['jquery_cdn']; $cdn_provider = $options['cdn_provider']; // New Login customization vars $login_logo_id = $options['login_logo_id']; $login_logo = wp_get_attachment_image_src( $login_logo_id, 'thumbnail' ); $login_logo_url = $login_logo[0]; $login_background_color = $options['login_background_color']; $login_button_primary_color = $options['login_button_primary_color']; ?> ... <!-- Login page customizations --> <h2 class="nav-tab-wrapper"><?php _e('Login customization', $this->plugin_name);?></h2> <p><?php _e('Add logo to login form change buttons and background color', $this->plugin_name);?></p> <!-- add your logo to login --> <fieldset> <legend class="screen-reader-text"><span><?php esc_attr_e('Login Logo', $this->plugin_name);?></span></legend> <label for="<?php echo $this->plugin_name;?>-login_logo"> <input type="hidden" id="login_logo_id" name="<?php echo $this->plugin_name;?>[login_logo_id]" value="<?php echo $login_logo_id; ?>" /> <input id="upload_login_logo_button" type="button" class="button" value="<?php _e( 'Upload Logo', $this->plugin_name); ?>" /> <span><?php esc_attr_e('Login Logo', $this->plugin_name);?></span> </label> <div id="upload_logo_preview" class="wp_cbf-upload-preview <?php if(empty($login_logo_id)) echo 'hidden'?>"> <img src="<?php echo $login_logo_url; ?>" /> <button id="wp_cbf-delete_logo_button" class="wp_cbf-delete-image">X</button> </div> </fieldset> <!-- login background color--> <fieldset class="wp_cbf-admin-colors"> <legend class="screen-reader-text"><span><?php _e('Login Background Color', $this->plugin_name);?></span></legend> <label for="<?php echo $this->plugin_name;?>-login_background_color"> <input type="text" class="<?php echo $this->plugin_name;?>-color-picker" id="<?php echo $this->plugin_name;?>-login_background_color" name="<?php echo $this->plugin_name;?>[login_background_color]" value="<?php echo $login_background_color;?>" /> <span><?php esc_attr_e('Login Background Color', $this->plugin_name);?></span> </label> </fieldset> <!-- login buttons and links primary color--> <fieldset class="wp_cbf-admin-colors"> <legend class="screen-reader-text"><span><?php _e('Login Button and Links Color', $this->plugin_name);?></span></legend> <label for="<?php echo $this->plugin_name;?>-login_button_primary_color"> <input type="text" class="<?php echo $this->plugin_name;?>-color-picker" id="<?php echo $this->plugin_name;?>-login_button_primary_color" name="<?php echo $this->plugin_name;?>[login_button_primary_color]" value="<?php echo $login_button_primary_color;?>" /> <span><?php esc_attr_e('Login Button and Links Color', $this->plugin_name);?></span> </label> </fieldset> <?php submit_button(__('Save all changes', $this->plugin_name), 'primary','submit', TRUE); ?> </form> |
我们现在增加了新的变量和所有需要的投入。我们现在还必须从 $options
变量中添加一个变量来“缓存”它们的值。他们来这里只是为了获取徽标图像url,因为我们将在我们的选项中保存图像id:
1 2 | $login_logo = wp_get_attachment_image_src( $login_logo_id, 'thumbnail' ); $login_logo_url = $login_logo[0]; |
这里没有什么花哨。我们只是简单地使用 wp_get_attachment_image_src
将我们的登录 login_logo_id
和我们想要的大小传递给它。然后 $login_logo_url
将只给我们图像url,这样我们就可以在 img
src属性中使用它。
从结果来看,这不是我们所期望的:
为了使这一工作,我们需要添加一些 Javascript和 CSS文件包括在 WordPress核心,以使它显示和正常工作。
打开 admin/class-wp-cbf-admin.php
,并将以下内容添加到 public function enqueue_styles()
和 public function enqueue_scripts()
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | /** * * admin/class-wp-cbf-admin.php * **/ public function enqueue_styles() { /** * This function is provided for demonstration purposes only. * * An instance of this class should be passed to the run() function * defined in Wp_Cbf_Loader as all of the hooks are defined * in that particular class. * * The Wp_Cbf_Loader will then create the relationship * between the defined hooks and the functions defined in this * class. */ if ( 'settings_page_wp-cbf' == get_current_screen() -> id ) { // CSS stylesheet for Color Picker wp_enqueue_style( 'wp-color-picker' ); wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/wp-cbf-admin.css', array( 'wp-color-picker' ), $this->version, 'all' ); } } /** * Register the JavaScript for the admin area. * * @since 1.0.0 */ public function enqueue_scripts() { /** * This function is provided for demonstration purposes only. * * An instance of this class should be passed to the run() function * defined in Wp_Cbf_Loader as all of the hooks are defined * in that particular class. * * The Wp_Cbf_Loader will then create the relationship * between the defined hooks and the functions defined in this * class. */ if ( 'settings_page_wp-cbf' == get_current_screen() -> id ) { wp_enqueue_media(); wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'js/wp-cbf-admin.js', array( 'jquery', 'wp-color-picker' ), $this->version, false ); } } |
如您所见,我们正在添加两个样式表: wp-color-picker
和 thickbox
。 只有当我们在插件页面上,因为有条件if语句到位时,才会排队:
1 | if ( 'settings_page_wp-cbf' == get_current_screen() -> id ) |
正如您再次看到的,我们的 JS和 CSS调用都在加载先前添加的依赖项。因此,我们将在其中添加的任何代码都将覆盖默认值:
1 2 3 | wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/wp-cbf-admin.css', array('wp-clor-picker' ), $this->version, 'all' ); wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'js/wp-cbf-admin.js', array( 'jquery', 'wp-color-picker' ), $this->version, false ); |
如果仔细查看 enqueue_scripts()
函数,您可能会想知道为什么这里没有 wp-color-picker
, media-upload
, or thickbox
,只作为我们插件的 javascript依赖项加载。
由于WordPress v3.5,媒体Uploader不再使用厚盒。要加载新的媒体上传依赖项,只需添加 wp_enqueue_media()
,它将加载所有需要的脚本。
查看这些文档页面以获得更多信息, wp_enqueue_media()
和 wp.media
引用。
同样,如果我们在插件的设置页面上,我们只是在添加这些文件。
1 | wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'js/wp-cbf-admin.js', array( 'jquery', 'wp-color-picker', 'media-upload' ), $this->version, false ); |
现在让我们将一些 javascript添加到插件的JS文件中: admin/js/wp-cbf-admin.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | /** * * admin/js/wp-cbf-admin.js * **/ (function( $ ) { 'use strict'; /** * All of the code for your admin-specific JavaScript source * should reside in this file. * * Note that this assume you're going to use jQuery, so it prepares * the $ function reference to be used within the scope of this * function. * * From here, you're able to define handlers for when the DOM is * ready: * * $(function() { * * }); * * Or when the window is loaded: * * $( window ).load(function() { * * }); * * ...and so on. * * Remember that ideally, we should not attach any more than a single DOM-ready or window-load handler * for any particular page. Though other scripts in WordPress core, other plugins, and other themes may * be doing this, we should try to minimize doing that in our own work. */ $(function(){ // Let's set up some variables for the image upload and removing the image var frame, imgUploadButton = $( '#upload_login_logo_button' ), imgContainer = $( '#upload_logo_preview' ), imgIdInput = $( '#login_logo_id' ), imgPreview = $('#upload_logo_preview'), imgDelButton = $('#wp_cbf-delete_logo_button'), // Color Pickers Inputs colorPickerInputs = $( '.wp-cbf-color-picker' ); // WordPress specific plugins - color picker and image upload $( '.wp-cbf-color-picker' ).wpColorPicker(); // wp.media add Image imgUploadButton.on( 'click', function( event ){ event.preventDefault(); // If the media frame already exists, reopen it. if ( frame ) { frame.open(); return; } // Create a new media frame frame = wp.media({ title: 'Select or Upload Media for your Login Logo', button: { text: 'Use as my Login page Logo' }, multiple: false // Set to true to allow multiple files to be selected }); // When an image is selected in the media frame... frame.on( 'select', function() { // Get media attachment details from the frame state var attachment = frame.state().get('selection').first().toJSON(); // Send the attachment URL to our custom image input field. imgPreview.find( 'img' ).attr( 'src', attachment.sizes.thumbnail.url ); // Send the attachment id to our hidden input imgIdInput.val( attachment.id ); // Unhide the remove image link imgPreview.removeClass( 'hidden' ); }); // Finally, open the modal on click frame.open(); }); // Erase image url and age preview imgDelButton.on('click', function(e){ e.preventDefault(); imgIdInput.val(''); imgPreview.find( 'img' ).attr( 'src', '' ); imgPreview.addClass('hidden'); }); }); // End of DOM Ready })( jQuery ); |
正如注释中所定义的,我们已经将 JS封装在 DOM就绪函数中: $(function(){ ...our code... });
它本身包含在自调用的匿名函数中。
首先,我们定义了我们将在Javascript中使用的一些var,然后,对于颜色选择器,我们只将 wpColorPicker()
方法调用到我们的颜色选择器字段-没有什么太复杂的。
对于图像上传,我们主要使用 wp.media Javascript引用的改编版本。几乎所有的评论,但让我们通过它一点:
显然,当我们单击 “Upload Logo”按钮时,我们将打开媒体上载框架,该框架由 imgUploadButton.on( 'click', function( event ){ ... frame.open() });
frame
是 wp.media
的表示形式,可以将一些选项作为:
- 弹出式窗口的标题,在这里:
title: 'Select or Upload Media for your Login Logo'
- 右下角按钮文本,在这里:
button: {text: 'Use as my Login page Logo'}
- 一个多选择选项(如果您希望能够获取多个图像),这里设置为 false:
multiple: false
然后,我们有一个 frame.on( 'select', function() {})
,一旦我们选择上传的图像并将结果返回为附件var在这里表示的 JSON对象,它就会被触发:
1 2 3 | var attachment = frame.state().get('selection').first().toJSON(); //console.log(attachment) |
您可以将该对象合并到日志中,以查看它从图像中为我们提供了一个完整的属性列表。我们使用它来给我们的 img
字段提供一个 src
值(使用 attachment.sizes.thumbnail.url
,所以我们有一个很好的 150x150px图像)以及隐藏字段的图像 id:
1 | <input type="hidden" id="login_logo_id" name="<?php echo $this->plugin_name;?>[login_logo_id]" value="<?php echo esc_url($login_logo_id); ?>` |
为什么给它一个id值而不是直接的图像url?嗯,这会让消毒更容易,因为它应该只是一个数字。
最后,我们有了 imgDelButton.on('click', function(e){});
将删除 img
源值。隐藏文件id值,并在单击时将 hidden
类添加回预览控制器。
因此,有了这个小JS,你现在有了一个几乎全功能的设置页面,里面有一些最酷的WordPress内置功能。
你现在应该有相同的结果与截图,以下是超级漂亮的虹膜颜色选择。如果你点击任何颜色字段,媒体上传弹出时点击上传,一旦图像被选中,我们的新标志就会很好地显示在那里。
相当整洁,对吧!
但再一次,如果你试图保存所有这些新的价值,什么都不会发生。为了保存和检索新的输入值,我们必须经过消毒和保存/更新。
现在就开始吧。
清理和保存/更新这些复杂字段
正如您可能从 part one中所记得的, register_setting( $this->plugin_name, $this->plugin_name, array($this, 'validate') );
将负责在验证函数验证后 update/saving我们的 validate
值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | /** * * admin/class-wp-cbf-admin.php * **/ public function validate($input) { // All checkboxes inputs $valid = array(); //Cleanup $valid['cleanup'] = (isset($input['cleanup']) && !empty($input['cleanup'])) ? 1 : 0; $valid['comments_css_cleanup'] = (isset($input['comments_css_cleanup']) && !empty($input['comments_css_cleanup'])) ? 1: 0; $valid['gallery_css_cleanup'] = (isset($input['gallery_css_cleanup']) && !empty($input['gallery_css_cleanup'])) ? 1 : 0; $valid['body_class_slug'] = (isset($input['body_class_slug']) && !empty($input['body_class_slug'])) ? 1 : 0; $valid['jquery_cdn'] = (isset($input['jquery_cdn']) && !empty($input['jquery_cdn'])) ? 1 : 0; $valid['cdn_provider'] = esc_url($input['cdn_provider']); // Login Customization //First Color Picker $valid['login_background_color'] = (isset($input['login_background_color']) && !empty($input['login_background_color'])) ? sanitize_text_field($input['login_background_color']) : ''; if ( !empty($valid['login_background_color']) && !preg_match( '/^#[a-f0-9]{6}$/i', $valid['login_background_color'] ) ) { // if user insert a HEX color with # add_settings_error( 'login_background_color', // Setting title 'login_background_color_texterror', // Error ID 'Please enter a valid hex value color', // Error message 'error' // Type of message ); } //Second Color Picker $valid['login_button_primary_color'] = (isset($input['login_button_primary_color']) && !empty($input['login_button_primary_color'])) ? sanitize_text_field($input['login_button_primary_color']) : ''; if ( !empty($valid['login_button_primary_color']) && !preg_match( '/^#[a-f0-9]{6}$/i', $valid['login_button_primary_color'] ) ) { // if user insert a HEX color with # add_settings_error( 'login_button_primary_color', // Setting title 'login_button_primary_color_texterror', // Error ID 'Please enter a valid hex value color', // Error message 'error' // Type of message ); } //Logo image id $valid['login_logo_id'] = (isset($input['login_logo_id']) && !empty($input['login_logo_id'])) ? absint($input['login_logo_id']) : 0; return $valid; } |
这就是我们的 validate
函数现在的样子-让我们看一看
对于 login_logo_id
没什么可说的。我们只是检查一下 $input['login_logo_id']
is set, and, if not,我们给它的价值是 0
(检查时,它是一个假值 empty()
). 如果设置了这个值,那么我们只需确保这个值是一个正整数 absint
.
现在,让我们解释一下我们的颜色选择器验证,正如您所看到的更详细:
首先,我们获取我们的 $input
输入值,并确保它是设置的,而不是空的:
1 | $valid['login_background_color'] = (isset($input['login_background_color']) && !empty($input['login_background_color'])) ? sanitize_text_field($input['login_background_color']) : ''; |
然后,如果值不是空(这也意味着不是空字符串),我们将对正则表达式进行测试,以确保它是十六进制字符串。这意味着它必须以 #
开头,然后包含 6个字符,这些字符可以是 0-9之间的整数,也可以是 a-f之间的字母:
1 | /^#[a-f0-9]{6}$/i |
如果 regex测试失败,我们将添加一些 settings error 它们是 settings_api:
1 | add_settings_error('login_button_primary_color', // Setting title 'login_button_primary_color_texterror', // Error ID 'Please enter a valid hex value color', // Error message 'error' // Type of message); |
像看到的 add_settings_error
文档,第一个参数是一个唯一的标识符,必须与我们的设置相关。这里: login_button_primary_color
.
然后,我们有另一种类型的 slug
,它将被添加到错误消息类中,如下所示: login_button_primary_color_texterror
.
然后,您想要显示的消息(非常重要的是,使此显化): Please enter a valid hex value color
.
最后,作为默认值可选的错误类型是 error。把它写下来总是很好的,这样您就可以直接知道这个错误消息是关于什么的。
下面是为颜色选择器保存非十六进制字符串时的错误屏幕截图:
如果一切都是正确的,那么我们现在可以通过成功通知来保存我们的选择了:
好的,现在我们有了大量的选项,这些选项的值都经过了适当的消毒和保存! 是时候让我们的插件能够改变我们网站的行为了!
创建将改变主题/后端行为的函数
我们还没有使我们的插件与我们的WordPress网站互动。现在情况会变的。我们将把它分成两部分:
- “Clean up”部分将改变我们的网站的前端和登录
- 然后, “Login Customizations”将应用于后端
为了保持代码的良好组织,我们将在其相关文件夹中编写每个部分。我们将使用 public
的前端,我们将使用 admin
的后端-这是完全合理的。
我们的前端功能
打开 public/class-wp-cbf-public.php
文件并在我们的 enqueue_scripts()
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | /** * * public/class-wp-cbf-public.php * **/ public function __construct( $plugin_name, $version ) { $this->plugin_name = $plugin_name; $this->version = $version; $this->wp_cbf_options = get_option($this->plugin_name); } /** * Cleanup functions depending on each checkbox returned value in admin * * @since 1.0.0 */ // Cleanup head public function wp_cbf_cleanup() { if($this->wp_cbf_options['cleanup']){ remove_action( 'wp_head', 'rsd_link' ); // RSD link remove_action( 'wp_head', 'feed_links_extra', 3 ); // Category feed link remove_action( 'wp_head', 'feed_links', 2 ); // Post and comment feed links remove_action( 'wp_head', 'index_rel_link' ); remove_action( 'wp_head', 'wlwmanifest_link' ); remove_action( 'wp_head', 'parent_post_rel_link', 10, 0 ); // Parent rel link remove_action( 'wp_head', 'start_post_rel_link', 10, 0 ); // Start post rel link remove_action( 'wp_head', 'rel_canonical', 10, 0 ); remove_action( 'wp_head', 'wp_shortlink_wp_head', 10, 0 ); remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0 ); // Adjacent post rel link remove_action( 'wp_head', 'wp_generator' ); // WP Version remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); remove_action( 'wp_print_styles', 'print_emoji_styles' ); } } // Cleanup head public function wp_cbf_remove_x_pingback($headers) { if(!empty($this->wp_cbf_options['cleanup'])){ unset($headers['X-Pingback']); return $headers; } } // Remove Comment inline CSS public function wp_cbf_remove_comments_inline_styles() { if(!empty($this->wp_cbf_options['comments_css_cleanup'])){ global $wp_widget_factory; if ( has_filter( 'wp_head', 'wp_widget_recent_comments_style' ) ) { remove_filter( 'wp_head', 'wp_widget_recent_comments_style' ); } if ( isset($wp_widget_factory->widgets['WP_Widget_Recent_Comments']) ) { remove_action( 'wp_head', array($wp_widget_factory->widgets['WP_Widget_Recent_Comments'], 'recent_comments_style') ); } } } // Remove gallery inline CSS public function wp_cbf_remove_gallery_styles($css) { if(!empty($this->wp_cbf_options['gallery_css_cleanup'])){ return preg_replace( "!<style type='text/css'>(.*?)</style>!s", '', $css ); } } // Add post/page slug public function wp_cbf_body_class_slug( $classes ) { if(!empty($this->wp_cbf_options['body_class_slug'])){ global $post; if(is_singular()){ $classes[] = $post->post_name; } } return $classes; } // Load jQuery from CDN if available public function wp_cbf_cdn_jquery(){ if(!empty($this->wp_cbf_options['jquery_cdn'])){ if(!is_admin()){ if(!empty($this->wp_cbf_options['cdn_provider'])){ $link = $this->wp_cbf_options['cdn_provider']; }else{ $link = 'http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js'; } $try_url = @fopen($link,'r'); if( $try_url !== false ) { wp_deregister_script( 'jquery' ); wp_register_script('jquery', $link, array(), null, false); } } } } |
这里的第一件事是,我们要添加对保存的选项的引用:
1 | $this->wp_cbf_options = get_option($this->plugin_name); |
因此,我们将能够在我们的功能中使用它。
然后,我们只是添加一些公共函数,每个函数都包含一个 if
条件检查,是否检查了它的相关选项(下面是有用的 $this->wp_cbf_options
引用)。
让我们以 wp_cbf_bobody_class_slug
函数为例,查看 body_class documentation on the WordPress Codex 文档,然后向下滚动到 “Add Classes By Filters”,给出下面的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /* From the WordPress Codex */ // Add specific CSS class by filter add_filter( 'body_class', 'my_class_names' ); function my_class_names( $classes ) { // add 'class-name' to the $classes array $classes[] = 'class-name'; // return the $classes array return $classes; } /* From our Plugin */ public function wp_cbf_body_class_slug( $classes ) { if(!empty($this->wp_cbf_options['body_class_slug'])){ global $post; if(is_singular()){ $classes[] = $post->post_name; } } return $classes; } |
我们可以看到,对于插件,我们使用的格式是相同的,如果我们在页面、附件页面或单个POST是 is_singular的话,我们希望添加 post_name
。然后,我们可以返回带有新类的 $classes
数组。
您可能会注意到,在我们的示例中,我们没有像Codex的示例那样应用 add_filter
。这是因为我们将把所有前端相关的过滤器和操作添加到 define_public_hooks
私有函数中,将后端相关的操作和过滤器添加到 define_admin_hooks
。如果您还记得,这些函数位于类 class-wp-cbf.php
文件中的 includes
文件夹中。
让我们这样做,并将我们的操作和过滤器添加到 includes/class-wpcbf.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /** * * includes/class-wp-cbf.php * **/ /** * Register all of the hooks related to the public-facing functionality * of the plugin. * * @since 1.0.0 * @access private */ private function define_public_hooks() { $plugin_public = new Wp_Cbf_Public( $this->get_plugin_name(), $this->get_version() ); /* * The following actions are commented out as we won't need any added style or script to our theme $this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_styles' ); $this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_scripts' ); */ // Below are our "public" frontend related actions and filters hooks // Cleanup - Actions and filters //Actions $this->loader->add_action( 'init', $plugin_public, 'wp_cbf_cleanup' ); $this->loader->add_action( 'wp_loaded', $plugin_public, 'wp_cbf_remove_comments_inline_styles' ); $this->loader->add_action( 'wp_loaded', $plugin_public, 'wp_cbf_remove_gallery_styles' ); $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'wp_cbf_cdn_jquery', PHP_INT_MAX); //Filters $this->loader->add_filter('wp_headers', $plugin_public, 'wp_cbf_remove_x_pingback'); $this->loader->add_filter( 'body_class', $plugin_public, 'wp_cbf_body_class_slug' ); } |
首先,您会注意到,我已经注释掉了 enqueue_styles
, enqueue_scripts
。这是因为这个插件不会添加任何 CSS或 Javascript到我们的网站。如果您需要将插件添加到您的网站的前端,则必须将一些代码写入这些文件 (public/css/wp-cbf-public.css
, public/js/wp-cbf-public.js
)。我们也留下了这两个行动没有评论。
我们增加了4个动作和2个过滤器,对应于我们的6个功能。让我们解释一下其中一个钩子呼叫。我们将保留我们的 body_class
子块例子:
1 | $this->loader->add_filter( 'body_class', $plugin_public, 'wp_cbf_body_class_slug' ); |
查看 includes/class-wp-cbf-loader.php
中的 add_filter
函数(这是我们使用 $this->loader->add_filter
调用的函数),我们有以下内容:
1 2 3 4 | public function add_filter( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) { $this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $accepted_args ); } |
所以我们很容易将它解密为:
$hook
is'body_class'
$component
is$plugin_public
定义在开头define_public_hook
我们将在所有 “public”(frontend)钩子调用中使用它$callback
在这里,我们的函数名wp_cbf_body_class_slug
来自class-wp-cbf-public.php
$priority
默认值为10时,我们不需要从这个调用中指定它,但有时您需要使用钩子来获得更高的优先级,所以您只需在这里添加一个数字(仅为整数)$accepted_args
默认值为1,这里相同,我们只有一个参数传递给我们的函数,所以我们不需要指定它,但是根据您想要调用的钩子,您需要根据文档来调整它
当然,根据您想要更改的内容,您必须搜索文档,以了解需要触发哪个 action/filter以及针对哪个钩子,所有这些都带有什么参数和优先级。
亲爱的,让我们看看现在的结果是什么。
回到你的网站管理和第一次取消检查 ‘Add Post slug to body class`。如果它被选中,保存并转到任何页面或单个帖子。我将转到初始WordPress安装中的默认 “Sample Page”,并从开发人员工具中检查我们的页面主体类。下面是您应该拥有的内容(或多或少取决于您可能已经安装和激活的插件):
现在,让我们激活我们的 “Add Post slug to body class”,然后你应该得到与下面的截图相同的结果。 post/page段现在添加到 body类中:
砰!我们的插件改变了我们的前端,如我们所愿。我们现在只需选中或取消复选框,我们将能够做到这一点,每一个网站,我们想现在。Plugins rock!
当然,你可以,你应该检查我们的所有其他选项发生了什么。现在是我们添加后端函数的时候了。
后端函数
打开 admin/class-wp-cbf-admin.php
之后 validate()
函数, 添加如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | /** * * admin/class-wp-cbf-admin.php * **/ ... public function __construct( $plugin_name, $version ) { $this->plugin_name = $plugin_name; $this->version = $version; $this->wp_cbf_options = get_option($this->plugin_name); } ... /** * Login page customizations Functions * * @since 1.0.0 */ private function wp_cbf_login_logo_css(){ if(isset($this->wp_cbf_options['login_logo_id']) && !empty($this->wp_cbf_options['login_logo_id'])){ $login_logo = wp_get_attachment_image_src($this->wp_cbf_options['login_logo_id'], 'thumbnail'); $login_logo_url = $login_logo[0]; $login_logo_css = "body.login h1 a {background-image: url(".$login_logo_url."); width:253px; height:102px; background-size: contain;}"; return $login_logo_css; } } // Get Background color is set and different from #fff return it's css private function wp_cbf_login_background_color(){ if(isset($this->wp_cbf_options['login_background_color']) && !empty($this->wp_cbf_options['login_background_color']) ){ $background_color_css = "body.login{ background:".$this->wp_cbf_options['login_background_color']."!important;}"; return $background_color_css; } } // Get Button and links color is set and different from #00A0D2 return it's css private function wp_cbf_login_button_color(){ if(isset($this->wp_cbf_options['login_button_primary_color']) && !empty($this->wp_cbf_options['login_button_primary_color']) ){ $button_color = $this->wp_cbf_options['login_button_primary_color']; $border_color = $this->sass_darken($button_color, 10); $message_color = $this->sass_lighten($button_color, 10); $button_color_css = "body.login #nav a, body.login #backtoblog a { color: ".$button_color." !important; } .login .message { border-left: 4px solid ".$message_color."; } body.login #nav a:hover, body.login #backtoblog a:hover { color: ". $border_color." !important; } body.login .button-primary { background: ".$button_color."; /* Old browsers */ background: -moz-linear-gradient(top, ".$button_color." 0%, ". $border_color.", 10%) 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,".$button_color."), color-stop(100%, ". $border_color.", 10%))); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, ".$button_color." 0%, ". $border_color.", 10%) 100%); /* Chrome10+,Safari5.1+ */ background: -o-linear-gradient(top, ".$button_color." 0%, ". $border_color.", 10%) 100%); /* Opera 11.10+ */ background: -ms-linear-gradient(top, ".$button_color." 0%, ". $border_color.", 10%) 100%); /* IE10+ */ background: linear-gradient(to bottom, ".$button_color." 0%, ". $border_color.", 10%) 100%); /* W3C */ -webkit-box-shadow: none!important; box-shadow: none !important; border-color:". $border_color."!important; } body.login .button-primary:hover, body.login .button-primary:active { background: ". $border_color."; /* Old browsers */ background: -moz-linear-gradient(top, ". $border_color." 0%, ". $border_color.", 10%) 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,". $border_color."), color-stop(100%,". $border_color.", 10%))); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, ". $border_color." 0%,". $border_color.", 10%) 100%); /* Chrome10+,Safari5.1+ */ background: -o-linear-gradient(top, ". $border_color." 0%,". $border_color.", 10%) 100%); /* Opera 11.10+ */ background: -ms-linear-gradient(top, ". $border_color." 0%,". $border_color.", 10%) 100%); /* IE10+ */ background: linear-gradient(to bottom, ". $border_color." 0%,". $border_color.", 10%) 100%); /* W3C */ } body.login input[type=checkbox]:checked:before{ color:".$button_color."!important; } body.login input[type=checkbox]:focus, body.login input[type=email]:focus, body.login input[type=number]:focus, body.login input[type=password]:focus, body.login input[type=radio]:focus, body.login input[type=search]:focus, body.login input[type=tel]:focus, body.login input[type=text]:focus, body.login input[type=url]:focus, body.login select:focus, body.login textarea:focus { border-color: ".$button_color."!important; -webkit-box-shadow: 0 0 2px ".$button_color."!important; box-shadow: 0 0 2px ".$button_color."!important; }"; return $button_color_css; } } // Write the actually needed css for login customizations public function wp_cbf_login_css(){ if( !empty($this->wp_cbf_options['login_logo_id']) || $this->wp_cbf_login_background_color() != null || $this->wp_cbf_login_button_color() != null){ echo '<style>'; if( !empty($this->wp_cbf_options['login_logo_id'])){ echo $this->wp_cbf_login_logo_css(); } if($this->wp_cbf_login_background_color() != null){ echo $this->wp_cbf_login_background_color(); } if($this->wp_cbf_login_button_color() != null){ echo $this->wp_cbf_login_button_color(); } echo '</style>'; } } /** * Utility functions * * @since 1.0.0 */ private function sass_darken($hex, $percent) { preg_match('/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i', $hex, $primary_colors); str_replace('%', '', $percent); $color = "#"; for($i = 1; $i <= 3; $i++) { $primary_colors[$i] = hexdec($primary_colors[$i]); $primary_colors[$i] = round($primary_colors[$i] * (100-($percent*2))/100); $color .= str_pad(dechex($primary_colors[$i]), 2, '0', STR_PAD_LEFT); } return $color; } private function sass_lighten($hex, $percent) { preg_match('/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i', $hex, $primary_colors); str_replace('%', '', $percent); $color = "#"; for($i = 1; $i <= 3; $i++) { $primary_colors[$i] = hexdec($primary_colors[$i]); $primary_colors[$i] = round($primary_colors[$i] * (100+($percent*2))/100); $color .= str_pad(dechex($primary_colors[$i]), 2, '0', STR_PAD_LEFT); } return $color; } |
由于您可以在这里完全不同,这与我们正在添加与管理相关的功能这一事实无关。这仅仅是因为如何使用这些特定的函数,所以这也是一个很好的例子。但是,不变的是,我们必须在 __construct
函数中添加对插件选项的引用: $this->wp_cbf_options = get_option($this->plugin_name);
然后我们有三个私有函数,它们只是返回一部分 CSS代码:
private function wp_cbf_login_logo_css()
returns$login_logo_css
private function wp_cbf_login_background_color()
returns$background_color_css
- 返回较长的CSS
private function wp_cbf_login_button_color()
as$button_color_css
在这三个私有函数下面,我们将在钩子定义中作为回调调用函数。
然后,您可能会想知道最后两个私有函数是什么。这些只是 helpers to emulate sass darken
和 lighten
HSL功能,以便我们可以得到一些稍微不同的颜色,我们的 hover
和 active
状态上的按钮或链接。
基本上,这里我们有一个函数,它将编写一个 <style>...our CSS code...</style>
使用前三个私有函数返回的 CSS代码标记到登录页面。
让我们把钩子加进去 includes/class-wp-cbf.php
里面的 define_admin_hooks()
函数中:
1 2 3 4 5 6 7 8 | /** * * includes/class-wp-cbf.php * **/ //Admin Customizations $this->loader->add_action( 'login_enqueue_scripts', $plugin_admin, 'wp_cbf_login_css' ); |
是的,是一条船!在这里,我们使用完全相同的 $this->loader->add_action
,唯一的更改是在 $component
上,我们在这里调用 $plugin_admin
而不是 $plugin_public
。
我们来测试一下!一旦您添加了徽标图像,并为您的登录背景、主按钮和链接颜色选择了颜色,保存并退出,一旦指向登录页面,您将看到您的徽标、背景和按钮以及链接颜色根据您在插件设置页面中的选择而改变。
太棒了! 你现在有了一个功能齐全的插件。你将能够重用所有的网站,只需上传和配置它的设置。
测试我们的插件
所以我们现在对插件很满意,我们应该测试它的所有功能,但是如果您想进一步研究,我建议您在开发时将开发人员插件添加到您的插件中,它将为您提供所有必要的工具来测试您的插件/主题作为废弃的通知、PHP/MYSQL控制台等等。
另一个有趣的步骤是使用一些 BDD或行为驱动的开发,这不是本教程的主题,但可能是一个有趣的未来文章,如果您想自己检查它,一定要看看 Codeception。
无论如何,一旦你确信你的插件工作得很好,没有任何错误或注意到,你现在就可以把它发送给WordPress团队了。
将我们的插件发送给 WordPress存储库团队检查
嗯,你已经做了所有这些工作,并想与世界分享它,这是伟大的,但你的插件将需要经过审查的WordPress之前,它可以托管在WordPress插件库。
在你走之前,阅读 how to send your plugin for review,你可以让它成为一个档案(.zip),并准备好分享(例如在你的 Dropbox或 Google Drive)。
如果还没有完成,还可以在 WordPress.org上创建一个帐户。
然后,提交后,你将只需耐心,这些家伙有时有大量的插件审查。
一旦获得批准,您将不得不将插件发送到 [WordPress SVN repository][29],但在此之前,请确保您的 “readme.txt”文件是 is valid的。
结论
就这样吧。我们已经建立了一个完全功能的WordPress插件从零(或几乎)感谢 WordPress plugin boilerplate。我们在这里已经讨论了很多,从为什么要构建一个插件,从哪里开始,插件编码本身,以及如何保持它的组织和干净。
我希望这会对你们有所帮助,希望你们和我一样享受这个话题。
接下来一个有趣的帖子将会是一些行为驱动的开发 (BDD),但是我很乐意先听听你们的想法。
一个较小的帖子也可以通过插件设置,页面样式和添加一些互动与一些 Javascript作为一个例子添加制表符。
当然,要了解有关WordPress插件的更多信息,请务必检查 WordPress Plugin Guideline。
你可以找到 full plugin on the WordPress repository。
谢谢!
原文:https://scotch.io/tutorials/how-to-build-a-wordpress-plugin-part-2