跳到主要内容

Typecho插件开发教程1:登录界面美化

· 阅读需 10 分钟

本文以登录界面美化为例来介绍Typecho插件开发。之所以选择这个插件,是因为它简单且比较有趣,适合入门学习。

开发环境搭建

工欲善其事,必先利其器。搭建好开发环境对于后续进行插件开发来说是很重要的,有个好用的 IDE 能有效提升开发效率。这里推荐使用 JetBrains 家的 PhpStorm,配合XDebug插件,来作为我们的开发环境。

PhpStorm的安装,省略。如果有edu邮箱可以免费使用,其余的自行解决。官网:PhpStorm: The Lightning-Smart IDE for PHP Programming by JetBrains

XDebug的安装,参考官网:Xdebug: Documentation » Installation

我的是Linux环境,安装配置总体上还是比较简单的。php.ini里配置xdebug(仅供参考,对于Windows,zend_extension那一行的配置肯定不一样):

[xdebug]
zend_extension=/usr/lib/php/modules/xdebug.so
xdebug.remote_enable = 1
xdebug.remote_handler = dbgp
xdebug.remote_mode = req
xdebug.remote_host = localhost
xdebug.remote_port = 9000
xdebug.idekey = PHPSTORM

之后可以在PhpStorm的设置里的 PHP > Debug 那里验证一下,确认端口号和上面配置的一样(默认:9000),验证全部通过就OK了:

IDE 拥有代码补全、智能提示、断点调试等功能,在开发过程中还是非常实用的。

插件接口

给插件取个名字,假设叫LoginBeautify吧,名字注意不能有_。然后在Typecho的插件目录创建一个和插件名一致的文件夹(也就是LoginBeautify),在新建的这个文件夹下创建文件Plugin.php。这个是Typecho的约定,Typecho会自动扫描插件目录,解析目录下的Plugin.php

Plugin.php内键入以下内容,主要是插件的描述信息,填写一下就行:

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
* 登录界面美化
*
* @package LoginBeautify
* @author jlice
* @version 1.0.0
* @link https://muranxuan.top
*/
class LoginBeautify_Plugin implements Typecho_Plugin_Interface
{
}

这里需要注意两点:

  1. 插件的类名必须是XXX_Plugin(其中XXX为插件的名字),否则Typecho无法正确加载插件。
  2. 必须实现Typecho_Plugin_Interface接口,也就是要implements Typecho_Plugin_Interface

Typecho_Plugin_Interface有一些方法需要我们实现。如果像上面这样空着,会报错,此时在红线上按Alt+Enter快捷键,然后会提示 Add method stubs,然后按Enter就会自动生成相应的方法代码了,还有默认的注释。

有4个方法,其含义很好理解。activatedeactivate分别是启用和禁用插件时运行的代码,configpersonalConfig分别是插件的配置面板和用户的配置面板(也就是在个人设置那里出现)。

需要注意的是,上面这些方法都是静态的(有static修饰),粗糙点说就是不能用$this

钩子

插件是为了便于扩展程序的功能,Typecho在很多地方预留了钩子,供插件挂载。这个要怎么理解呢?结合钩子的图片:

假如Typecho的作者觉得某个地方可以交给插件去完成,可以在这个地方放一个钩子,然后插件挂载到这个钩子上(有点像把东西挂在挂钩上),程序在运行时,会检查钩子上有没有挂载插件,如果有会执行插件的代码。

通过上面的理解,我们可以知道,插件要声明注册到哪个钩子,执行什么代码,这样Typecho才能知道何时执行以及执行什么代码。当然,插件也可以挂载到多个钩子。实际上,Typecho在启用或禁用插件时就会把插件的数据保存到数据库,位于options表里。

那么,Typecho预留了哪些钩子呢?

Typecho预留的钩子以两种形式出现:

Typecho_Plugin::factory()->function();
$this->pluginHandle()->function();

如果要看所有的钩子,可以参考官方文档,或者在代码里搜索。选择网站的根目录,然后按Ctrl+Shift+F搜索:

找到合适的钩子

钩子那么多,怎么能知道哪个钩子符合自己的需求呢?这个问题,em...,其实不太好回答。

我们注意到两个目录:一个是var/Widget/,这里是Typecho的核心代码,里面有Typecho定义的各种Widget;另一个是admin/,这里是后台管理的代码。

Widget是组件的意思,例如Widget_Archive就是输出文章的,例如首页和文章页面。Widget里的钩子可以定制这些组件的行为。具体有哪些组件可以参考官方文档,或者直接看var/Widget/这个目录就可以了。

钩子的位置有两个层面的意思,或者说为了指定要挂载哪个钩子,我们需要确定两件事:一是这个钩子在哪,对于admin/下的,需要指定.php文件位置,对于var/Widget/里的钩子,只需要指明类名即可(也就是Widget的名字);二是这个钩子的名字,也就是function的名字。

下面是一些例子:

// var/Widget/Upload.php
$result = Typecho_Plugin::factory('Widget_Upload')->trigger($hasUploaded)->uploadHandle($file);
// Widget: Widget_Upload function: uploadHandle

// var/Widget/Contents/Post/Edit.php
$this->pluginHandle()->finishPublish($contents, $this);
// Widget: Widget_Contents_Post_Edit function: finishPublish

// admin/write-js.php
Typecho_Plugin::factory('admin/write-js.php')->write();
// php: admin/write-js.php function: write

需要注意的是第一个例子里的trigger并不是钩子的名字,这个是Typecho用来判断是否有插件挂载到这里用的。

由于我们要做登录界面美化,也就是改登录界面背景,很自然地想到后台管理的登录界面,也就是admin/login.php。然而,这个文件里并没有提供钩子。但是,我们注意到,在这个文件的最后引入了footer.php

<?php
include 'footer.php';

>

按住Ctrl键点击footer.php,可以跳转到footer.php,而footer.php里有一个钩子:

/** 注册一个结束插件 */
Typecho_Plugin::factory('admin/footer.php')->end();

于是,我们在插件的activate方法里挂载这个钩子:

public static function activate()
{
Typecho_Plugin::factory('admin/footer.php')->end = array('LoginBeautify_Plugin', 'end');
}

我们把LoginBeautify_Plugin这个类(也就是本插件的类名)的end方法挂载到这个钩子上。不过,我们的LoginBeautify_Plugin类并没有end方法,因此需要添加一个end方法。现在LoginBeautify_Plugin类的代码如下(省略了注释):

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;

class LoginBeautify_Plugin implements Typecho_Plugin_Interface
{
public static function activate()
{
Typecho_Plugin::factory('admin/footer.php')->end = array('LoginBeautify_Plugin', 'end');
}
public static function deactivate(){}
public static function config(Typecho_Widget_Helper_Form $form){}
public static function personalConfig(Typecho_Widget_Helper_Form $form){}
public static function end()
{
}
}

由于钩子end没有输入参数,所以我们这里增加的end方法也没有输入参数。如果钩子有输入参数,我们对应增加的方法里应该有对应的输入参数。

使用Widget

其实后台管理的界面都引入了footer.php,因此,我们需要判断用户的登录状态,当用户没有登录时才修改背景,否则就会影响到后台管理的其它界面。

我们注意到login.php的最开始有如下代码:

<?php
include 'common.php';

if ($user->hasLogin()) {
$response->redirect($options->adminUrl);
}

这几行代码的意思是:引入common.php,如果用户已经登录了,就跳转到管理地址。可是,$user$response$options这些变量哪来的呢?

按住Ctrl键点击common.php,可以跳转到common.php。可以发现如下代码:

Typecho_Widget::widget('Widget_Options')->to($options);
Typecho_Widget::widget('Widget_User')->to($user);
Typecho_Widget::widget('Widget_Security')->to($security);
Typecho_Widget::widget('Widget_Menu')->to($menu);

Typecho_Widget::widget是一个工厂方法,可以返回类的实例。to()方法可以把这个实例赋值给某个变量。

因此,在我们的插件里页可以采用类似的做法(图片地址替换成自己的哦):

public static function end()
{
Typecho_Widget::widget('Widget_User')->to($user);
if (!$user->hasLogin()) {
?> <script>$("body").css("background", "url('https://muranxuan.top/usr/uploads/demo.jpg')");</script> <?php
}
}

当用户没有登录时,我们需要做点事情。这里,我们通过jQuery来修改页面的背景。效果如下:

表单配置

目前,页面的背景是写死在代码里的,不支持配置。所以,我们希望插件能配置页面背景的 URL。为此,在config方法里,增加背景 URL 的输入框:

public static function config(Typecho_Widget_Helper_Form $form)
{
$url = new Typecho_Widget_Helper_Form_Element_Text('bgUrl', NULL, NULL, _t("背景URL"));
$form->addInput($url);
}

Typecho_Widget_Helper_Form_Element_Text可以用来创建一个文本框,有5个参数:表单输入项名称,选择项,默认值,标题,描述。

end方法里,我们将原先写死的 URL 换成表单里bgUrl的值(注意写法,是上面第1个参数bgUrl哦,不是=左边的$url):

public static function end()
{
Typecho_Widget::widget('Widget_User')->to($user);
$url = Typecho_Widget::widget('Widget_Options')->plugin('LoginBeautify')->bgUrl;
if (!$user->hasLogin()) {
?> <script>$("body").css("background", "url(<?= $url ?>)");</script> <?php
}
}

这样,我们就可以在插件配置界面去设置背景 URL 了。

如果出现下面的错误,把插件禁用再启用就可以了:

PHP模板语法

也许对于不熟悉 PHP 的人来说,前面的代码里,用户没有登录时设置背景这一行可能看得不太懂:


> <script>$("body").css("background", "url(<?= $url ?>)");</script> <?php

首先,?>是 PHP 的结束标签,表示 PHP 代码告一段落了。后面接着是输出的 HTML 了。在 HTML 里,我们写了一小段 JavaScript 代码,用来设置背景图片的 URL。为了能在 HTML 里拼接 PHP 里的变量,使用<?= ?>这样的模板语法,这样我们就把从插件配置里获取的背景图片 URL 插入到了这里。最后是<?php表示又开始是 PHP 代码了。

貌似有点绕?我们也可以用纯 PHP 做这件事,这一行代码可以替换为:

echo '<script>$("body").css("background", "url(' .  $url . ')");</script>';

其中,.是 PHP 里字符串拼接符,利用 PHP 拼接出上面的 HTML 内容,然后利用echo输出到页面。

下面是本插件的全部代码,便于学习:

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
* 登录界面美化
*
* @package LoginBeautify
* @author jlice
* @version 1.0.0
* @link https://muranxuan.top
*/
class LoginBeautify_Plugin implements Typecho_Plugin_Interface
{
public static function activate()
{
Typecho_Plugin::factory('admin/footer.php')->end = array('LoginBeautify_Plugin', 'end');
}

public static function deactivate() {}

public static function config(Typecho_Widget_Helper_Form $form)
{
$url = new Typecho_Widget_Helper_Form_Element_Text(_t('bgUrl'), NULL, NULL, _t("背景URL"));
$form->addInput($url);
}

public static function personalConfig(Typecho_Widget_Helper_Form $form) {}

public static function end()
{
Typecho_Widget::widget('Widget_User')->to($user);
$url = Typecho_Widget::widget('Widget_Options')->plugin('LoginBeautify')->bgUrl;
if (!$user->hasLogin()) {
echo '<script>$("body").css("background", "url(' . $url . ')");</script>';
}
}
}

由于本期是Typecho插件教程的第1期,所以写得非常的基础,应该很容易看懂吧(我自己是这么觉得的)。

如果需要更好的登录界面美化,可以参考这个插件:Typecho登录/注册美化插件 - Typecho爱好者博客

好了,本期的插件教程就到这里了,你学会了么?赶紧动手试试吧!