插件注册

Learn how to create and register plugins for Winter CMS.

Introduction

插件是添加新功能或扩展 Winter CMS 基本功能的基础。插件注册过程允许插件声明它们的特性,例如components、导航项和后端页面。插件可以做什么的一些例子:

目录结构

插件位于/plugins 应用程序目录的子目录。根据要求,插件可以从极其简单到非常复杂。最简单的插件只需要一个Plugin.php 文件,但它们可以根据需要一直容纳整个应用程序结构。

简单的插件结构

最简单的插件只需要Plugin.php 文件如下所述。

📂 plugins
 ┗ 📂 myauthor          /* Author name */
   ┗ 📂 myplugin        /* Plugin name */
     ┗ 📜 Plugin.php    /* Plugin registration file, required */

典型的插件结构

以下是大多数插件在与最常用的 Winter CMS 功能交互时最终会是什么样子的示例。

NOTE: 如果您正在为Marketplace, 这updates/version.yaml 文件是必需的。

📂 plugins
 ┗ 📂 myauthor              /* Author name */
   ┗ 📂 myplugin            /* Plugin name */
     ┣ 📂 assets            /* CSS, JavaScript and image assets for pages and components */
     ┣ 📂 components        /* Frontend components */
     ┣ 📂 controllers       /* Backend controllers */
     ┣ 📂 lang              /* Localization files */
     ┃ ┗ 📂 en              /* Specific locale folder */
     ┃   ┗ 📜 lang.php      /* Translations */
     ┣ 📂 models            /* Models */
     ┣ 📂 updates           /* Database migrations */
     ┃ ┗ 📜 version.yaml    /* Changelog */
     ┣ 📂 views             /* Custom view files */
     ┃ ┗ 📂 mail            /* Custom mail templates */
     ┣ 📜 README.md         /* Documentation describing the purpose of the plugin */
     ┗ 📜 Plugin.php        /* Plugin registration class */

复杂的插件结构

以下是一个复杂插件在使用 Winter CMS 提供的大量功能以及提供其自己的一些功能时的外观示例。

📂 plugins
 ┗ 📂 myauthor                              /* Author name */
   ┗ 📂 myplugin                            /* Plugin name */
     ┣ 📂 assets                            /* CSS, JavaScript and image assets for pages and components */
     ┃ ┣ 📂 css
     ┃ ┣ 📂 favicons
     ┃ ┣ 📂 images
     ┃ ┣ 📂 js
     ┃ ┗ 📂 scss
     ┣ 📂 behaviors                         /* Any custom behaviors provided by the plugin */
     ┣ 📂 classes                           /* Any custom classes provided by the plugin */
     ┣ 📂 components                        /* Components frontend */
     ┃ ┣ 📂 record                          /* Folder for the Record component's partials */
     ┃ ┃ ┗ 📜 default.htm                   /* The default partial that's rendered by the component */
     ┃ ┣ 📂 partials                        /* Any partials shared by more than one component in the plugin */
     ┃ ┗ 📜 Record.php                      /* Record Component that probably handles retrieving and displaying a single record */
     ┣ 📂 config                            /* Configuration files */
     ┃ ┗ 📜 config.php
     ┣ 📂 console                           /* Any custom CLI commands provided by the plugin */
     ┣ 📂 controllers                       /* Backend controllers */
     ┃ ┣ 📂 records                         /* Directory for the view and configuration files for the given controller */
     ┃ ┃ ┣ 📜 _list_toolbar.php             /* List toolbar partial file */
     ┃ ┃ ┣ 📜 config_filter.yaml            /* Configuration for the Filter widget present on the controller lists */
     ┃ ┃ ┣ 📜 config_form.yaml              /* Configuration for the Form widget present on the controller */
     ┃ ┃ ┣ 📜 config_importexport.yaml      /* Configuration for the Import/Export behavior */
     ┃ ┃ ┣ 📜 config_list.yaml              /* Configuration for the Lists widget present on the controller */
     ┃ ┃ ┣ 📜 config_relation.yaml          /* Configuration for the RelationController behavior */
     ┃ ┃ ┣ 📜 create.php                    /* View file for the create action */
     ┃ ┃ ┣ 📜 index.php                     /* View file for the index action */
     ┃ ┃ ┣ 📜 preview.php                   /* View file for the preview action */
     ┃ ┃ ┗ 📜 update.php                    /* View file for the update action */
     ┃ ┗ 📜 Records.php                     /* Backend controller for the Record model */
     ┣ 📂 docs                              /* Any plugin-specific documentation should live here */
     ┣ 📂 formwidgets                       /* Any custom FormWidgets provided by the plugin */
     ┣ 📂 lang                              /* Localization files */
     ┃ ┗ 📂 en                              /* Specific locale folder */
     ┃   ┗ 📜 lang.php                      /* Translations for that locale */
     ┣ 📂 layouts                           /* Any custom backend layouts used by the plugin */
     ┣ 📂 models                            /* Models provided by the plugin */
     ┃ ┣ 📂 record                          /* Directory containing configuration files specific to that model */
     ┃ ┃ ┣ 📜 columns.yaml                  /* Configuration file used for the Lists widget */
     ┃ ┃ ┗ 📜 fields.yaml                   /* Configuration file used for the Form widget */
     ┃ ┗ 📜 Record.php                      /* Model class for the Record model */
     ┣ 📂 partials                          /* Any custom partials used by the plugin */
     ┣ 📂 reportwidgets                     /* Any custom ReportWidgets provided by the plugin */
     ┣ 📂 tests                             /* Test suite for the plugin */
     ┣ 📂 traits                            /* Any custom Traits provided by the plugin */
     ┣ 📂 updates                           /* Database migrations */
     ┃ ┃ ┗ 📂 v1.0.0                        /* Migrations for a specific version of the plugin */
     ┃ ┃   ┗ 📜 create_records_table.php    /* Database migration file, referenced in version.yaml */
     ┃ ┗ 📜 version.yaml                    /* Changelog */
     ┣ 📂 views                             /* Custom view files */
     ┃ ┗ 📂 mail                            /* Custom mail templates provided by the plugin */
     ┣ 📂 widgets                           /* Any custom Widgets provided by the plugin */
     ┣ 📜 LICENSE                           /* License file */
     ┣ 📜 README.md                         /* Documentation describing the purpose of the plugin */
     ┣ 📜 Plugin.php                        /* Plugin registration file */
     ┣ 📜 composer.json                     /* Composer file to manage dependencies for the plugin */
     ┣ 📜 helpers.php                       /* Global helpers provided by the plugin loaded via composer.json */
     ┣ 📜 phpunit.xml                       /* Unit testing configuration */
     ┣ 📜 plugin.yaml                       /* Simplified plugin registration configuration YAML file, used by Builder plugin */
     ┗ 📜 routes.php                        /* Any custom routes provided by the plugin */

插件命名空间

插件命名空间非常重要,特别是如果您要在Winter市场.通过使用唯一的插件命名空间,您可以消除插件与其他作者冲突的可能性。

当您在 Marketplace 上注册为作者时,系统会要求您提供作者代码,该代码应用作所有插件的根命名空间。注册时,您只能指定一次作者代码。

Marketplace 提供的默认作者代码由作者的名字和姓氏组成:JohnSmith。注册后无法更改代码。例如,您所有的插件命名空间都应该在根命名空间下定义\JohnSmith\Blog.

注册文件

Plugin.php 文件,称为插件注册文件, 是声明插件核心功能和信息的初始化脚本。在确定可用插件时,将在 Winter CMS 的启动过程中读取该文件。注册文件可以提供以下内容:

  1. 有关插件、名称和作者的信息。
  2. 用于扩展 CMS 和说明插件意图的注册方法。

注册脚本应该使用插件命名空间。注册脚本应该定义一个类名Plugin 扩展了\System\Classes\PluginBase 班级。插件注册类唯一需要的方法是pluginDetails 方法。插件注册文件示例:

<?php

namespace Acme\Blog;

class Plugin extends \System\Classes\PluginBase
{
    public function pluginDetails()
    {
        return [
            'name' => 'Blog Plugin',
            'description' => 'Provides some really cool blog features.',
            'author' => 'ACME Corporation',
            'icon' => 'icon-snowflake-o'
        ];
    }

    public function registerComponents()
    {
        return [
            'Acme\Blog\Components\Post' => 'blogPost'
        ];
    }
}

支持的方法

插件注册类支持以下方法:

Method Description
pluginDetails() 返回有关插件的信息。
register() register 方法,在首次注册插件时调用。
boot() 引导方法,在请求路由之前调用。
registerComponents() 注册任何前端组件 被这个插件使用。
registerFormWidgets() 注册任何后端表单小部件 由这个插件提供。
registerListColumnTypes() 注册任何自定义列表列类型 由这个插件提供。
registerMailLayouts() 注册任何邮件视图布局 由这个插件提供。
registerMailPartials() 注册任何邮件视图部分 由这个插件提供。
registerMailTemplates() 注册任何邮件视图模板 由这个插件提供。
registerMarkupTags() 寄存器附加标记可以在 CMS 中使用。
registerNavigation() 寄存器后端导航菜单项 对于这个插件。
registerPermissions() 注册任何后端权限 被这个插件使用。
registerReportWidgets() 注册任何后端报告小部件,包括仪表板小部件。
registerSchedule() 寄存器计划任务 定期执行。
registerSettings() 注册任何后端配置链接 被这个插件使用。
registerValidationRules() 注册任何自定义验证器 由这个插件提供。

基本插件信息

pluginDetails 是插件注册类的必需方法。它应该返回一个包含以下键的数组:

Key Description
name 插件名称,必填。
description 插件描述,必填。
author 插件作者姓名,必填。
icon 插件图标的名称。可用图标的完整列表可以在用户界面文档.此字体提供的任何图标名称均有效,例如icon-glass,icon-music.此键是必需的,如果iconSvg 未设置。
iconSvg 用于代替标准图标的 SVG 图标。 SVG 图标应该是一个矩形并且可以支持颜色。此键是必需的,如果icon 未设置。
homepage 指向作者网站地址的链接,可选。

路由和初始化

插件注册文件可以包含两种方法:bootregister.这些方法在 Winter CMS 启动过程中的不同点运行。

register 找到插件并读取插件注册文件时立即调用方法。它可用于注册或提供全局服务、定义底层框架内的功能或初始化插件的功能。

public function register()
{
    App::register(MyProviderClass::class, function ($app) {
        return new MyProviderClass();
    });
}

NOTE:register 方法在 Winter CMS 启动过程的早期运行,Winter CMS 或 Laravel 中的某些功能在那个阶段可能不可用,特别是在第三方插件或服务方面。你应该使用boot 依赖于第三方插件或服务的任何功能的方法。

boot 加载所有服务并注册所有插件后调用方法。此方法应用于定义要在每次页面加载时运行的功能,例如扩展插件或附加到事件。

public function boot()
{
    User::extend(function($model) {
        $model->hasOne['author'] = ['Acme\Blog\Models\Author'];
    });
}

bootregister 在更新过程中或在某些关键的后端部分和命令行工具中不会调用方法,以保护系统免受严重错误的影响。要克服此限制,请使用提升权限.

插件还可以提供一个名为routes.php 可能包含自定义路由逻辑,如定义在路由服务.例如:

Route::group(['prefix' => 'api_acme_blog'], function() {
    Route::get('cleanup_posts', function(){ return Posts::cleanUp(); });
});

最后,插件还可以提供一个名为init.php.该文件的作用类似于boot 方法,因为它可用于定义在每个页面加载时运行的功能,但在更全局的上下文中。

依赖定义

一个插件可以通过定义一个依赖于其他插件$require 中的财产插件注册文件.该属性应包含一个插件名称数组,这些插件名称被认为是此插件运行的要求。依赖于的插件Acme.User 插件可以通过以下方式声明此要求:

namespace Acme\Blog;

class Plugin extends \System\Classes\PluginBase
{
    /**
     * @var array Plugin dependencies
     */
    public $require = ['Acme.User'];

    // [...]
}

依赖定义将影响插件的操作方式和更新过程如何订购和应用更新.安装过程将尝试自动安装任何依赖项,但是如果在系统中检测到插件没有任何依赖项,它将自动禁用以防止系统错误。

应注意确保不进行循环依赖引用——例如,如果Acme.Demo 插件取决于Acme.Blog 插件,Acme.Blog 插件也不应该依赖于Acme.Demo 插入。

延长树枝

自定义 Twig 过滤器和函数可以在 CMS 中注册registerMarkupTags 插件注册类的方法。

树枝选项 也可以通过提供一个数组来传递以更改已注册过滤器和函数的行为'options' 包含要在注册时传递的选项的元素,其中通常会提供可调用值。如果提供了选项,那么正在注册的过滤器/函数的可调用处理程序必须存在于'callable' 元素或作为数组的第一个元素。

IMPORTANT:通过注册的所有自定义 Twig 过滤器和功能MarkupManager (IE。registerMarkupTags() 将有is_safe 选项设置为['html'] 默认情况下,这意味着 Twig 的自动转义在默认情况下是禁用的(实际上就好像| raw 过滤器总是位于你的过滤器或函数的输出之后)除非你提供is_safe 注册时的选项('options' => ['is_safe' => []]).

下一个示例注册了三个 Twig 过滤器和三个函数。

public function registerMarkupTags()
{
    return [
        'filters' => [
            // A global function, i.e str_plural()
            'plural' => 'str_plural',

            // A local method, i.e $this->makeTextAllCaps()
            'uppercase' => [$this, 'makeTextAllCaps'],

            // Any callable with custom options defined - first element method
            'userInputToEmojis' => ['input_to_emojis', 'options' => ['is_safe' => []]],
        ],
        'functions' => [
            // A static method call, i.e Form::open()
            'form_open' => ['Winter\Storm\Html\Form', 'open'],

            // Using an inline closure
            'helloWorld' => function() { return 'Hello World!'; },

            // Any callable with custom options defined - named 'callable' method
            'goodbyeWorld' => [
                'callable' => ['Acme\Plugin\Nuke', 'boom'],
                'options'  => ['needs_environment' => true],
            ],
        ],
    ];
}

public function makeTextAllCaps($text)
{
    return strtoupper($text);
}

以下 Twig 自定义选项可用:

Option Type Default Description
needs_environment boolean false 如果 true 提供当前TwigEnvironment 作为过滤器调用的第一个参数
needs_context boolean false 如果 true 提供当前TwigContext 作为第一个参数(第二个如果needs_environment also set) 到过滤器调用
is_safe array [] 语言数组(通常html 或者all 是有效值)过滤器/函数的输出是安全的,无需转义
pre_escape string '' (仅过滤器)将在将值传递给您设置的语言的过滤器之前预先转义该值(通常'html')
preserves_safety array [] (仅过滤器)语言数组(通常html) 过滤器将保留链中先前过滤器的安全设置。也就是说,如果链中的前一个过滤器说它是安全的并且不需要转义那么这个也不会,但是如果它说它不安全并且需要转义那么这个也一样。
is_variadic boolean false 如果为真,则将提供给过滤器的任何额外参数作为单个数组作为过滤器调用的最后一个参数传递
deprecated boolean false 如果为真,则将当前过滤器标记为已弃用(通常与alternative 提供替代选择
alternative string '' 如果deprecated 是真的,提供了一个推荐的替代过滤器来代替使用。

导航菜单

插件可以通过覆盖registerNavigation 的方法插件注册类.本节向您展示如何将菜单项添加到后端导航区域。使用两个子菜单项注册顶级导航菜单项的示例:

public function registerNavigation()
{
    return [
        'blog' => [
            'label'       => 'Blog',
            'url'         => Backend::url('acme/blog/posts'),
            'icon'        => 'icon-pencil',
            'permissions' => ['acme.blog.*'],
            'order'       => 500,
            // Set counter to false to prevent the default behaviour of the main menu counter being a sum of
            // its side menu counters
            'counter'     => ['\Author\Plugin\Classes\MyMenuCounterService', 'getBlogMenuCount'],
            'counterLabel'=> 'Label describing a dynamic menu counter',
            // Optionally you can set a badge value instead of a counter to display a string instead of a numerical counter
            'badge'       => 'New',

            'sideMenu' => [
                'posts' => [
                    'label'       => 'Posts',
                    'icon'        => 'icon-copy',
                    'url'         => Backend::url('acme/blog/posts'),
                    'permissions' => ['acme.blog.access_posts'],
                    'counter'     => 2,
                    'counterLabel'=> 'Label describing a static menu counter',
                ],
                'categories' => [
                    'label'       => 'Categories',
                    'icon'        => 'icon-copy',
                    'url'         => Backend::url('acme/blog/categories'),
                    // If the value is a callable and it returns `0`, then it will be treated as if it returns `null`, displaying nothing.
                    // If the value is an explicit value, then `0` will be displayed if provided.
                    'counter'     => 0,
                    'permissions' => ['acme.blog.access_categories'],
                ]
            ]
        ]
    ];
}

当您注册后端导航时,您可以使用本地化字符串 为了label 值。后端导航也可以由permissions 值并对应于定义后端用户权限.后端导航出现在整体导航菜单项上的顺序由order 价值。较高的数字意味着该项目将在菜单项的顺序中出现得较晚,而较小的数字意味着它会出现在较早的位置。

要使子菜单项可见,您可以设置导航上下文 在后端控制器中使用BackendMenu::setContext 方法。这将使父菜单项处于活动状态并在侧边菜单中显示子项。

Key Description
label 指定菜单标签本地化字符串键,必需。
icon 来自的图标名称Winter CMS 图标集合, 选修的。
iconSvg 用于代替标准图标的 SVG 图标,SVG 图标应该是一个矩形并且可以支持颜色,可选。
url 菜单项应指向的 URL(例如Backend::url('author/plugin/controller/action'), 必需的。
counter 在菜单图标附近输出的数值。该值应该是一个数字或一个返回数字的可调用对象,可选。
counterLabel 一个字符串值,用于描述计数器中的数字引用,可选。
badge 一个字符串值来代替计数器输出,该值应该是一个字符串,如果设置,它将覆盖 badge 属性,可选。
attributes 应用于菜单项的属性和值的关联数组,可选。
permissions 后端用户必须拥有的一系列权限才能查看菜单项(注意:直接访问 URL 仍需要单独的权限检查),可选。
code 作为该菜单选项的唯一标识符的字符串值。NOTE:这是系统生成的值,不应在注册导航项时提供。
owner 一个字符串值,以“Author.Plugin”格式指定菜单项所有者插件或模块。NOTE:这是系统生成的值,不应在注册导航项时提供。

注册中间件

要注册自定义中间件,您可以通过使用将其直接应用于插件中的后端控制器控制器中间件,或者您可以使用以下方法扩展 Controller 类。

public function boot()
{
    \Cms\Classes\CmsController::extend(function($controller) {
        $controller->middleware('Path\To\Custom\Middleware');
    });
}

或者,您可以通过以下方式将其直接推送到内核中。

public function boot()
{
    // Add a new middleware to beginning of the stack.
    $this->app['Illuminate\Contracts\Http\Kernel']
        ->prependMiddleware('Path\To\Custom\Middleware');

    // Add a new middleware to end of the stack.
    $this->app['Illuminate\Contracts\Http\Kernel']
        ->pushMiddleware('Path\To\Custom\Middleware');
}

提升权限

默认情况下,插件被限制访问系统的某些区域。这是为了防止可能将管理员锁定在后端之外的严重错误。当在没有提升权限的情况下访问这些区域时,bootregister 初始化方法 因为插件不会触发。

Request Description
/combine 资产组合器生成器 URL
/backend/system/updates 站点更新上下文
/backend/system/install 安装程序路径
/backend/backend/auth 后端认证路径(登录、注销)
winter:up 运行所有挂起迁移的 CLI 命令
winter:update 触发更新过程的 CLI 命令
winter:env 将配置文件转换为环境变量的 CLI 命令.env 文件
winter:version 检测安装的 Winter CMS 版本的 CLI 命令

定义$elevated 属性为您的插件授予提升的权限。

/**
 * @var bool Plugin requires elevated permissions.
 */
public $elevated = true;
豫ICP备18041297号-2