wordpress插件开发_WordPress插件开发的真实示例

本文提供了一个WordPress插件开发的真实示例,用于创建商业地点列表。插件包括自定义内容类型、简码和小部件,便于展示和管理多个位置的详细信息。通过设置内容类型、交易时间、小部件和简码输出,为网站添加位置展示功能。此外,还介绍了如何处理激活和停用插件时的重写规则,以及加载公共和管理员脚本和样式。
摘要由CSDN通过智能技术生成

wordpress插件开发

To make the most out of this tutorial, you should have a basic understanding of topics such as actions, filters, shortcodes, widgets and object orientated design.

为了充分利用本教程,您应该对主题有基本的了解,例如actionsfiltersshortcodeswidgetsobject orientated design

If you’re interested in brushing up on the basics, you can read my previous article An Introduction to WordPress Plugin Development. It will help you to build a solid understanding of the concepts and ideas we’ll be using throughout this tutorial.

如果您对基础知识感兴趣,可以阅读我以前的文章WordPress插件开发简介 。 它将帮助您对我们将在本教程中使用的概念和想法有深入的了解。

wordpress-plugin

真实示例-商业地点列表 (Real World Example – Business Locations List)

Let’s jump straight into a real world example of how plugins can be used to address issues and add functionality to your website.

让我们直接进入一个真实的示例,说明如何使用插件解决问题并向您的网站添加功能。

Often businesses will want to showcase the various locations or offices that they have. While these locations could be created as pages, it would be better to create them as a content type that matches their unique information and provides the user with an easy interface to display these locations.

通常,企业会希望展示其所处的各个位置或办公室。 尽管可以将这些位置创建为页面,但最好将它们创建为与它们的唯一信息相匹配的内容类型,并为用户提供显示这些位置的简便界面。

While we could build this functionality into our child theme, it is generic enough that we could build it as a plugin to make implementing it into other sites simple and easy.

虽然我们可以将此功能构建到子主题中,但它具有足够的通用性,因此我们可以将其构建为插件,从而使将其轻松实现到其他站点中变得容易。

Our example will cover the development of a plugin that processes and outputs locations. These locations will be a custom content type with additional meta information for storing location specific data. The plugin will also demonstrate the various ways in which you can output your information (such as the individual location page, a location listing widget and a location listing shortcode)

我们的示例将涵盖处理和输出位置的插件的开发。 这些位置将是具有附加元信息的自定义内容类型,用于存储位置特定的数据。 该插件还将演示输出信息的各种方式(例如单个位置页面,位置列表小部件和位置列表简码)

plugin_location_final

设置一切 (Setting Everything Up)

Lets set everything up, jump to to your plugin directory and create the following folder / file structure

让我们进行所有设置,跳至您的插件目录并创建以下文件夹/文件结构

  • wp_simple_location_plugin

    wp_simple_location_plugin

    • css

      CSS

      • wp_location_public_styles.css

        wp_location_public_styles.css
      • wp_location_admin_styles.css

        wp_location_admin_styles.css
    • inc

      公司

      • wp_location_widget.php

        wp_location_widget.php
      • wp_location_shortcode.php

        wp_location_shortcode.php
    • wp_simple_location_plugin.php

      wp_simple_location_plugin.php

The top level file wp_simple_location_plugin.php is going to be our main file. It is here we’ll load our styles from our CSS directory and also the additional PHP files from the includes directory.

顶级文件wp_simple_location_plugin.php将成为我们的主文件。 在这里,我们将从CSS目录中加载样式,并从includes目录中加载其他PHP文件。

主要位置类别 (Main Location Class)

Inside the wp_simple_location_plugin.php file we’ll define our core functionality for the plugin. We’ll also include our additional files that are needed for the widget and shortcode functionality.

wp_simple_location_plugin.php文件中,我们将定义插件的核心功能。 我们还将包括小部件和简码功能所需的其他文件。

没有直接访问 (No Direct Access)

A recommendation is to block direct access to your PHP files by checking if the ABSPATH variable is defined (if so we terminate execution). Add the following code directly after your opening PHP tag:

建议通过检查是否定义了ABSPATH变量来阻止对PHP文件的直接访问(如果这样我们将终止执行)。 在打开PHP标记后直接添加以下代码:

defined( 'ABSPATH' ) or die( 'Nope, not accessing this' );

插件声明 (Plugin Declaration)

To get our plugin working it needs to have a plugin declaration defined. A plugin declaration is a set of comments that WordPress will look for and contains information about your plugin. This must be included as without it your plugin won’t appear in the WP plugin manager.

为了使我们的插件正常工作,需要定义一个插件声明。 插件声明是WordPress将寻找的一组注释,其中包含有关您的插件的信息。 必须包含它,因为没有它,您的插件将不会出现在WP插件管理器中。

<?php
/*
Plugin Name: WordPress Simple Location Plugin
Plugin URI:  https://github.com/simonrcodrington/Introduction-to-WordPress-Plugins---Location-Plugin
Description: Creates an interfaces to manage store / business locations on your website. Useful for showing location based information quickly. Includes both a widget and shortcode for ease of use.
Version:     1.0.0
Author:      Simon Codrington
Author URI:  http://www.simoncodrington.com.au
License:     GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/

You define your plugins name, description, version and other information here. The majority of this will be shown on the plugins administration page when users activate the plugin.

您可以在此处定义插件名称,描述,版本和其他信息。 当用户激活插件时,其中大部分将显示在插件管理页面上。

wp_simple_location类 (The wp_simple_location Class)

We now create the shell of the wp_simple_location class. This class will contain a majority of the functionality for this plugin and will be used to hold our properties and methods.

现在,我们创建wp_simple_location类的外壳。 此类将包含此插件的大部分功能,并将用于保存我们的属性和方法。

class wp_simple_location{

}

包括简码和小部件文件 (Include the shortcode and widget files)

Since we’ll be using both a widget and a shortcode, the functionality for each has been split into its own file.

由于我们将同时使用小部件和简码,因此每种功能均已拆分为自己的文件。

Go to the end of the wp_simple_location class and add the following:

转到wp_simple_location类的末尾并添加以下内容:

//include shortcodes
include(plugin_dir_path(__FILE__) . 'inc/wp_location_shortcode.php');
//include widgets
include(plugin_dir_path(__FILE__) . 'inc/wp_location_widget.php');

This will load both of our files which we’ll look at later.

这将加载我们稍后将要查看的两个文件。

类属性 (Class Properties)

Class properties are variables that can be accessed from functions within the class.

类属性是可以从类中的函数访问的变量。

Declaring properties makes it easier to access commonly used elements.

声明属性可以更轻松地访问常用元素。

We only have one property in this function; an array of days that will be used for the trading hours of the location. Let’s define the value as an empty array as later on we’ll populate it with our values.

我们在此功能中只有一个属性; 一连串的天数,将用于该位置的交易时间。 让我们将值定义为一个空数组,稍后再用我们的值填充它。

//properties
private $wp_location_trading_hour_days = array();

_construct函数 (The _construct Function)

The _construct function is an important part of the plugin as it acts as the master function, letting us handle and execute other functions.

_construct函数是插件的重要组成部分,因为它充当主函数,使我们能够处理和执行其他函数。

This function is a magic function; Magic functions are special functions added to PHP5 that are triggered on certain conditions automatically. This function is triggered the instant that our class is instantiated (the class is created and assigned to a variable).

此功能是magic功能; 魔术函数是PHP5中添加的特殊函数,它们会在某些条件下自动触发。 实例化我们的类后立即触发该函数(创建该类并将其分配给变量)。

In a plugin you will use this function to add all of your actions, filters and function calls. You could add these hooks outside the class but it’s fine to leave them here.

在插件中,您将使用此函数添加所有操作,过滤器和函数调用。 您可以将这些钩子添加到类之外,但是可以将它们保留在此处。

Copy the following code. We’ll go through each of the elements one at a time so you can see what is happening.

复制以下代码。 我们将一次浏览每个元素,以便您看到正在发生的事情。

//magic function (triggered on initialization)
public function __construct(){

    add_action('init', array($this,'set_location_trading_hour_days')); //sets the default trading hour days (used by the content type)
    add_action('init', array($this,'register_location_content_type')); //register location content type
    add_action('add_meta_boxes', array($this,'add_location_meta_boxes')); //add meta boxes
    add_action('save_post_wp_locations', array($this,'save_location')); //save location
    add_action('admin_enqueue_scripts', array($this,'enqueue_admin_scripts_and_styles')); //admin scripts and styles
    add_action('wp_enqueue_scripts', array($this,'enqueue_public_scripts_and_styles')); //public scripts and styles
    add_filter('the_content', array($this,'prepend_location_meta_to_content')); //gets our meta data and dispayed it before the content

    register_activation_hook(__FILE__, array($this,'plugin_activate')); //activate hook
    register_deactivation_hook(__FILE__, array($this,'plugin_deactivate')); //deactivate hook

}

The register_activation_hook and register_deactivation_hook functions are used to hook into functions when the plugin is activated or deactivated. We use these hooks to ensure our content type (our locations) have been added correctly and to flush our permalinks (so we can use pretty permalinks)

当激活或停用插件时, register_activation_hookregister_deactivation_hook函数用于挂接到函数。 我们使用这些挂钩来确保正确添加了我们的内容类型(我们的位置)并刷新了永久链接(因此我们可以使用漂亮的永久链接)

设置位置交易时间天数 (Setting the Location Trading Hour Days)

Our plugin allows the admin to define the opening and closing times for different days on single location basis.

我们的插件允许管理员在单个位置上定义不同日期的营业时间和营业时间。

The backend admin where you enter these details looks at our property $wp_location_trading_hour_days which contains an array of our days. We need to call the set_location_trading_hour_days function to set the days we want to display location trading hours for.

您在其中输入这些详细信息的后端管理员查看我们的属性$wp_location_trading_hour_days ,其中包含我们的日子。 我们需要调用set_location_trading_hour_days函数来设置要显示位置交易时间的天数。

//set the default trading hour days (used in our admin backend)
public function set_location_trading_hour_days(){

    //set the default days to use for the trading hours
    $this->wp_location_trading_hour_days = apply_filters('wp_location_trading_hours_days', 
        array('monday' => 'Monday',
              'tuesday' => 'Tuesday',
              'wednesday' => 'Wednesday',
              'thursday' => 'Thursday',
              'friday' => 'Friday',
              'saturday' => 'Saturday',
              'sunday' => 'Sunday',
        )
    );      
}

When assigning the values of the array we also called the wp_location_trading_hours_days filter. This means that a theme or another plugin could redefine the days that locations are open for business (they might filter the array and add ‘holidays’ to the array so they can enter a trading hour value for that day).

在分配数组的值时,我们也称为wp_location_trading_hours_days过滤器。 这意味着主题或其他插件可以重新定义营业地点的营业日(它们可以过滤阵列并在阵列中添加“假期”,以便他们可以输入当天的交易时间值)。

设置位置交易时间天数 (Setting the Location Trading Hour Days)

Here we define our new custom location content type that will be used in our plugin.

在这里,我们定义了将在插件中使用的新的自定义位置内容类型。

We define the labels and arguments for the content type and pass our arguments to the register_post_type function.

我们为内容类型定义标签和参数,然后将参数传递给register_post_type函数。

The codex page on custom content types explains all of the options you can specify. For our content type we want basic support for the Title, Editor and Featured Image.

自定义内容类型的法典页面说明了您可以指定的所有选项。 对于我们的内容类型,我们需要标题,编辑器和特色图像的基本支持。

//register the location content type
public function register_location_content_type(){
     //Labels for post type
     $labels = array(
           'name'               => 'Location',
           'singular_name'      => 'Location',
           'menu_name'          => 'Locations',
           'name_admin_bar'     => 'Location',
           'add_new'            => 'Add New', 
           'add_new_item'       => 'Add New Location',
           'new_item'           => 'New Location', 
           'edit_item'          => 'Edit Location',
           'view_item'          => 'View Location',
           'all_items'          => 'All Locations',
           'search_items'       => 'Search Locations',
           'parent_item_colon'  => 'Parent Location:', 
           'not_found'          => 'No Locations found.', 
           'not_found_in_trash' => 'No Locations found in Trash.',
       );
       //arguments for post type
       $args = array(
           'labels'            => $labels,
           'public'            => true,
           'publicly_queryable'=> true,
           'show_ui'           => true,
           'show_in_nav'       => true,
           'query_var'         => true,
           'hierarchical'      => false,
           'supports'          => array('title','thumbnail','editor'),
           'has_archive'       => true,
           'menu_position'     => 20,
           'show_in_admin_bar' => true,
           'menu_icon'         => 'dashicons-location-alt',
           'rewrite'            => array('slug' => 'locations', 'with_front' => 'true')
       );
       //register post type
       register_post_type('wp_locations', $args);
}

When you have this you should see a new menu for your post type

收到邮件后,您应该会看到一个新菜单

plugin_location_context_menu

向位置内容类型添加元框' (Adding a Meta Box to the Location Content Type’)

We define a custom meta box that will be displayed on our location page. This box will contain all of our additional fields we want to save as meta data (such as a phone number, email, address).

我们定义了一个自定义元框,该框将显示在我们的位置页面上。 此框将包含我们要另存为元数据的所有其他字段(例如电话号码,电子邮件,地址)。

Inside our function we call the add_meta_box() function and supply our arguments.

在我们的函数内部,我们调用add_meta_box()函数并提供参数。

//adding meta boxes for the location content type*/
public function add_location_meta_boxes(){

    add_meta_box(
        'wp_location_meta_box', //id
        'Location Information', //name
        array($this,'location_meta_box_display'), //display function
        'wp_locations', //post type
        'normal', //location
        'default' //priority
    );
}

Our third value to add_meta_box is the function that will display the output for the box. We’are calling the location_meta_box_display function which is the next function we’ll add to our class

我们添加到add_meta_box第三个值是将显示框的输出的函数。 我们正在调用location_meta_box_display函数,这是我们将添加到类中的下一个函数

位置元框显示 (Location meta box display)

This is the function that will be called from our location meta box and It displays additional fields that the admin can use to save information about their location.

这是将从我们的位置元框中调用的函数,它显示管理员可以用来保存有关其位置的信息的其他字段。

//display function used for our custom location meta box*/
public function location_meta_box_display($post){

    //set nonce field
    wp_nonce_field('wp_location_nonce', 'wp_location_nonce_field');

    //collect variables
    $wp_location_phone = get_post_meta($post->ID,'wp_location_phone',true);
    $wp_location_email = get_post_meta($post->ID,'wp_location_email',true);
    $wp_location_address = get_post_meta($post->ID,'wp_location_address',true);

    ?>
    <p>Enter additional information about your location </p>
    <div class="field-container">
        <?php 
        //before main form elementst hook
        do_action('wp_location_admin_form_start'); 
        ?>
        <div class="field">
            <label for="wp_location_phone">Contact Phone</label>
            <small>main contact number</small>
            <input type="tel" name="wp_location_phone" id="wp_location_phone" value="<?php echo $wp_location_phone;?>"/>
        </div>
        <div class="field">
            <label for="wp_location_email">Contact Email</label>
            <small>Email contact</small>
            <input type="email" name="wp_location_email" id="wp_location_email" value="<?php echo $wp_location_email;?>"/>
        </div>
        <div class="field">
            <label for="wp_location_address">Address</label>
            <small>Physical address of your location</small>
            <textarea name="wp_location_address" id="wp_location_address"><?php echo $wp_location_address;?></textarea>
        </div>
        <?php
        //trading hours
        if(!empty($this->wp_location_trading_hour_days)){
            echo '<div class="field">';
                echo '<label>Trading Hours </label>';
                echo '<small> Trading hours for the location (e.g 9am - 5pm) </small>';
                //go through all of our registered trading hour days
                foreach($this->wp_location_trading_hour_days as $day_key => $day_value){
                    //collect trading hour meta data
                    $wp_location_trading_hour_value =  get_post_meta($post->ID,'wp_location_trading_hours_' . $day_key, true);
                    //dsiplay label and input
                    echo '<label for="wp_location_trading_hours_' . $day_key . '">' . $day_key . '</label>';
                    echo '<input type="text" name="wp_location_trading_hours_' . $day_key . '" id="wp_location_trading_hours_' . $day_key . '" value="' . $wp_location_trading_hour_value . '"/>';
                }
            echo '</div>';
        }       
        ?>
    <?php 
    //after main form elementst hook
    do_action('wp_location_admin_form_end'); 
    ?>
    </div>
    <?php

}

Let’s run through what this function does

让我们来看一下此函数的作用

  • First it creates a secure nonce field for the meta box (nonces are used to verify that a submit action came from the correctly place).

    首先,它为元框创建一个安全的随机数字段( 随机数用于验证提交操作是否来自正确的位置 )。

  • It collects our phone, email and address meta information (if we have any).

    它收集我们的电话,电子邮件和地址元信息(如果有的话)。
  • Just before the form starts we add the wp_location_admin_form_start action hook. This will let other plugins or themes hook into this location, allowing additional fields or information to be displayed.

    在表单开始之前,我们添加了wp_location_admin_form_start操作挂钩。 这将使其他插件或主题挂接到该位置,从而允许显示其他字段或信息。

  • Displays the phone, email and address fields (and pre-populates them if we have any previous values).

    显示电话,电子邮件和地址字段(如果我们之前有任何值,则预填充它们)。
  • Displays the list of trading hour days for the location. Here the admin can define the trading hours on a day by day basis. These days can be dynamic as they are attached to the wp_location_trading_hours_days filter.

    显示位置的交易时间天数列表。 管理员可以在这里每天定义交易时间。 这些天可能是动态的,因为它们被附加到wp_location_trading_hours_days过滤器中。

  • Just before the form finishes we add the wp_location_admin_form_end action hook. This will let other plugins or the themes hook into this location, allowing additional fields or information to be displayed.

    在表单完成之前,我们添加了wp_location_admin_form_end操作挂钩。 这将使其他插件或主题挂接到该位置,从而允许显示其他字段或信息。

You should see your meta box displayed on your location. It should look something like this

您应该看到您的位置显示了您的元框。 它应该看起来像这样

plugin_location_background_box

在激活时注册内容类型和刷新重写规则 (Registering Content Type and Flushing Re-write Rules on Activate)

When a plugin first activates you can call a function to perform once off actions. That is what we do with the plugin_activate function.

插件首次激活时,您可以调用一个函数来执行一次性动作。 那就是我们使用plugin_activate函数plugin_activate

Even though we register our content type via the init hook, we still need to call the register_location_content_type from within it (to ensure that our content type has been added correctly)

即使我们通过init钩子注册了内容类型,我们仍然需要从其内部调用register_location_content_type (以确保正确添加了我们的内容类型)

We also flush the rewrite rules so that we can use pretty permalinks for our locations (we can have links such as example.com/location/mylocation instead of example.com/?p=144)

我们还刷新了重写规则,以便我们可以对位置使用漂亮的永久链接(我们可以使用诸如example.com/location/mylocation链接,而不是example.com/?p=144 )

//triggered on activation of the plugin (called only once)
public function plugin_activate(){  
    //call our custom content type function
    $this->register_location_content_type();
    //flush permalinks
    flush_rewrite_rules();
}

停用时刷新刷新规则 (Flushing Re-write Rules on Deactivation)

Plugin deactivate is triggered when we deactivate our plugin. Since we’re removing the plugin and the plugin defined a custom content type, we want to use this chance to flush our re-write rules for consistency.

当我们停用插件时,会触发插件停用。 由于我们要删除插件,并且插件定义了自定义内容类型,因此我们希望借此机会刷新重新编写规则以保持一致性。

//trigered on deactivation of the plugin (called only once)
public function plugin_deactivate(){
    //flush permalinks
    flush_rewrite_rules();
}

在单个位置显示我们的位置元信息 (Displaying Our Location Meta Information on a Single Location)

Since we’re defining additional meta information for each location, we create a function that handles the display of this extra information when viewing the single location page.

由于我们正在为每个位置定义其他元信息,因此我们创建了一个函数,用于在查看单个位置页面时处理这些额外信息的显示。

The prepend_location_meta_to_content function is hooked onto the the_content filter which means we can add our additional information before the pages main content.

prepend_location_meta_to_content函数挂接到the_content过滤器上,这意味着我们可以在页面主要内容之前添加其他信息。

public function prepend_location_meta_to_content($content){

    global $post, $post_type;

    //display meta only on our locations (and if its a single location)
    if($post_type == 'wp_locations' && is_singular('wp_locations')){

        //collect variables
        $wp_location_id = $post->ID;
        $wp_location_phone = get_post_meta($post->ID,'wp_location_phone',true);
        $wp_location_email = get_post_meta($post->ID,'wp_location_email',true);
        $wp_location_address = get_post_meta($post->ID,'wp_location_address',true);

        //display
        $html = '';

        $html .= '<section class="meta-data">';

        //hook for outputting additional meta data (at the start of the form)
        do_action('wp_location_meta_data_output_start',$wp_location_id);

        $html .= '<p>';
        //phone
        if(!empty($wp_location_phone)){
            $html .= '<b>Location Phone</b> ' . $wp_location_phone . '</br>';
        }
        //email
        if(!empty($wp_location_email)){
            $html .= '<b>Location Email</b> ' . $wp_location_email . '</br>';
        }
        //address
        if(!empty($wp_location_address)){
            $html .= '<b>Location Address</b> ' . $wp_location_address . '</br>';
        }
        $html .= '</p>';

        //location
        if(!empty($this->wp_location_trading_hour_days)){
            $html .= '<p>';
            $html .= '<b>Location Trading Hours </b></br>';
            foreach($this->wp_location_trading_hour_days as $day_key => $day_value){
                $trading_hours = get_post_meta($post->ID, 'wp_location_trading_hours_' . $day_key , true);
                $html .= '<span class="day">' . $day_key . '</span><span class="hours">' . $trading_hours . '</span></br>';
            }
            $html .= '</p>';
        }

        //hook for outputting additional meta data (at the end of the form)
        do_action('wp_location_meta_data_output_end',$wp_location_id);

        $html .= '</section>';
        $html .= $content;

        return $html;  


    }else{
        return $content;
    }

}

Let’s run through what this function does:

让我们来看一下此函数的作用:

  • Since the function is added to the the_content hook, it will run every time a page is loaded. As such we use the global $post and $post_type variables to ensure we’re on a single location page only

    由于该函数已添加到the_content挂钩中,因此它将在每次加载页面时运行。 因此,我们使用全局$post$post_type变量来确保仅在单个位置页面上

  • We collect our basic location information such as the email, phone and address.

    我们收集基本的位置信息,例如电子邮件,电话和地址。
  • Just as we’re about to display our meta information we call the wp_location_meta_data_output_start action hook. This action will let other plugins or themes hook into the starting output of the meta information (if someone added a new field to the location this hook can be used to display the saved info).

    正如我们将要显示元信息一样,我们将其wp_location_meta_data_output_start操作挂钩。 此操作将使其他插件或主题挂接到元信息的初始输出中(如果有人在该位置添加了新字段,则此挂勾可用于显示已保存的信息)。

  • We output the email, phone and address information.

    我们输出电子邮件,电话和地址信息。
  • We go through our wp_location_trading_hour_daysvariable and see if we have days defined. If we do we loop through all of them and collect its trading hours and display them.

    我们遍历wp_location_trading_hour_days变量,看看是否定义了日期。 如果我们这样做,我们将遍历所有它们并收集其交易时间并显示它们。

  • Just before we finish all of our output we call the wp_location_meta_data_output_end action. This action will let someone output additional information before the closing of the location meta.

    在完成所有输出之前,我们调用wp_location_meta_data_output_end操作。 此操作将使某人在关闭位置元数据之前输出其他信息。

获取位置清单 (Getting the Location Listing Out)

We create a function who’s purpose is to build the HTML for a listing of our locations.

我们创建一个函数,该函数的目的是为位置列表生成HTML。

The get_locations_output function is by both the location shortcode and the location widget to generate its mark up.

get_locations_output函数通过位置简码和位置小部件来生成其标记。

Since it’s used for multiple purposes, this function has several important actions. I’ll will break them all down step by step.

由于此功能用于多种用途,因此它具有几个重要的作用。 我将逐步分解它们。

//main function for displaying locations (used for our shortcodes and widgets)
public function get_locations_output($arguments = ""){

    //default args
    $default_args = array(
        'location_id'   => '',
        'number_of_locations'   => -1
    );

    //update default args if we passed in new args
    if(!empty($arguments) && is_array($arguments)){
        //go through each supplied argument
        foreach($arguments as $arg_key => $arg_val){
            //if this argument exists in our default argument, update its value
            if(array_key_exists($arg_key, $default_args)){
                $default_args[$arg_key] = $arg_val;
            }
        }
    }

    //find locations
    $location_args = array(
        'post_type'     => 'wp_locations',
        'posts_per_page'=> $default_args['number_of_locations'],
        'post_status'   => 'publish'
    );
    //if we passed in a single location to display
    if(!empty($default_args['location_id'])){
        $location_args['include'] = $default_args['location_id'];
    }

    //output
    $html = '';
    $locations = get_posts($location_args);
    //if we have locations 
    if($locations){
        $html .= '<article class="location_list cf">';
        //foreach location
        foreach($locations as $location){
            $html .= '<section class="location">';
                //collect location data
                $wp_location_id = $location->ID;
                $wp_location_title = get_the_title($wp_location_id);
                $wp_location_thumbnail = get_the_post_thumbnail($wp_location_id,'thumbnail');
                $wp_location_content = apply_filters('the_content', $location->post_content);
                if(!empty($wp_location_content)){
                    $wp_location_content = strip_shortcodes(wp_trim_words($wp_location_content, 40, '...'));
                }
                $wp_location_permalink = get_permalink($wp_location_id);
                $wp_location_phone = get_post_meta($wp_location_id,'wp_location_phone',true);
                $wp_location_email = get_post_meta($wp_location_id,'wp_location_email',true);

                //apply the filter before our main content starts 
                //(lets third parties hook into the HTML output to output data)
                $html = apply_filters('wp_location_before_main_content', $html);

                //title
                $html .= '<h2 class="title">';
                    $html .= '<a href="' . $wp_location_permalink . '" title="view location">';
                        $html .= $wp_location_title;
                    $html .= '</a>';
                $html .= '</h2>';


                //image & content
                if(!empty($wp_location_thumbnail) || !empty($wp_location_content)){

                    $html .= '<p class="image_content">';
                    if(!empty($wp_location_thumbnail)){
                        $html .= $wp_location_thumbnail;
                    }
                    if(!empty($wp_location_content)){
                        $html .=  $wp_location_content;
                    }

                    $html .= '</p>';
                }

                //phone & email output
                if(!empty($wp_location_phone) || !empty($wp_location_email)){
                    $html .= '<p class="phone_email">';
                    if(!empty($wp_location_phone)){
                        $html .= '<b>Phone: </b>' . $wp_location_phone . '</br>';
                    }
                    if(!empty($wp_location_email)){
                        $html .= '<b>Email: </b>' . $wp_location_email;
                    }
                    $html .= '</p>';
                }

                //apply the filter after the main content, before it ends 
                //(lets third parties hook into the HTML output to output data)
                $html = apply_filters('wp_location_after_main_content', $html);

                //readmore
                $html .= '<a class="link" href="' . $wp_location_permalink . '" title="view location">View Location</a>';
            $html .= '</section>';
        }
        $html .= '</article>';
        $html .= '<div class="cf"></div>';
    }

    return $html;
}
  • Firstly, the function has an optional argument called arguments. This is used because both the shortcode and the widget will pass options to the display (to customise exactly what will be returned).

    首先,该函数具有一个可选参数,称为arguments 。 之所以使用它,是因为简码和窗口小部件都会将选项传递给显示(以精确地自定义将返回的内容)。

  • The function defines a set of default arguments into the $default_args array. This will basically set the number of locations to -1 (all locations) and also set the location ID to nothing (meaning we want to display a list of locations, not just one by default).

    该函数在$default_args数组中定义了一组默认参数。 基本上,这会将位置数设置为-1(所有位置),并将位置ID设置为空(这意味着我们要显示位置列表,而不是默认显示一个)。

  • We check to see if the passed $arguments variable is not empty and is also an array (meaning we have passed an array of arguments). We go through all of the elements inside the $arguments array and check to see if any of the array keys matches anything in our $default_args array. If something matches we update the $default_args array.

    我们检查传递的$arguments变量是否不为空,并且是否为数组(表示我们已经传递了参数数组)。 我们遍历$arguments数组中的所有元素,并检查是否有任何数组键与$default_args数组中的任何内容匹配。 如果匹配,则更新$default_args数组。

  • We use the $default_args array to build a search for get_posts() which will find all of our locations (if we specified a single location we’ll just search for that)

    我们使用$default_args数组构建对get_posts()的搜索,该搜索将找到我们所有的位置(如果我们指定一个位置,我们将仅搜索该位置)

  • Now we start building our HTML output. We define our $html variable and start building our output.

    现在,我们开始构建HTML输出。 我们定义$html变量并开始构建输出。

  • We collect all of our information about the location (title, content, image, link etc) and get it ready for output.

    我们收集有关位置的所有信息(标题,内容,图像,链接等),并准备输出。
  • Before we continue collecting our output we all the wp_location_before_main_content filter on our $html variable. This will let third parties add extra content before the title of the location. This is useful as if we defined any extra fields in our admin we can use this to output them.

    在继续收集输出之前,我们在$html变量上使用了所有wp_location_before_main_content过滤器。 这将使第三方在位置标题之前添加额外的内容。 这很有用,就像我们在管理员中定义了任何其他字段一样,我们可以使用它来输出它们。

  • The title, image, content, phone and email are added to our output (conditionally checking if they exist).

    标题,图像,内容,电话和电子邮件将添加到我们的输出中(有条件地检查它们是否存在)。
  • Before we’re about to output the read more button we call our second filter, the wp_location_after_main_content filter. This will let third parties add content right before the button.

    在输出“更多”按钮之前,我们先调用第二个过滤器,即wp_location_after_main_content过滤器。 这将使第三方在按钮之前添加内容。

  • We add our read more button to the output and then return our $html variable.

    我们将read more按钮添加到输出中,然后返回$html变量。

保存位置的其他元信息 (Saving Additional Meta Information for Locations)

When we save a single location we want to run a function to collect and update our additional meta information (such as email, phone, address etc).

当我们保存一个位置时,我们要运行一个功能来收集和更新我们的其他元信息(例如电子邮件,电话,地址等)。

We use the save_location function for this purpose.

为此,我们使用save_location函数。

//triggered when adding or editing a location
public function save_location($post_id){

    //check for nonce
    if(!isset($_POST['wp_location_nonce_field'])){
        return $post_id;
    }   
    //verify nonce
    if(!wp_verify_nonce($_POST['wp_location_nonce_field'], 'wp_location_nonce')){
        return $post_id;
    }
    //check for autosave
    if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE){
        return $post_id;
    }

    //get our phone, email and address fields
    $wp_location_phone = isset($_POST['wp_location_phone']) ? sanitize_text_field($_POST['wp_location_phone']) : '';
    $wp_location_email = isset($_POST['wp_location_email']) ? sanitize_text_field($_POST['wp_location_email']) : '';
    $wp_location_address = isset($_POST['wp_location_address']) ? sanitize_text_field($_POST['wp_location_address']) : '';

    //update phone, memil and address fields
    update_post_meta($post_id, 'wp_location_phone', $wp_location_phone);
    update_post_meta($post_id, 'wp_location_email', $wp_location_email);
    update_post_meta($post_id, 'wp_location_address', $wp_location_address);

    //search for our trading hour data and update
    foreach($_POST as $key => $value){
        //if we found our trading hour data, update it
        if(preg_match('/^wp_location_trading_hours_/', $key)){
            update_post_meta($post_id, $key, $value);
        }
    }

    //location save hook 
    //used so you can hook here and save additional post fields added via 'wp_location_meta_data_output_end' or 'wp_location_meta_data_output_end'
    do_action('wp_location_admin_save',$post_id, $_POST);

}

Let’s analyse what we’re doing:

让我们分析一下我们在做什么:

  • We’re firstly checking our nonce and verifying that it exists (passed from the meta box). We also check to make sure we’re not auto-saving. Once we’re sure everything is ok we move on.

    我们首先检查随机数并验证其是否存在(从元框传递)。 我们还会检查以确保我们不会自动保存。 一旦确定一切正常,我们就继续。
  • We collect the phone, email and address information and sanitize them with the sanitize_text_field() function. They are assigned to variables and then used in our update_post_meta() function to save them to the location.

    我们收集电话,电子邮件和地址信息,并使用sanitize_text_field()函数对其进行sanitize_text_field() 。 它们被分配给变量,然后在我们的update_post_meta()函数中用于将它们保存到该位置。

  • Because our trading hours are dynamic and we have to collect and save them a little differently. Since we don’t know how many will exist we can’t extract them from the $_POST array by name. So we go through all of the $_POST variables and check to see if any of them start with wp_location_trading_hours_. If they do we update their value and save them as meta information.

    因为我们的交易时间是动态的,所以我们必须以不同的方式收集和保存它们。 由于我们不知道会存在多少,因此无法按名称从$_POST数组中提取它们。 因此,我们遍历了所有$_POST变量,并检查它们是否以wp_location_trading_hours_ 。 如果这样做,我们将更新其值并将其另存为元信息。

  • Finally, just before we finish we call the wp_location_admin_save action. This action will take current id of the location as $post_id and let a third party function collect additional information from the global $_POST and save them to the location.

    最后,在完成之前,我们调用wp_location_admin_save操作。 此操作会将位置的当前ID用作$post_id并让第三方功能从全局$_POST收集其他信息并将其保存到该位置。

加载我们的管理员和公共脚本和样式 (Loading Our Admin and Public Scripts and Styles)

We need to load additional CSS files for both the front end and back end of our website. We create two functions that will load any scripts or styles we need.

我们需要为网站的前端和后端加载其他CSS文件。 我们创建两个函数,这些函数将加载所需的任何脚本或样式。

Inside these CSS files are basic styling for the admin fields inside the meta box and also slight front end styling.

这些CSS文件中的内容是meta框内admin字段的基本样式,还包含一些前端样式。

The plugin will work just fine without any CSS so these can be safely omitted if you’re not interested. (If you don’t want to add these, also remember to remove their action call from the construct function).

该插件在没有任何CSS的情况下也可以正常工作,因此如果您不感兴趣,可以安全地忽略它们。 (如果不想添加它们,也请记住从构造函数中删除它们的动作调用)。

//enqueus scripts and stles on the back end
public function enqueue_admin_scripts_and_styles(){
    wp_enqueue_style('wp_location_admin_styles', plugin_dir_url(__FILE__) . '/css/wp_location_admin_styles.css');
}

//enqueues scripts and styled on the front end
public function enqueue_public_scripts_and_styles(){
    wp_enqueue_style('wp_location_public_styles', plugin_dir_url(__FILE__). '/css/wp_location_public_styles.css');

}

位置简码 (Location Shortcode)

Now we can look at the shortcode class that will be used in combination with our main class.

现在我们来看看将与我们的主类结合使用的shortcode类。

Adding shortcodes gives the admin an easy to use interface to showcase the various locations they might have. These shortcodes will also be customisable, allowing the admin to specify an exact location by its ID or all of them. When you use this shortcode on a page it should look similar to the following

添加简码使管理员可以轻松使用界面来展示他们可能拥有的各个位置。 这些短代码也可以自定义,从而允许管理员通过其ID或所有ID指定确切的位置。 当您在页面上使用此短代码时,它应类似于以下内容

plugin_location_shortcode_output

We’ll be working inside the wp_location_shortcode.phpfile

我们将在wp_location_shortcode.php文件中进行工作

拒绝直接访问 (Deny Direct Access)

Just like in our main PHP file we want to deny any direct access. Do add the following to the top of our file

就像在主PHP文件中一样,我们希望拒绝任何直接访问。 请将以下内容添加到文件顶部

defined( 'ABSPATH' ) or die( 'Nope, not accessing this' );

wp_location_shortcode类 (The wp_location_shortcode class)

Let’s start by creating the class outline for the shortcode. This class isn’t as large as our main class but it will contain a few functions we’ll look at shortly.

让我们开始为简码创建类大纲。 此类并不像我们的主类那么大,但是它将包含一些我们稍后将要讨论的功能。

//defines the functionality for the location shortcode
class wp_location_shortcode{

}

_construct函数 (The _construct function)

We define our construct function and use it to add our actions and filters. This class only needs one function.

我们定义构造函数,并使用它来添加操作和过滤器。 此类仅需要一个功能。

//on initialize
public function __construct(){
    add_action('init', array($this,'register_location_shortcodes')); //shortcodes
}

注册位置简码 (Register the Location Shortcode)

We use this function to add our shortcode.

我们使用此函数添加我们的简码。

We call the add_shortcode function to create a new shortcode called wp_locations. We then will use the location_shortcode_output function to output the shortcode.

我们调用add_shortcode函数来创建一个名为wp_locations的新短wp_locations 。 然后,我们将使用location_shortcode_output函数输出简码。

//location shortcode
public function register_location_shortcodes(){
    add_shortcode('wp_locations', array($this,'location_shortcode_output'));
}

为简码建立输出 (Building the Output for the Shortcode)

This function is called from the add_shortcode function and is used to build the output for the shortcode.

此函数从add_shortcode函数中调用,用于为简码构建输出。

//shortcode display
public function location_shortcode_output($atts, $content = '', $tag){

    //get the global wp_simple_locations class
    global $wp_simple_locations;

    //build default arguments
    $arguments = shortcode_atts(array(
        'location_id' => '',
        'number_of_locations' => -1)
    ,$atts,$tag);

    //uses the main output function of the location class
    $html = $wp_simple_locations->get_locations_output($arguments);

    return $html;
}

Let’s look at what this function does:

让我们看一下此函数的作用:

  • The function takes in the arguments of the shortcode $atts, the content between the shortcode $content and the name of shortcode $tag. We use these elements to help build the output

    该函数接受$atts的参数, $atts $content和shortcode $tag名称之间的$content 。 我们使用这些元素来帮助构建输出

  • We reference the global $wp_simple_locations variable so that we’ll have access to the main location class (and all of its functions).

    我们引用全局$wp_simple_locations变量,以便我们可以访问主要的位置类(及其所有功能)。

  • We create a default array of arguments for the shortcode using the shortcode_atts() function.

    我们使用shortcode_atts()函数为该shortcode_atts()创建一个默认的参数数组。

  • We use the get_locations_output function from the $wp_simple_locations object to build the shortcode output. We pass in our arguments so that the shortcode can provide dynamic content. For example a location ID can be passed so only a single location is returned.

    我们使用$wp_simple_locations对象中的get_locations_output函数构建简码输出。 我们传入参数,以便短代码可以提供动态内容。 例如,可以传递位置ID,以便仅返回单个位置。

  • We return the shortcode and it is displayed in whatever page or post you added it to.

    我们返回简码,它会显示在您添加到的任何页面或帖子中。

创建一个新的wp_location_shortcode对象 (Creating a New wp_location_shortcode Object)

At the end of your class you will create a new wp_location_shortcodeobject. All of the functionality inside of the class will be activated and you will be able to use your new shortcode.

在课程结束时,您将创建一个新的wp_location_shortcode对象。 该类内部的所有功能都将被激活,您将能够使用新的简码。

$wp_location_shortcode = new wp_location_shortcode;

位置小部件 (Location Widget)

Let’s wrap this up by looking at the class that handles the functionality for the location widget.

我们来看一下处理位置小部件功能的类,以结束本文。

We add widget support because almost all themes support widgets and they give the admin user a quick and simple way to showcase their locations (or perhaps a single location).

我们添加了窗口小部件支持,因为几乎所有主题都支持窗口小部件,并且它们为管理员用户提供了一种快速简单的方式来展示其位置(或单个位置)。

We open the wp_location_widget.php file and begin.

我们打开wp_location_widget.php文件并开始。

拒绝直接访问 (Deny Direct Access)

Again we deny direct access to the PHP file by including the following at the very beginning:

再次,我们从一开始就包含以下内容,以拒绝直接访问PHP文件:

defined( 'ABSPATH' ) or die( 'Nope, not accessing this' );

wp_location_widget类 (The wp_location_widget Class)

Let’s create our basic structure for the wp_location_widget class.

让我们为wp_location_widget类创建基本结构。

The class is similar to our other classes we have created; however this time we’re extending the already defined WP_widget class.

该类与我们创建的其他类相似。 但是这一次,我们将扩展已经定义的WP_widget类。

//main widget used for displaying locations
class wp_location_widget extends WP_widget{

}

_construct函数 (The _construct Function)

Inside our _construct function we define the basic properties of our widget by overloading the parent _construct function and supplying our own values. We set the ID, name and description of the widget.

_construct函数内部,我们通过重载父_construct函数并提供自己的值来定义小部件的基本属性。 我们设置小部件的ID,名称和描述。

In addition, we also hook our register_wp_location_widgets function onto the widgets_init hook so that we can register our widget.

另外,我们还将我们的register_wp_location_widgets函数挂钩到widgets_init挂钩上,以便我们可以注册我们的窗口小部件。

//initialise widget values
public function __construct(){
    //set base values for the widget (override parent)
    parent::__construct(
        'wp_location_widget',
        'WP Location Widget', 
        array('description' => 'A widget that displays your locations')
    );
    add_action('widgets_init',array($this,'register_wp_location_widgets'));
}

构建窗口小部件的管理界面 (Building the Admin Interface for the Widget)

The admin interface is what the admin user will interact with when setting up the widget.

管理界面是设置小部件时管理员用户将与之交互的界面。

Because we want to offer some options, you can choose how many locations will be shown via the widget and also if you want to select a single location to display.

因为我们要提供一些选项,所以您可以选择通过窗口小部件显示多少个位置,也可以选择要显示的位置。

The function form() is inherited from the parent WP_widget so it will be automatically called when we’re on the widget admin screen.

函数form()是从父WP_widget继承的,因此当我们在小部件管理屏幕上时,它将自动被调用。

//handles the back-end admin of the widget
    //$instance - saved values for the form
    public function form($instance){
        //collect variables 
        $location_id = (isset($instance['location_id']) ? $instance['location_id'] : 'default');
        $number_of_locations = (isset($instance['number_of_locations']) ? $instance['number_of_locations'] : 5);

        ?>
        <p>Select your options below</p>
        <p>
            <label for="<?php echo $this->get_field_name('location_id'); ?>">Location to display</label>
            <select class="widefat" name="<?php echo $this->get_field_name('location_id'); ?>" id="<?php echo $this->get_field_id('location_id'); ?>" value="<?php echo $location_id; ?>">
                <option value="default">All Locations</option>
                <?php
                $args = array(
                    'posts_per_page'    => -1,
                    'post_type'         => 'wp_locations'
                );
                $locations = get_posts($args);
                if($locations){
                    foreach($locations as $location){
                        if($location->ID == $location_id){
                            echo '<option selected value="' . $location->ID . '">' . get_the_title($location->ID) . '</option>';
                        }else{
                            echo '<option value="' . $location->ID . '">' . get_the_title($location->ID) . '</option>';
                        }
                    }
                }
                ?>
            </select>
        </p>
        <p>
            <small>If you want to display multiple locations select how many below</small><br/>
            <label for="<?php echo $this->get_field_id('number_of_locations'); ?>">Number of Locations</label>
            <select class="widefat" name="<?php echo $this->get_field_name('number_of_locations'); ?>" id="<?php echo $this->get_field_id('number_of_locations'); ?>" value="<?php echo $number_of_locations; ?>">
                <option value="default" <?php if($number_of_locations == 'default'){ echo 'selected';}?>>All Locations</option>
                <option value="1" <?php if($number_of_locations == '1'){ echo 'selected';}?>>1</option>
                <option value="2" <?php if($number_of_locations == '2'){ echo 'selected';}?>>2</option>
                <option value="3" <?php if($number_of_locations == '3'){ echo 'selected';}?>>3</option>
                <option value="4" <?php if($number_of_locations == '4'){ echo 'selected';}?>>4</option>
                <option value="5" <?php if($number_of_locations == '5'){ echo 'selected';}?>>5</option>
                <option value="10" <?php if($number_of_locations == '10'){ echo 'selected';}?>>10</option>
            </select>
        </p>
        <?php
    }

Let’s go through what’s happening here:

让我们看一下这里发生的事情:

  • First we’re defining values for our location ID and the number of locations. We check to see if the $instance variable holds any of these values (if they already exist). If the values do exist we extract them, if they do not we simple supply default values (setting number of locations to 5 and the location ID to default).

    首先,我们为位置ID和位置数量定义值。 我们检查$instance变量是否包含这些值中的任何一个(如果它们已经存在)。 如果值确实存在,我们将提取它们,如果不存在,我们将简单提供默认值(将位置数设置为5,并将位置ID设置为默认值)。

  • We create a label and form field that will be used to display the locations to the admin. We fetch all of the locations with get_posts() and display them. We check against each location to see if it matches the value saved in the location id (if it has been set).

    我们创建一个标签和表单字段,用于向管理员显示位置。 我们使用get_posts()获取所有位置并显示它们。 我们针对每个位置进行检查,以查看其是否与位置ID(如果已设置)中保存的值匹配。

  • We create the select list for the number of locations to display. Again we check each option to see if it matches a value passed into the number of locations variable.

    我们为要显示的位置数创建选择列表。 再次,我们检查每个选项,以查看它是否与传递给locations变量number的值匹配。

更新小部件并保存选项 (Updating the Widget and Saving Options)

The widget needs to call an update function to save its form values and that is what this function does.

窗口小部件需要调用一个update函数来保存其表单值,这就是该函数的作用。

The update() function is again inherited from the parent WP_widget class so we just specify how we’re going to save our values

update()函数再次从父WP_widget类继承,因此我们仅指定如何保​​存值

//handles updating the widget 
//$new_instance - new values, $old_instance - old saved values
public function update($new_instance, $old_instance){

    $instance = array();

    $instance['location_id'] = $new_instance['location_id'];
    $instance['number_of_locations'] = $new_instance['number_of_locations'];

    return $instance;
}

We’re given two variables, the $new_instance and the $old_instance.

我们得到了两个变量, $new_instance$old_instance

The new instance contains our current values in the form and the old instance contains our previous values.

新实例包含表单中的当前值,而旧实例包含先前的值。

What we do is create a new array to hold our extracted values and then return them.

我们要做的是创建一个新数组来保存我们提取的值,然后返回它们。

在前端显示小部件 (Displaying the Widget on the Front End)

The widget() function is another function inherited from the parent WP_widget class and is responsible for the output of the widget for the front end. It leverages the display function from the wp_simple_locations class to build its output.

widget()函数是从父WP_widget类继承的另一个函数,负责前端的窗口小部件的输出。 它利用wp_simple_locations类中的显示功能来构建其输出。

//handles public display of the widget
//$args - arguments set by the widget area, $instance - saved values
public function widget( $args, $instance ) {

    //get wp_simple_location class (as it builds out output)
    global $wp_simple_locations;

    //pass any arguments if we have any from the widget
    $arguments = array();
    //if we specify a location

    //if we specify a single location
    if($instance['location_id'] != 'default'){
        $arguments['location_id'] = $instance['location_id'];
    }
    //if we specify a number of locations
    if($instance['number_of_locations'] != 'default'){
        $arguments['number_of_locations'] = $instance['number_of_locations'];
    }

    //get the output
    $html = '';

    $html .= $args['before_widget'];
    $html .= $args['before_title'];
    $html .= 'Locations';
    $html .= $args['after_title'];
    //uses the main output function of the location class
    $html .= $wp_simple_locations->get_locations_output($arguments);
    $html .= $args['after_widget'];

    echo $html;
}

Let’s go through what we have done:

让我们来看一下我们已经完成的工作:

  • We collect our global $wp_simple_locationsobject as we want to use its display function.

    我们要使用其显示功能来收集全局$wp_simple_locations对象。

  • We create a blank array of arguments and then check to see if our widget specified any arguments (such as the number of locations to display or a single specific location).

    我们创建一个空白的参数数组,然后检查我们的窗口小部件是否指定了任何参数(例如要显示的位置数或单个特定位置)。
  • We start our build process for the output and define our $html variable. We call the get_locations_output() function defined in the $wp_simple_locationsobject and pass in our arguments (it will return all of the HTML we need).

    我们开始输出的构建过程,并定义$html变量。 我们调用在$wp_simple_locations对象中定义的get_locations_output()函数,并传入我们的参数(它将返回所需的所有HTML)。

  • We echo the result of our $html variable and the widget is displayed.

    我们回显$html变量的结果,并显示小部件。

注册小部件以供使用 (Registering the Widget for Use)

We use this function to register our widget with WordPress. We do this by calling the register_widget() function and supplying the name of our class as the value into the function.

我们使用此功能向WordPress注册小部件。 为此,我们调用register_widget()函数,并将类的名称作为值提供给该函数。

//registers our widget for use
public function register_wp_location_widgets(){
    register_widget('wp_location_widget');
}

I hope you’ve enjoyed this real world example of building a WordPress plugin from scratch, stay tuned for more.

我希望您喜欢这个从头开始构建WordPress插件的真实示例,请继续关注。

翻译自: https://www.sitepoint.com/real-world-example-wordpress-plugin-development/

wordpress插件开发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值