WordPress 插件开发-插件基础

  • 创建一个可靠的插件基础
  • 确定目录和文件的路径
  • 使用“启用”( Active )和“停用”( Deactivate )功能
  • 了解可用的插件的卸载方法
  • 提供最佳实践和代码标准
  • 了解规范的代码文档
  • 使用插件开发参考表

WordPress 中开发一个插件的时候,最重要的是要从可靠的插件基础开始。有一个好的基础可以在开发过程中避免许多令人头痛的问题。本部分内容讨论的技术会作为一个示范在整个教程中使用。

创建一个插件文件

WordPress 中的插件可以是一个单独的 PHP 文件,也可以是一个目录下的多个文件

为你的插件命名

在为插件选名字的时候,最好根据插件的实际功能来。你的插件名字必须是唯一的并且能很好的描述它的功能。

使用文件夹

强烈建议把你的插件放在 plugins 目录下的子文件夹中。所有的从 WordPress.org 下载的插件都自动的放在了子目录下面。这样你就可以方便的包含多文件以及使用其他的内容,比如图片。子目录还可以再包含子目录来更好的组织你的插件文件。文件夹的名字必须和你的主插件文件相同。在文件夹名称中不应该包含任何空格’ ‘或者下划线’_’,如果需要就是用连字号’-‘。子目录和文件的目录组织结构会在接下来的”最佳实践“中讨论。

最佳实践
下面是开发 WordPress 插件的一系列的最佳实践。这一部分所介绍的最佳实践是你应该在所有的插件中都严格遵守的。这有助于消除许多在 WordPress 中遇到的常规错误。这些最佳实践同样会使你的插件的组织更加清晰。

全部加上前缀

在开发插件的时候,在所有东西前面都加上唯一的前缀是非常关键的。这包括插件中的文件、函数名、变量名、以及其他所有包含在插件中的东西。为什么呢?很简单,插件开发中一个最普遍的错误就是使用了太普遍的函数名或者变量名。例如你的函数名是 update_options(),如果用户安装的其他插件中有同名的函数,网站会出错,因为在 PHP 中是不能有两个同名的函数的

一种比较好的规则是用你的插件的首字母,和你自己的首字母来做前缀。例如你的名字是 heronk,你的插件名字是 test,那么你的前缀可以是 heronk_test_update_options()。这样子其他的插件与你的插件同名的机会就很小了。这样和其他插件冲突的风险就很小了。

对于变量名,这也是个好办法。在声明变量时,不要使用常见的名字。例如,你的插件中有个变量叫 $post 。这就可能引起非预期的结果,因为 $post 是 WordPress 中包含文章数据的全局变量。如果在 WordPress 仍然需要用文章数据的时候你的插件覆盖了 $post 中的数据,那就可能遇到严重的问题了。所以你应该使用与上面类似的前缀 $heronk_test_post。在其他插件中基本不会用到这个变量了。

下面的文章中,使用 heronk_ 和 myplugin_ 组合来做前缀,比如 heronk_myplugin_function_name()。

文件组织

对于开发专业的插件来说,插件文件的良好组织是非常重要的。一般来说,在你的插件文件夹中只放两个文件:一个是插件的主 PHP 文件,另一个是 uninstall.php 文件。为了有序的组织,把其他的插件所需文件都放到其他子目录下面去。

同样建议将插件分到几个小的文件中。这么做一个主要的原因是性能的原因。例如,你应该把所有的管理界面的代码放在一个独立的文件中。这使得你可以在用户浏览 WordPress 管理员界面时有条件的包含管理员代码。

<?php
    if( is_admin() ) {
        // 在 wp-admin 里面
        require_once( dirname(__FILE__ ).'/includes/admin.php');
    }
?>

上面的例子使用 is_admin() 条件语句来验证用户是否在 WordPress 的管理控制板中。如果是,你的插件应该包含 /includes/admin.php 文件。

目录结构

另一个插件开发中的要点是要有清晰的目录结构,从而很好的包含插件中所有的小文件。例如,你的插件需要 JavaScript 文件,就建立一个 /js 目录来存放 JavaScript 文件。如果有样式表文件,就建立一个 /css 目录来存放所有的样式表文件。同样所有的图片放在 /images 目录里面。

下面看看一个插件的标准的目录结构:

  • /unique-plugin-name — 唯一的插件名称,不要包含空格或者特殊字符
  • unique-plugin-name.php — 唯一的插件名称,插件主文件
  • uninstall.php — 插件的卸载文件
  • /js — JavaScript 文件
  • /css — css 样式表
  • /images — 图片
  • /includes — 存放要包含的 PHP 文件

用清晰的目录结构来使得你的文件有序使得追踪文件的时候更方便。多于其他的插件卡发着来说,他们更容易理解你的插件的逻辑。

必要的插件头部

插件要在 WordPress 中起作用,其头部是唯一的必须代码。“插件头部”是位于插件文件头部的一段 PHP 的注释代码块。这段注释块告诉 WordPress 这是一个有效的插件。

创建头部
下面是一个插件的头部示例:

<?php
    /*
    Plugin Name: My Plugin
    Plugin URI:http://example.com/wordpress-plugins/my-plugin
    Description: 插件的详细说明
    Version: 1.0
    Author: heronk
    Author URI:https://www.oddba.cn
    License: GPLv2
    */
?>

WordPress 只需要 Plugin Name 这一行就可以识别插件,不过最好是像上面一样填写详细的信息。

Plugin URI 是连接到你的插件的详细内容的网页的链接。Description 是插件的简短描述。Version 是当前插件的版本。WordPress 使用这里设置的版本号来检查插件在 wordpress.org 的更新。下面两行是 Author – 作者,和 Author URI – 作者的网站。最后一行是这个插件发布所遵照的许可证。

插件许可证

在插件头部的注释块中,最好包含你的插件的版权。对于插件的功能来说,这不是必须的,但是任何时候你发布代码,都最好包含版权信息。这是你的用户知道你的插件的版权是什么,以及他们可以如何使用你的插件。

WordPress 的版权是 GPL 软件版权所以任何 WordPress 的插件都要是和 GPL。 下面是一段 GPL 版权注释块的示例。

/* Copyright YEAR PLUGIN_AUTHOR_NAME (email : PLUGIN AUTHOR EMAIL)
This program 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
(at your option) any later version.
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
*/

 

确定目录结构

通常你要确定你的插件的目录和文件结构。例如,图片都放在 images 文件夹下。一般来说,最好在插件中硬编码一个目录结构。WordPress 可以被配置运行在许许多多的情形下,所以目录结构说不定就是个错误。这部分介绍确定目录和文件结构的合适的方法。

插件路径

最基本的事情就是把你的文件和目录引入到 WordPress 的安装中。要引入文件有两种方法:使用本地服务器路径,或者使用标准的 URL。

本地服务器路径就是计算机的目录。本地服务器路径通常在你要包含在你本地服务器上的内容时使用。URL 则通常用来连接你的服务器外部的内容,不过这并不是说你就不能用 URL 来连接本地服务器上的内容了。

WordPress 支持把 wp-content 目录移动到不同的位置。由于这个原因,在 WordPress 中你不应该硬编码目录,而应该用可用的函数来确定正确的路径。

本地路径
插件开发中有一个常见问题:确定你的插件文件的本地路径的合适方法是什么?要确定你的插件的本地路径,应该使用 plugin_dir_path() 函数。 plugin_dir_path() 函数从它的文件名中提取和插件目录对应的物理地址。

<?php 
        plugin_dir_path($file); 
?>

参数:$file – (string)(必须) — 插件的文件名

下面看如何确定到你的插件目录的本地路径的例子:

<?php
        echo plugin_dir_path(__FILE__);
?>

把 PHP 常量 __FILE__ 传递给了 plugin_dir_path() 函数。这会生成指向你的插件目录的完整的本地路径。

/wp-content/plugins/my-plugin/

如何得到在你插件目录下的子目录中文件的本地路径呢?同样可以使用 plugin_dir_pah() 函数并加上子目录和文件名:

<?php
        echo plugin_dir_path(__FILE__).'js/scripts.js';
?>

这段代码会输出下面的结果:

/wp-content/plugins/my-plugin/js/scripts.js

这个函数在开发一个WordPress 插件时很有用。使用正确的方法来访问你的插件文件和目录可以确保你的插件与 WordPress 安装的兼用性,不管它是怎么自定义的。

URL 路径
同样有函数帮助确定WordPress 中的 URL。下面是这些函数的列表:

  • plugins_url() — 插件目录的 URL (例如:https://example.com/wp-content/plugins)
  • include_url() — includes 目录的 URL (例如:https://example.com/wp-includes)
  • content_url() — content 目录的 URL (例如:https://example.com/wp-content)
  • admin_url() — admin 目录的 URL (例如:https://example.com/wp-admin/)
  • site_url() — 当前网站的 URL (例如:https://example.com)
  • home_url() — 当前网站首页的 URL (例如:https://example.com)
  • site_url() 和 home_url() 很相似,容易混淆。site_url() 返回的是数据库中 wp_options 表里面的 siteurl 字段值。这是指向 WordPress 核心文件的 URL。如果你的 WordPress 核心文件在你的服务器的子目录中,比如 /wordpress,那么 site_url() 的值就会是 https://example.com/wordpress 。

home_url() 则从 wp_option 表中取得 home 字段的值。这个地址是你希望访问你的 WordPress 网站的 URL 地址。例如,你的 WordPres 核心文件放在 /wordpress 目录下,但是你希望你的 URL是 https://example.com,那么就要把 home 的值设置成 https://example.com。

plugins_url() 函数在写 WordPress 插件的时候很有用。这个函数可以确定在插件目录下的任何文件的完全 URL。

<?php 
        plugins_url( $path, $plugin);
 ?>

 

参数:

  • $path — (string)(可选) — 相对于 插件 URL 的路径
  • $plugin — (string)(可选) — 要相对的插件文件(如果是自己,就传 __FILE__ )

例如:要在插件中引用一个图片文件,可以这样子:

<?php 
        echo'<img src="'.plugins_url('images/icon.png',__FILE__).%20'"/>';
?>

第一个参数是要用到的图片文件的相对路径。第二个参数是要取相对路径时的参考文件,本例中直接是 —__FILE__。上面的代码会生成下面的 HTML 标签:

<imgsrc="https://example.com/wp-content/plugins/my-plugin/images/icon.png"/>

 

下面是使用 plugin_url() 函数来确定插件的 URL 的好处:

  • 支持 mu — 插件的插件目录。
  • 自动检测 SSL,所以如果 SSL 被开启,那么 URL 会自动返回包含 https 的。
  • 使用 WP_PLUGIN_URL 常量,意味着即使用户把插件移动到了自定义的位置,也可以检测到插件的目录。
  • 使用 WPMU_PLUGIN_URL 常量来支持 Multisite

activate/deactivate 函数
WordPress 有一些插件开发中可用到的通用函数。下面介绍 activate 和 deactivate 函数。

插件 activation 函数

插件 activation 函数当一个插件在 WordPress 中”activated(启用)”时被触发。

这个函数叫做 register_activation_hook()。使用这个函数可以用来为插件设置一些默认选项。也可以用来验证插件和 WordPress 版本的兼容性。这个插件接收两个参数:

<?php 
    register_activation_hook($file, $function); 
?>

参数:

$file — (string)(必须) — 主插件文件的路径。
$function — (string)(必须) — 当插件启用时要执行的函数。
下面是一个例子:

<?php
register_activation_hook(__FILE__, ' heronk_myplugin_install');
function  heronk_myplugin_install() {
// 启用时要做的事情
}
?>

第一个参数是插件的路径,使用了 __FILE__ 常量。这个一个 PHP 自带的常量,指向调用它的文件的 绝对路径。第二个参数是唯一的一个函数名。

知道了 register_activation_hook() 函数的用处后,我们看一个真实的例子。下面的例子用来验证 WordPress 的版本:

<?php
register_activation_hook(__FILE__, 'heronk_install');
function heronk_install() {
if( version_compare( get_bloginfo('version' ), '3.1', '<') ) {
deactivate_plugins(basename( __FILE__ )); //禁用插件
}
}
?>

我们使用 get_bloginfo() 函数来取得当前安装的 WordPress 版本号。接着用 PHP 函数 version_compare() 来验证安装的 WordPress 的版本是否至少为 3.1。如果低于 3.1,就调用 deactivate_plugins() 函数来 禁用插件。

在启用时进行默认设置
另一个常用的启用技术就是在你的插件启用时为它设置默认选项。假设你的插件有许多许多选项,也许有一些选择需要被设置才能保证插件正常工作。你可以再启动时自动设置默认值,而不用让用户跑到设置页面来亲自设置。

<?php
register_activation_hook(__FILE__, 'heronk_install');
function heronk_istall() {
  $heronk_myplugin_options= array(
    'view'=> 'grid',
    'food'=> 'bazon',
    'mode'=> 'zombie'
  );
  update_option('heronk_myplugin_options',$heronk_myplugin_options );
}
?>

 

上面的代码在你插件启用时生成了一个包含默认值的数组并把它们存到 WordPress 中。在 part-7,”插件设置” 中将详细介绍创建和保存插件选项。

插件 deactivation (禁用)函数

和启用函数类似,同样有禁用函数。叫做 register_deactivation_hook() 函数。这个函数在插件被禁用时触发。这个函数和前面的启用函数一页同样接收两个参数。

<?php 
    register_deactivation_hook($file, $function); 
?>

参数:

  • $file — (string)(必须) — 主插件文件的路径。
  • $function — (string)(必须) — 当插件禁用时要执行的函数。

下面是一个例子:

<?php
register_deactivation_hook(__FILE__, 'heronk_myplugin_uninstall' );
function heronk_myplugin_unstall() {
  // 执行内容
}
?>

在禁用插件时执行 heronk_myplugin_uninstall() 函数。

禁用 ≠ 卸载
在处理禁用的时候,不应该包含卸载插件的功能。因为禁用了还可以重新启用的,但卸载(删除)就不一样了。注意:WordPress 在自动更新的时候,会先禁用所有的插件,然后再安装 WordPress 的新版本。

卸载的方法

为插件提供卸载功能可以方便的删除掉插件添加到 WordPress 中的数据。这应该是每个插件的必须部分。这不会带来多少工作量,但是可以让用户来选择是否完全删除它。

为什么卸载是必须的?

想像一下插件是安装到你电脑中的一个软件。你就希望这个软件有个非常方便的卸载的方法。你同样希望可以彻底卸载该软件的所有内容。WordPress 中的插件也是同样的意思,实质就是安装在 WordPress 中的软件。如果用户希望删除这个插件,你应该为用户提供从 WordPress 中彻底删除它的功能。

关心插件用户的数据是一个良好的规则。例如如果你的插件建立事件作为自定义文章类型,也许用户希望卸载插件,但并不希望删除他们所有的事件。因此你或许希望询问用户是否要删除他们的数据。

WordPress 提供了两种卸载插件的方法:一个是 uninstall.php 文件,另一个是 uninstall 钩子。

uninstall.php
第一种方法:uninstall.php 文件。这是典型的流行方法,因为它把你所有的卸载代码放在一个独立的文件中。要使用这个方法,创建一个 uninstall.php 文件并放置在插件的根目录。如果这个文件存在,WordPress 会在插件被删除的时候执行这个文件的代码。下面是使用 uninstall.php 文件的一个例子:

<?php
// 如果 uninstall 不是从 WordPress 调用,则退出
if( !defined('WP_UNINSTALL_PLUGIN' ) )
exit();
// 从 options 表删除选项
delete_option( 'heronk_myplugin_options' );
// 删除其他额外的选项和自定义表
?>

第一件要做的事就是验证确实是 WordPress 在调用 uninstall.php 文件。通过严重 WP_UNINSTALL_PLUGIN 常量是否定义。如果没有,立即退出。这是一个保证只有在删除插件时才能够执行 uninstall.php 文件的安全的方法。

在验证了这是合法的卸载调用以后,就可以从数据库中删除插件的设置项了。插件卸载脚本的目的是要从数据库中删除任何与插件相关的内容。这包括删除所有选项,已经删除所有自定义的表。你不需要操心删除插件的文件或者目录的事情,一旦卸载脚本执行了,WordPress 会自动为你做这些事情。

卸载钩子
第二种可用的卸载方法叫做卸载钩子。如果你删除一个不存在 uninstall.php 的插件,WordPress 会执行卸载钩子(如果存在的话).

<?php 
    register_uninstall_hook($file, $function); 
?>

 

参数:

  • $file — (string)(必须) — 插件主文件的路径
  • $function — (string)(必须) — 在插件卸载后要执行的函数

下面看卸载函数的例子:

<?php
register_activation_hook(__FILE__, 'heronk_myplugin_activate' );
function heronk_myplugin_activate() {
  // 注册卸载函数
  register_uninstall_hook(__FILE__, 'heronk_myplugin_uninstaller' );
}
function heronk_myplugin_uninstaller() {
  // 删除插件创建的选择,表等等
  delete_option('heronk_myplugin_options' );
}
?>

register_uninstall_hook() 必须在启用函数中调用。因此要在使用 register_activation_hook() 函数执行插件启用的函数中包含 uninstall 钩子。接着调用 uninstall 函数。注意:如果插件根目录下包含 uninstall.php 文件,那么删除钩子是不会执行的。

重要:要知道不能使用一个类的方法作为卸载钩子的回调函数。因为卸载钩子会保存一个 $this 的引用到数据库中,它多那个页面的加载来说是唯一的。

如本节中提醒的,使用卸载钩子有许多陷阱。所以最好使用更简洁的 uninstall.php 文件来进行卸载。

编码规范

WordPress 在所有的核心代码中保持着一系列的编码规范。这有助于保持整个 WordPress 的代码格式始终如一,易于阅读。所以建议在插件开发过程中遵循这些编码规范。

代码文档

一个最明显,但又最容易忽略的步骤就是代码注释。一定记得要注释,几乎所有的 WordPress 核心函数都包含 PHPDoc 形式的行内文档。PHPDoc 是一个标准的描述函数作用的注释形式。下面是一段基本的 PHPDoc 格式的函数注释:

<?php
/**
 * Short description
 *
 * Longer more detailed description
 *
 * @param type $varname1 Description
 * @param type $varname2 Description
 * @return type Description
 */
function heronk_super_function( $varname1,$varname2 ) {
  // 函数内容
}
?>

上面的 PHPDoc 注释直接在注释块中描述函数的功能。如果一个开发者查看你的插件的代码,他不用看函数的代码也可以很快可以明白你的函数的功能,接受什么参数,返回什么结果。这些注释也被许多可视化软件使用,例如 PHPDocumentor 和 PHPXref。

变量、函数和文件的命名
变量和函数名应该全部小写。代词应该用下划线来分割。下面是函数和变量命名的正确方法:

function heronk_myplugin_function_name ( $heronk_myplugin_variable ) {
// do something
}
?>

文件名同样应该只使用小写字母,不过,文件名应该用连字号”-”来分割单词,而不要用下划线。例如你应该这样命名插件文件:heronk-plugin-name.php

单引号 v.s. 双引号
PHP 同时允许使用单引号或者双引号来定义字符串。WordPress 中推荐在可能的情况下尽量使用单引号。使用单引号的一个好处这样你就很少需要在字符串中去避免 HTML 的引号。下面是使用单引号来输出一个超链接的方法的例子:

<?php
echo '<a href="https://example.com/" > 访问 example.com </a> ';
?>

在 PHP 中连接一个字符串时,你也可以使用双引号方法。例如:看下面的为网站 URL 插入一个变量的简单方法:

<?php
$heronk_myplugin_website= 'https://example.com/';
echo " <a href='$heronk_myplugin_website' > 访问 example.com </a> ";
?>

把 $heronk_myplugin_website 变量设置成你想包含在 HTML 超链接中的 URL。然后把这个字符串连接到 echo 语句中的网站 URL 中。

缩进
缩进应该反映代码的逻辑结构。这意味着在缩进时使用 tab 而不是空格。下面是很差的 if 语句缩进的例子:

<?php
if ( 条件 ) {
echo 'Yes';
} elseif( 条件2 ){
echo 'No';
}
?>

上面代码的逻辑很难体现出来,因为没有反应 if 语句代码逻辑结构的缩进。下面是使用合理缩进的例子:

<?php
if ( 条件 ) {
    echo'Yes';
} elseif( 条件2 ){
    echo'No';
}
?>

注意如何使用合适的缩进来使得代码的逻辑更显而易见。你可以简单的跳过这段内部代码而直接明白这些语句的结果。这就是为什么你的所有代码都必须有合适的缩进。

括号格式
多行代码块必须使用花括号。括号应该和你要检查的条件语句在同一行中。看合适的使用花括号的例子:

<?php
  if( condition ) {
      action1();
      action2();
  } elseif ( condition2 || condition3 ) {
      action3();
      action4();
  } else {
      defaultaction();
  }
?>

如果你的代码块很长,最好在结束的花括号后面加一小段注释来帮助确定这是哪个花括号的结束。

<?php
  if( condition ) {
      action1();
      action2();
  } elseif ( condition2 || condition3 ) {
      action3();
      action4();
  } else {
      defaultaction();
  }// end of condition check
?>

空格的使用
在括号的前后面、逻辑或者赋值运算符的两端都应该加一个空格。下面看看几个合适的使用空格不同例子:

<?php
  if( $foo == 34 ) {
      // do something
  }
  foreach( $foo as $bar ) {
      //  do something
  }
  $foo= array( 34, 16, 8 );
  functionsuper_function( $param1= 'foo',$param2 = 'bar' ) {
      // do something
  }
?>

注意每一个语句的空格使用技巧。这使得阅读以及理解代码逻辑更容易,因为真个代码简洁一致。

PHP 简写
你不应该在代码中使用 PHP 标签的简写形式 ( <? ?> )。因为简写形式要起作用必须在服务器中开启。许多主机的配置,该选项是被禁止的,这样你的代码直接就崩溃了。所以应该使用标准的标签形式:

<?php ?>

 

SQL 语句

在操作数据库时,你可能会用到 SQL 语句。复杂的 SQL 语句可以分成多行来写。尽管 SQL 语句是大小写不敏感的,最好还是把 SQL 命令写成大写形式。

SELECT username FROM table1WHERE status = 'active'

插件开发检查列表

在开发 WordPress 插件时,你需要记住许多事情来创建一个合适的插件基础。下面的列表帮助你完成这个过程。跟随这个列表,你就可以确保有一个合适的插件基础了:

  • 确定一个具有描述性的唯一的插件名
      • 名字是否可以描述你的插件的功能
      • 是否验证了插件名在插件目录中不存在
  • 为插件设置一个唯一的前缀
      • 前缀是否足够特殊来避免冲突
  • 建立插件的目录结构
      • 是否需要 PHP 目录
      • 是否需要 JavaScript 目录
      • 是否需要 CSS 目录
      • 是否需要 images 目录
  • 建立默认的插件文件
      • 建立和插件文件夹名同名的主插件文件
      • 建立 uninstall.php 文件来执行卸载过程
  • 建立插件的头部代码
      • 设置你希望显示的插件的名字
      • 添加一个详细的插件目的的描述
      • 设置正确的版本
      • 确保设置了插件的 URI 和 作者的 URI 的值
  • 包括版权信息
      • 在插件头下面直接包含版权信息
  • 建立插件的启用函数
      • 插件功能的实现是否需要一个特殊的或者更高的 WordPress
      • 插件是否要在启用时设置默认值
  • 建立插件的禁用函数
      • 插件是否要在禁用时执行什么东西
  • 建立插件的卸载脚本
      • 建立 uninstall.php 文件
      • 在文件中包含卸载脚本
  • 文件引用
      • 使用合适的目录常量和函数来确定 WordPress 和插件中的路径

入门样板插件

如果直接从头开始写一个插件,会做很多不必要的重复工作,我们可以使用样板插件来开发每个新插件。使用样板文件的一个优点是在我们的插件中保持一致性。如果插件的用户已经熟悉了插件样板,他们为插件贡献代码的时候,也更加轻松。下面是几个常用的 WordPress 样板插件项目。

本文采用「CC BY-SA 4.0 CN」协议转载自互联网、仅供学习交流,内容版权归原作者所有,如涉作品、版权和其他问题请留言处理。

给TA打赏
共{{data.count}}人
人已打赏
玩站

WordPress 插件开发-过滤器钩子(filter hooks)

2023-2-23 10:53:22

玩站

WordPress插件开发: 给每篇文章后面添加版权信息

2023-2-23 16:25:58

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧