本文是我翻译并整理Moodle官方插件开发基础教程完成的,包含了每章的教学内容以及Quiz答案,一些我在学习中觉得翻译不妥的地方都使SEO靠我用了原文或进行了原文批注。由于我自己还在学习,所以仍在持续更新中
。
整理不易,如有错误,还请包涵ww。在Moodle模块化Moodle中的“M”代表“模块化”。大SEO靠我多数用户直接与之交互的特性都是通过独立的模块实现的,这些模块在Moodle中通常被称为插件。下载后,Moodle已经提供了许多标准插件。默认情况下,这些都是Moodle安装的一部分。甚至更多的插件可以SEO靠我作为附加插件安装。附加插件的官方存储库是Moodle plugins目录。那里的插件由独立的社区贡献者维护。在文件系统上,标准插件几乎占据了Moodle安装的一半。另一半由所谓的核心子系统组成。这些子SEO靠我系统提供了插件使用的核心api
插件类型Moodle中有许多插件类型,每一种都专注于特定的功能领域。每种插件类型都有自己的特性。了解各种插件类型的目的、可能性和限制是很重要的,这SEO靠我样你就可以选择最合适的类型来实现所要求的特性。所有给定类型的插件都安装在一个公共父目录中的单独目录中。例如,每个/blocks/*目录都是一个特定的插件,它们都是Block类型的一个插件不能有一种以上SEO靠我的类型。在Moodle中,每种插件类型都有其独特的安装位置。
活动模块是Moodle中必不可少的插件。它们代表着学习在课程中发生的活动。教师通过增加活动模块的实例(SEO靠我如测验、作业、论坛或书籍)来创建课程的主要内容例如,您当前正在阅读的这个特定文本是Book活动模块提供的HTML页面。有些模块,比如Books、url或Pages,在用户界面中被称为资源。活动模块通常SEO靠我以链接的形式显示在主课程页面的大纲中。
活动模块的共同特性
分级 - 活动模块可以分级。Gradebook API提供了在课程成绩簿中为活动模块实例创建成绩项的方法;完成 - 活动和资源可以通过ActivSEO靠我ity completion API标记为完成,允许课程进度。跟踪每一个学生;备份和恢复 - 活动和资源可以包含在课程备份中,之后通过Backup API进行恢复;条件访问 - AvailabilitSEO靠我y API允许基于各种标准控制活动模块实例的访问和可见性。Blocks块是内容项目,可以添加到左,右或中心栏的任何页面在Moodle。块可能出现的区域由主题布局控制。块可以向用户显示SEO靠我有用的信息,如最近的课程活动、即将到来的日历事件或课程列表。
Blocks can also be used on the Dashboard.
块的常见特性
跨站点显示 - 块可以配置为显示在整个站点的不SEO靠我同页面,例如在每个页面,仅在测试尝试页面等备份和恢复 - 类似活动模块,块可以包括在课程备份。可配置的 - 块实例可以配置。自定义字段可以添加到块配置表单中。块通常比活动模块更容易实现。Moodle拥有强大的主题,可以通过使用HTML和CSS实现各种效果。主题定义了Moodle站点的外观和感觉
主题的共同特征
不仅是CSS - 除了提供自己的层叠样式表(CSS)外,定制主题还可以改变SEO靠我生成的HTML页面许多部分的整体布局继承 - 主题可以定义它的父主题,其中一些属性是隐式继承的。身份验证插件使用钩子(hooks)来增强或替换部分用户身份验证流。标准SEO靠我的身份验证方法包括根据内部存储的密码、LDAP或OAuth2提供商(如谷歌或Facebook)进行身份验证。
注册插件控制谁被注册到课程中,以及他们在那里扮演什么角色(因此也就有SEO靠我权限)。
课程中创建的注册插件可以有多个实例,通常每个实例都有自己的配置。
注册插件的共同特点
角色 - 注册插件通常负责在给定的课程中分配一个特定的角色,例如学生或老师。被分配的角色通过定义的功能给用户某SEO靠我些权限。课程格式决定了课程主页的布局。教师可以在课程设置表中指定所选择的课程格式。
课程格式的常见功能
导航 - 课程格式还负责在课程内部构建导航树,然后显示在面包屑导航SEO靠我小部件中。章节 - 课程格式可以将课程内容组织成章节。课程部分是一个核心层次的概念,每个课程格式都可以以自己的方式解释和使用。备份和恢复 - 课程格式数据包含在课程备份中,可以在以后恢复。管理工具为各种站点管理和维护任务提供了有用的工具。管理员和管理人员通常可以通过站点管理树菜单访问它们。
不管它们的名字可能暗示什么,管理工具并不需要只被站点管理员使用。学习计划、数据隐私SEO靠我和用户参观(User tours)等功能实际上是由管理工具插件提供的,并为非管理用户(如学生)提供功能。
本地插件用于实现特定于站点或机构的功能。它们被设计成一种通用的插SEO靠我件类型,以一种干净和可维护的方式用于各种本地定制。通常情况下,它们甚至不应该被公开分发和分享本地插件也经常被用于那些不适合任何其他标准插件类型的特性。例如,整个Moodle plugins目录是作为一SEO靠我个安装在Moodle站点上的本地插件来实现的。
本地插件的特性
导航 - 一个本地插件可以将新项目注入到站点导航和管理树中,或者改变现有的导航。自定义首页 - 一个本地插件可以用来提供一个自定义网站首页,SEO靠我利用$CFG->customfrontpageinclude配置变量自定义web服务功能 - 本地插件是实现可通过web服务层公开的自定义外部功能的优雅解决方案。其他插件类型请参阅开发人员文档中的插件类型页面,以获得支持类型的广泛列表。其他常用的自定义插件类型包括
ReportsText filtersAtto editor buttons和其他类型。
每个Moodle插件都安装在Moodle源代码树中自己的文件夹中。文件夹的位置取决于插件类型和插件名称。所有相同类型的插件都作为一个公共父目录中的子文件夹安装。这个插件类型的父目录的路径定义在lSEO靠我ib/components.json文件中。也可以在dev docs Plugin types中找到。
除了出现在用户界面上的可读的插件名(如“分配活动”)外,每个插件都有自己的系统插件名。这是包含该特SEO靠我定插件的所有文件的文件夹的名称。
EXAMPLE
任务分配是一个活动模块。所有活动模块都安装在Moodle源代码树的mod父文件夹中的子文件夹中。赋值的插件名是assign。所以它在Moodle源代码中的SEO靠我完整位置是/mod/assign。
因此,不能同时安装两个相同类型和相同插件名的插件。可以有两个文件夹名称相同的插件,只要它们是不同类型的——因此安装在不同的父文件夹中
每个插件类型都定义了一个简短的系统名称。对于Activity modules,它是mod。对于Blocks,它是block。对于Question types,它是qtype,等等。这些简短的SEO靠我名称定义在lib/components.json文件中。也可以在dev docs Plugin types中找到。
这允许每个安装的Moodle插件都有一个唯一的标识符,由插件类型名(plugin tySEO靠我pe name)和插件文件夹名(plugin folder name)组成。用下划线符号隔开,这两个组成了所谓的完整组件名(Moodle开发人员非正式地称之为frankenstyle)。
插件的完整组件SEO靠我名称(frankenstyle)在Moodle PHP代码中扮演着重要的角色。说明:
被用作插件在全局命名空间中创建的函数和类的前缀,以避免与其他插件冲突。对于有命名空间的类,它被用作命名空间的第一个元SEO靠我素。所有由插件创建的数据库表都应该使用插件的组件名作为名称前缀。关于这个概念及其在Moodle中的用法的更多细节,请参阅开发文档Frankenstyle。
插SEO靠我件文件夹中的文件实现了插件的逻辑,以及插件和Moodle核心之间所需的接口。每种插件类型都有自己的细节和要求。在所有插件类型中,仍然有许多文件以相同的方式工作。
在这些文件中,最重要的是:
versionSEO靠我.php它提供了一些关于插件的元数据,如版本号、依赖关系或成熟度级别;lang/en/{plugintype}_{pluginname}.php定义了插件中使用的英文字符串;db/install.xmSEO靠我l该xml定义了插件的数据库表。和许多其他这些常见文件。
完整列表请参见dev docs Plugin files。
入乡随俗.jpg
多年来,Moodle开发SEO靠我人员已经就如何格式化和组织代码的某些风格和指导方针达成了一致。在Moodle核心和插件中使用相同的风格有助于我们检查和学习彼此的代码。这可能不是你个人喜欢的风格。不过,在使用Moodle进行开发时,请SEO靠我从最开始就坚持使用它。
通过编码风格,熟悉指导方针许多PHP编辑器和ide支持基于指定规则和设置的自动高亮显示和校正。有关设置环境的详细说明,请参阅Developer工具有两个插件Code-checkeSEO靠我r和Moodle PHPdoc check,您可以添加到您的开发安装。然后,你可以针对你的插件代码在本地运行这些插件,从推荐的编码风格中获得所有变化的报告最后,一旦你使用Git来跟踪你所维护的插件的变SEO靠我化,你就可以set up the Travis CI来运行这些和其他连续的自动检查。In this tutorial we explore:
How incomiSEO靠我ng HTTP requests are handled by the PHP scripts in Moodle.How to read and sanitise the value of submSEO靠我itted data such as GET request arguments.How to easily generate a URL pointing to a script in MoodleSEO靠我.现代PHP框架提供了自己的处理和路由传入HTTP请求并生成响应的方法。Moodle开发在这些框架出现之前就已经开始了,它使用了典型的传统方法,即服SEO靠我务器端脚本生成动态网页。
从体系结构的角度来看,Moodle可以被看作是实现Page Controller软件设计模式。
“[…] Page Controller is an object (in theSEO靠我 broader sense of the term, not only an instance of a class but every kind of item) kept on the servSEO靠我er, which is called upon a certain endpoint is the target of a HTTP request. For example, plain old SEO靠我PHP scripts, which you call with URLs like /index.php, /list.php or /folder/member.php?id=42 are PagSEO靠我e Controllers.”
Source: Practical PHP Patterns: Page Controller by Giorgio Sironi, July 14, 2010, WebSEO靠我 Dev Zone
For more information please see for example Patterns of Enterprise Application ArchitectureSEO靠我 Catalog by Martin Fowler et al.
在Moodle中有两种主要的PHP脚本:
Request handlers(请求处理程序)SEO靠我:
这些是页面控制器直接被用户的web浏览器访问。这些脚本是HTTP请求的端点,它们通常打印生成的HTML并执行其他操作。
EXAMPLE: 脚本/课程/视图。访问/course/view.php可以显示SEO靠我课程主页,也可以处理一些与课程相关的操作,比如移动部分moving sections。
Libraries(库):
这些脚本不应该通过HTTP请求直接访问。它们由其他脚本加载,并提供实现所有逻辑的库函数和SEO靠我类定义。
EXAMPLE: 课程视图脚本加载另一个库脚本/lib/completionlib.php,它提供了活动完成api相关的函数。
.2.1 Request handlersRequest handSEO靠我lers process the incoming HTTP request. In the vast majority of cases, this will either be a GET or SEO靠我a POST request. All request handlers must start by including the main config.php script from the rooSEO靠我t of the Moodle installation.
require(__DIR__ . /../../config.php);文件的特定路径将取决于请求处理程序脚本在Moodle文件treę中的SEO靠我深度。
主配置文件定义了一些基本变量,比如数据库连接细节或Moodle数据文件夹的位置。config.php然后加载lib/setup.php文件来设置执行环境:
加载所有必需的核心库。很多东西,比如数据SEO靠我库连接,会话(session),当前课程,主题和语言都被初始化了。请参阅开发文档What happens when you require config.php获取更多安装过程的详细信息。
请求处理程序SEO靠我通常在开始时执行的其他事情是:
读取并清除所有输入参数
执行访问控制检查
如果只允许通过身份验证的用户访问脚本特性,请确保他们已登录。如果需要特殊权限,请确保用户拥有该权限。如果请求即将触发数据库中的更改…SEO靠我
确认已提交有效的会话密钥令牌(sesskey)。一旦操作被执行,重定向用户到一个新的URL,可以安全地从浏览器重新加载,而无需重新触发操作。 .2.2 Libraries库不应该被浏览SEO靠我器直接访问。它们只能由其他脚本加载 - 要么是请求处理程序,要么是其他库。
传统的库文件应该以下面一行开始:
defined(MOODLE_INTERNAL) || die();这是为了停止脚本的执行,如SEO靠我果库没有被其他脚本加载。常量MOODLE_INTERNAL是在执行环境设置期间定义的。换句话说,这是为了确保config.php文件(因此还有lib/setup.php)已经加载。
请参阅RequireSEO靠我/include section of the Coding style,了解如何从另一个文件加载库。
.2.3 Class files类文件是Moodle中库文件的子集,具有一些特殊的规则。
它们位于cSEO靠我lasses/文件夹中。它们支持自动加载,不应该通过显式的require_once()调用来加载。它们的命名有一定的规则,在开发文档中描述了自动类加载(Automatic class loading)SEO靠我。因为这些文件只定义了一个没有副作用的类,所以它们不应该有MOODLE_INTERNAL检查。当前的指南建议对所有新库代码使用有命名空间的类。然而,你仍然会在Moodle核心中看到很多传统的库文件,以SEO靠我及贡献的插件。
请求处理程序负责读取和处理HTTP参数。出于安全原因,Moodle插件不应该访问PHP请求超全局变量,如$_GET、$_POST或$_REQUEST。相反,MoSEO靠我odle core提供了一些辅助函数,如required_param()或optional_param(),它们应该用来读取提交的参数的值。
来自用户的所有输入参数都必须被视为潜在的恶意,必须始终进行消SEO靠我毒和验证。为此,需要指定适当的参数类型。该类型由PARAM_*常量之一指定。最常用的类型有:
PARAM_INT声明该参数应被视为一个整数;PARAM_ALPHA是用于包含英文ASCIl字母[a - zSEO靠我, A - Z]的短字符串;PARAM_BOOL将输入值如0、1、“yes”、“no”、"true"或"off"转换为布尔变量;PARAM_NOTAGS从提交的文本中剥离所有的HTML标签;PARAMSEO靠我_TEXT用于更长的纯文本。去掉了所有的HTML标签,只保留了对标准的多语言内容过滤器(Multi-language content filter)的支持。请参阅lib/moodlelib.php的源SEO靠我代码,了解更多PARAM类型常量及其内联文档。
Moodle不需要安装在web服务器的HTML文档文件夹的根目录下。许多机构都将他们的Moodle安装在类似于wSEO靠我ww.ourschool/lms/等子文件夹中。当我们需要Moodle脚本的URL时,我们需要考虑配置变量wwwroot。此外,Moodle支持admin/文件夹的重命名,以避免与一些网络托管平台的冲SEO靠我突。
在Moodle中使用URLs的便捷方式是使用moodle_url类。
它负责在URL路径前加上定义的主机(host)和根目录(root);它负责重命名的管理目录;它使得定义要与请求一起提交的HTTPSEO靠我参数变得很容易。moodle_url类实例可以传递给Moodle需要URL的任何函数。核心函数redirect()就是这样一个函数的例子。该类还实现了PHP魔术方法__toString(),因此可以将SEO靠我其视为字符串(String)。
EXAMPLE 显示一个指向给定课程的链接:
$courseid = required_param(courseid, PARAM_INT); $courseSEO靠我viewurl = new moodle_url(/course/view.php, [id => $courseid]); echo <a href=" . $courseviewuSEO靠我rl . ">Back to the course</a>;In this tutorial we explore:
Where and how text stringsSEO靠我 can be defined in plugins.How translations can be distributed together with the plugin.How to formaSEO靠我t date, time and decimal numbers in the user’s language.代码本身不应该有硬编码的文本。相反,Moodle使用了一种SEO靠我内建机制,允许我们在一个专用文件中定义所有文本字符串。所有应该出现在用户界面中的文本都在插件的字符串文件中定义了一个简单的PHP关联数组。英语字符串必须始终存在,它们可以作为翻译成其他语言的源。
EXASEO靠我MPLEThe file mod/quiz/lang/en/quiz.php provides all texts for the Quiz activity module. Among othersSEO靠我 there is a line:
$string[editingquiz] = Editing quiz;where the string editingquiz is defined. To disSEO靠我play that string somewhere in the user interface, the get_string() core function is used:
echo get_stSEO靠我ring(editingquiz, mod_quiz);通常,在Moodle plugins目录中共享的插件应该只提供英文字符串。它们可以由Moodle社区翻译,翻译包含在Moodle的标准语言包中。SEO靠我
如果你正在开发一个不打算公开分享的插件,你可以自己提供和维护翻译。
您仍然应该包含英语作为文本字符串的主要来源。将translations添加到插件的lang/xy/文件夹中,其中xy是Moodle语言SEO靠我包代码(Moodle language pack code)。文件的格式与英文文件的格式完全一致。除了插件之外,核心子系统还在语言文件中定义了它们的字符串。它们位于Moodle安装目录中的lang/eSEO靠我n/文件夹中。
关于如何处理文本字符串及其翻译的更多信息,请参阅开发文档String APl。
Moodle在内部将所有日期和时间存储为Unix时间戳(Unix timesSEO靠我tamps),时间戳基本上是1970年1月1日以来的秒数。
要以用户自己的语言格式显示Unix时间戳(与时区无关),可以使用Time APl提供的函数。
最常见的是,您将使用userdate()函数。
$nSEO靠我ow = time(); echo userdate($now);要手动指定显示格式,请使用core_langconfig组件中定义的strftime formatting strinSEO靠我gs之一。例如,要只显示日期而不显示时间,请使用:
$date = new DateTime("tomorrow", core_date::get_user_timezone_object()); SEO靠我 $date->setTime(0, 0, 0); echo userdate($date->getTimestamp(), get_string(strftimedatefSEO靠我ullshort, core_langconfig));不同的语言在显示小数时使用不同的小数分隔符。使用format_float()核心函数来向用户很好地显示十进制SEO靠我数。实际计算时不要使用此函数!
EXAMPLE 在Moodle中显示的一个典型的十进制数可以是学生的成绩。以小数点后两位的精度显示:
$grade = 20.00 / 3; echo forSEO靠我mat_float($grade, 2);In this tutorial we explore:
How the Page API iSEO靠我s used to describe pages and their context within a Moodle site.How to turn our custom request handlSEO靠我ing script into a Moodle page.How to format the output text.任何Moodle网站上的所有页面都有一些共同的功能SEO靠我,如用户菜单、导航元素或页脚。为了让这些工作正常进行,每个页面都需要知道一些关于自己的信息,例如:
什么地址应该被认为是页面的URL?什么文本应该显示为page title和heading?页面应该采用SEO靠我什么布局,例如,是否应该为边块留出空间等?当前显示的页面由moodle_page类(class)的一个实例表示,该实例可以作为$PAGE全局变量用于请求处理脚本。开发文档Page APl描述了类方法、SEO靠我它们的用途以及如何使用它们。
下面的页面介绍了一些我们将在Hello world插件中使用的常用方法。
.1.1 Page URL每个页面都应该有一个唯一的URL。这是应该用来返回到页面的地址,例如,在编SEO靠我辑它的一个块(block)之后。
$PAGE->set_url(new moodle_url(/local/helloworld/index.php));一旦设置了当前页面的URL,我们就可以通过$PASEO靠我GE->url属性访问它。
echo <form method="get" action=".$PAGE->url.">;它还可以用作其他url的基础。
$nextitemurl = new moodleSEO靠我_url($PAGE->url, [item => $nextitemid]); .1.2 Page context我们必须指定当前页面所属的Moodle上下文。上下文将在本教程后面SEO靠我讨论角色和权限时进行更深入的讨论。出于我们插件的目的,我们将只使用top-level system context。
$PAGE->set_context(context_system::instancSEO靠我e());可以通过PAGE->context属性访问为当前页面设置的上下文(context)。
.1.3 Page title and heading每个页面都应该定义其标题。主题使用它作为页面<heaSEO靠我d>部分内的<title>标签的值。HTML浏览器使用它作为显示页面的窗口或标签的标题。
$PAGE->set_title(get_string(pluginname, local_helloworldSEO靠我));要定义应显示为页面主标题的文本,请定义标题。
$PAGE->set_heading(get_string(hellouser, local_helloworld, $username)); SEO靠我 .1.4 Page layout页面布局布局描述了Moodle页面的通用结构 - 是否有侧边块区域,是否显示导航栏,是否显示页脚等。所有支持的布局都定义在当前使用的主题或其父主题的themSEO靠我e/.../config.php文件中。常用的布局有base(默认),standard, course, frontpage, mydashboard 和 login。
在一些主题,如Classic,默SEO靠我认的基本布局不显示侧块区域。因此,使用带有侧导航块的标准布局来指定自定义脚本是很常见的。
$PAGE->set_pagelayout(standard);一旦设置好页面,输出呈现机制就有足够的信息来实际生成页面的所有HTML。
要完成DOM初始化并开始输出HTTP头文件和实际的<html>内容,调用
echo $OUTPUT->header();$OSEO靠我UTPUT是Moodle在设置过程中创建的一个有用的全局变量。关于Output APl的全部细节超出了本教程的范围,但是现在,只要知道$OUTPUT是一个输出呈现类的实例,它的目的是生成表示页面及其上SEO靠我所有小部件的HTML,就足够了。
要在页面上打印页脚,关闭</body>和</html>标签,并完成输出呈现,调用
echo $OUTPUT->footer(); .2.1 User sSEO靠我ubmitted data在将用户提交的所有内容作为页面HTML的一部分显示之前,必须仔细处理。如果内容来自用户,则绝不应该通过简单的echo()调用来显示它。下一章将描述将要使用的一些辅助函数。
安全SEO靠我原因 - 用户输入可能潜在地包含一些恶意内容 - 有意或无意地提交。将这些内容直接打印到页面HTML源代码将使站点容易受到跨站点脚本和其他安全威胁。功能原因 - 为了确保Moodle的核心功能能够正常SEO靠我工作 - 最显著的是文本过滤或媒体文件嵌入 - 用户提交的内容必须在显示在页面上之前进行处理。应该强调的是,用户提交的内容实际上包括了广泛的数据,包括HTTP查询字符串(HTTP query striSEO靠我ng)、表单数据(form data)、用户代理字符串(user-agent strings)、cookies、请求引用器(request referrer)等。
.2.2 Output helper SEO靠我functions在将用户数据包含到页面的HTML源代码中之前,需要调用一些输出帮助函数来处理这些数据。
s() - 这是PHP原生htmlspecialchars()的包装器。它应该用于我们不想解释任SEO靠我何HTML的纯文本。一个典型的例子可能是HTML标记属性的值。
echo <input type="text" name="uname" value=" . s($uname) . ">;format_SEO靠我string() - 显示短字符串,不使用HTML格式和文本过滤。该字符串由所有配置为应用于网站管理中的“内容和标题”(“Content and headings”)的过滤器处理。用于在课程主页上显示SEO靠我活动模块名称、分组和队列名称、课程名称等文本。
echo format_string($course->name);format_text() - 这是用来显示长格式的文本,应用了所有的filters。SEO靠我Moodle中的丰富文本,例如那些用默认文本编辑器创建的 - 通常已经被格式化为HTML。但根据用户的偏好,情况可能并非总是如此。因此,此函数首先将文本转换为HTML并应用所有过滤器。由此产生的HTMSEO靠我L是任何无效的不安全的部分被清除。通过这个函数删除文本的一个典型例子是图书资源中的一个章节或提交到论坛的一个帖子。
该函数接受$options参数来调优预期的行为。例如,可以设置noclean标志来跳过SEO靠我HTML清理,这可以用于内容只能来自可信用户(如课程中的老师)的地方。
echo format_text($post->content, $post->contentformat, [noclean =SEO靠我> true, context => $context]);下面的流程图可以提供一个粗略的指南,说明在何种情况下使用什么辅助函数,这取决于一些决策。
Rich text - 内容应该支持富文本格式吗?FSEO靠我ilter - 是否应该应用文本过滤器?请注意,为了支持多语言内容,需要过滤。User data - 文本是否包含用户提交的数据?JavaScript - 内联的JavaScript应该被信任并保存在SEO靠我文本中吗? .2.3 HTML tagsMoodle附带了一个高级的输出呈现引擎,它鼓励开发人员将PHP脚本中的逻辑处理与通过呈现器(renderers)和模板(templates)SEO靠我生成HTML表示分离开来。除了其他好处外,它允许主题通过覆盖默认的呈现方法和模板来完全控制生成的HTML。
为了实现本教程演示插件的目的,我们可以使用一种更简单的方法,直接从处理请求的PHP脚本生成HTSEO靠我ML。
Moodle提供了一个名为html_writer的助手类,它提供了大量的静态方法来从PHP脚本生成HTML。一些常用的方法是html_writer::link()或html_writer::imSEO靠我g()。
EXAMPLE 为表单(form)<input>字段生成HTML。
echo html_writer::tag(input, , [type => text,name => username,plSEO靠我aceholder => get_string(typeyourname, local_helloworld),]);Author: Lorain.
Completion time: ???网站备案号:浙ICP备17034767号-2