install addons

This commit is contained in:
zhao 2020-02-18 19:28:14 +08:00
parent a9f5deaf52
commit f68a930812
19 changed files with 1657 additions and 4 deletions

274
addons/README.md Normal file
View File

@ -0,0 +1,274 @@
# think-addons
The ThinkPHP 6 Addons Package
## 安装
> composer require zzstudio/think-addons
## 配置
### 生成配置
系统安装后会自动在 config 目录中生成 addons.php 的配置文件,
如果系统未生成可在命令行执行
```php
php think addons:config
```
快速生成配置文件
### 公共配置
```php
'addons' => [
// 是否自动读取取插件钩子配置信息(默认是开启)
'autoload' => true,
// 当关闭自动获取配置时需要手动配置hooks信息
'hooks' => [
// 可以定义多个钩子
'testhook'=>'test' // 键为钩子名称,用于在业务中自定义钩子处理,值为实现该钩子的插件,
// 多个插件可以用数组也可以用逗号分割
],
'route' => [],
'service' => [],
];
```
或者在\config目录中新建`addons.php`,内容为:
```php
<?php
return [
// 是否自动读取取插件钩子配置信息
'autoload' => false,
// 当关闭自动获取配置时需要手动配置hooks信息
'hooks' => [
// 可以定义多个钩子
'testhook'=>'test' // 键为钩子名称,用于在业务中自定义钩子处理,值为实现该钩子的插件,
// 多个插件可以用数组也可以用逗号分割
],
'route' => [],
'service' => [],
];
```
## 创建插件
> 创建的插件可以在view视图中使用也可以在php业务中使用
安装完成后访问系统时会在项目根目录生成名为`addons`的目录,在该目录中创建需要的插件。
下面写一个例子:
### 创建test插件
> 在addons目录中创建test目录
### 创建钩子实现类
> 在test目录中创建 Plugin.php 类文件。注意:类文件首字母需大写
```php
<?php
namespace addons\test; // 注意命名空间规范
use think\Addons;
/**
* 插件测试
* @author byron sampson
*/
class Plugin extends Addons // 需继承think\Addons类
{
// 该插件的基础信息
public $info = [
'name' => 'test', // 插件标识
'title' => '插件测试', // 插件名称
'description' => 'thinkph6插件测试', // 插件简介
'status' => 0, // 状态
'author' => 'byron sampson',
'version' => '0.1'
];
/**
* 插件安装方法
* @return bool
*/
public function install()
{
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
return true;
}
/**
* 实现的testhook钩子方法
* @return mixed
*/
public function testhook($param)
{
// 调用钩子时候的参数信息
print_r($param);
// 当前插件的配置信息配置信息存在当前目录的config.php文件中见下方
print_r($this->getConfig());
// 可以返回模板,模板文件默认读取的为插件目录中的文件。模板名不能为空!
return $this->fetch('info');
}
}
```
### 创建插件配置文件
> 在test目录中创建config.php类文件插件配置文件可以省略。
```php
<?php
return [
'display' => [
'title' => '是否显示:',
'type' => 'radio',
'options' => [
'1' => '显示',
'0' => '不显示'
],
'value' => '1'
]
];
```
### 创建钩子模板文件
> 在test->view目录中创建info.html模板文件钩子在使用fetch方法时对应的模板文件。
```html
<h1>hello tpl</h1>
如果插件中需要有链接或提交数据的业务可以在插件中创建controller业务文件
要访问插件中的controller时使用addon_url生成url链接。
如下:
<a href="{:addons_url('Action/link')}">link test</a>
<a href="{:addons_url('test://Action/link')}">link test</a>
格式为:
test为插件名Action为controller中的类名[多级控制器可以用.分割]link为controller中的方法
```
### 创建插件的controller文件
> 在test目录中创建controller目录在controller目录中创建Index.php文件
> controller类的用法与tp6中的controller一致
```php
<?php
namespace addons\test\controller;
class Index
{
public function link()
{
echo 'hello link';
}
}
```
## 使用钩子
> 创建好插件后就可以在正常业务中使用该插件中的钩子了
> 使用钩子的时候第二个参数可以省略
### 模板中使用钩子
```html
<div>{:hook('testhook', ['id'=>1])}</div>
```
### php业务中使用
> 只要是thinkphp6正常流程中的任意位置均可以使用
```php
hook('testhook', ['id'=>1])
```
### 插件公共方法
```php
/**
* 处理插件钩子
* @param string $event 钩子名称
* @param array|null $params 传入参数
* @param bool $once 是否只返回一个结果
* @return mixed
*/
function hook($event, $params = null, bool $once = false);
/**
* 读取插件的基础信息
* @param string $name 插件名
* @return array
*/
function get_addons_info($name);
/**
* 获取插件Plugin的单例
* @param string $name 插件名
* @return mixed|null
*/
function get_addons_instance($name);
/**
* 插件显示内容里生成访问插件的url
* @param $url 在插件控制器中可忽略插件名,在非插件中生成时需指定插件名。例:插件名://控制器/方法
* @param array $param
* @param bool|string $suffix 生成的URL后缀
* @param bool|string $domain 域名
* @return bool|string
*/
function addons_url($url = '', $param = [], $suffix = true, $domain = false);
```
## 插件目录结构
### 最终生成的目录结构为
```html
www WEB部署目录或者子目录
├─addons 插件目录
├─app 应用目录
│ ├─controller 控制器目录
│ ├─model 模型目录
│ ├─ ... 更多类库目录
│ │
│ ├─common.php 公共函数文件
│ └─event.php 事件定义文件
├─config 配置目录
│ ├─app.php 应用配置
│ ├─cache.php 缓存配置
│ ├─console.php 控制台配置
│ ├─cookie.php Cookie配置
│ ├─database.php 数据库配置
│ ├─filesystem.php 文件磁盘配置
│ ├─lang.php 多语言配置
│ ├─log.php 日志配置
│ ├─middleware.php 中间件配置
│ ├─route.php URL和路由配置
│ ├─session.php Session配置
│ ├─trace.php Trace配置
│ └─view.php 视图配置
├─view 视图目录
├─route 路由定义目录
│ ├─route.php 路由定义文件
│ └─ ...
├─public WEB目录对外访问目录
│ ├─index.php 入口文件
│ ├─router.php 快速测试文件
│ └─.htaccess 用于apache的重写
├─extend 扩展类库目录
├─runtime 应用的运行时目录(可写,可定制)
├─vendor Composer类库目录
├─.example.env 环境变量示例文件
├─composer.json composer 定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行入口文件
```

View File

@ -27,7 +27,8 @@
"taoser/think-auth": "^1.0",
"topthink/think-multi-app": "^1.0",
"topthink/think-captcha": "^3.0",
"topthink/think-view": "^1.0"
"topthink/think-view": "^1.0",
"zzstudio/think-addons": "^2.0"
},
"require-dev": {
"symfony/var-dumper": "^4.2",

60
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6ebd3a4578a0960080346336d4970bfd",
"content-hash": "6228bf65a800388ad9f6b06b8ac6ba72",
"packages": [
{
"name": "league/flysystem",
@ -907,6 +907,64 @@
],
"description": "thinkphp template driver",
"time": "2019-11-06T11:40:13+00:00"
},
{
"name": "zzstudio/think-addons",
"version": "2.0.5",
"source": {
"type": "git",
"url": "https://github.com/zz-studio/think-addons.git",
"reference": "7eb740cb219a111d593a05ad88248a74f640fe5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zz-studio/think-addons/zipball/7eb740cb219a111d593a05ad88248a74f640fe5c",
"reference": "7eb740cb219a111d593a05ad88248a74f640fe5c",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=7.1.0",
"topthink/framework": "^6.0",
"topthink/think-helper": "^3.0.0",
"topthink/think-view": "^1.0"
},
"type": "library",
"extra": {
"think": {
"services": [
"think\\addons\\Service"
],
"config": {
"addons": "src/config.php"
}
}
},
"autoload": {
"psr-4": {
"think\\": "src/"
},
"files": [
"src/helper.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "byron",
"email": "xiaobo.sun@qq.com"
}
],
"description": "The ThinkPHP6 Addons Package",
"time": "2020-01-06T06:42:39+00:00"
}
],
"packages-dev": [

17
config/addons.php Normal file
View File

@ -0,0 +1,17 @@
<?php
// +----------------------------------------------------------------------
// | thinkphp5 Addons [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016 http://www.zzstudio.net All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Byron Sampson <xiaobo.sun@qq.com>
// +----------------------------------------------------------------------
return [
'autoload' => true,
'hooks' => [],
'route' => [],
'service' => [],
];

View File

@ -12,4 +12,5 @@ return array(
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php',
'39594db8502267d6df2fe2dca5f3914d' => $vendorDir . '/zzstudio/think-addons/src/helper.php',
);

View File

@ -10,7 +10,7 @@ return array(
'think\\trace\\' => array($vendorDir . '/topthink/think-trace/src'),
'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'),
'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'),
'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-template/src'),
'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-template/src', $vendorDir . '/zzstudio/think-addons/src'),
'taoser\\think\\' => array($vendorDir . '/taoser/think-auth/src'),
'app\\' => array($baseDir . '/app'),
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),

View File

@ -13,6 +13,7 @@ class ComposerStaticInit9b80d9a7bd440d07cac42880e0942921
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php',
'39594db8502267d6df2fe2dca5f3914d' => __DIR__ . '/..' . '/zzstudio/think-addons/src/helper.php',
);
public static $prefixLengthsPsr4 = array (
@ -77,6 +78,7 @@ class ComposerStaticInit9b80d9a7bd440d07cac42880e0942921
1 => __DIR__ . '/..' . '/topthink/think-helper/src',
2 => __DIR__ . '/..' . '/topthink/think-orm/src',
3 => __DIR__ . '/..' . '/topthink/think-template/src',
4 => __DIR__ . '/..' . '/zzstudio/think-addons/src',
),
'taoser\\think\\' =>
array (

View File

@ -1201,5 +1201,65 @@
}
],
"description": "thinkphp template driver"
},
{
"name": "zzstudio/think-addons",
"version": "2.0.5",
"version_normalized": "2.0.5.0",
"source": {
"type": "git",
"url": "https://github.com/zz-studio/think-addons.git",
"reference": "7eb740cb219a111d593a05ad88248a74f640fe5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zz-studio/think-addons/zipball/7eb740cb219a111d593a05ad88248a74f640fe5c",
"reference": "7eb740cb219a111d593a05ad88248a74f640fe5c",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=7.1.0",
"topthink/framework": "^6.0",
"topthink/think-helper": "^3.0.0",
"topthink/think-view": "^1.0"
},
"time": "2020-01-06T06:42:39+00:00",
"type": "library",
"extra": {
"think": {
"services": [
"think\\addons\\Service"
],
"config": {
"addons": "src/config.php"
}
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"think\\": "src/"
},
"files": [
"src/helper.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "byron",
"email": "xiaobo.sun@qq.com"
}
],
"description": "The ThinkPHP6 Addons Package"
}
]

3
vendor/services.php vendored
View File

@ -1,8 +1,9 @@
<?php
// This file is automatically generated at:2020-02-12 16:42:46
// This file is automatically generated at:2020-02-18 19:24:39
declare (strict_types = 1);
return array (
0 => 'think\\captcha\\CaptchaService',
1 => 'think\\app\\Service',
2 => 'think\\trace\\Service',
3 => 'think\\addons\\Service',
);

274
vendor/zzstudio/think-addons/README.md vendored Normal file
View File

@ -0,0 +1,274 @@
# think-addons
The ThinkPHP 6 Addons Package
## 安装
> composer require zzstudio/think-addons
## 配置
### 生成配置
系统安装后会自动在 config 目录中生成 addons.php 的配置文件,
如果系统未生成可在命令行执行
```php
php think addons:config
```
快速生成配置文件
### 公共配置
```php
'addons' => [
// 是否自动读取取插件钩子配置信息(默认是开启)
'autoload' => true,
// 当关闭自动获取配置时需要手动配置hooks信息
'hooks' => [
// 可以定义多个钩子
'testhook'=>'test' // 键为钩子名称,用于在业务中自定义钩子处理,值为实现该钩子的插件,
// 多个插件可以用数组也可以用逗号分割
],
'route' => [],
'service' => [],
];
```
或者在\config目录中新建`addons.php`,内容为:
```php
<?php
return [
// 是否自动读取取插件钩子配置信息
'autoload' => false,
// 当关闭自动获取配置时需要手动配置hooks信息
'hooks' => [
// 可以定义多个钩子
'testhook'=>'test' // 键为钩子名称,用于在业务中自定义钩子处理,值为实现该钩子的插件,
// 多个插件可以用数组也可以用逗号分割
],
'route' => [],
'service' => [],
];
```
## 创建插件
> 创建的插件可以在view视图中使用也可以在php业务中使用
安装完成后访问系统时会在项目根目录生成名为`addons`的目录,在该目录中创建需要的插件。
下面写一个例子:
### 创建test插件
> 在addons目录中创建test目录
### 创建钩子实现类
> 在test目录中创建 Plugin.php 类文件。注意:类文件首字母需大写
```php
<?php
namespace addons\test; // 注意命名空间规范
use think\Addons;
/**
* 插件测试
* @author byron sampson
*/
class Plugin extends Addons // 需继承think\Addons类
{
// 该插件的基础信息
public $info = [
'name' => 'test', // 插件标识
'title' => '插件测试', // 插件名称
'description' => 'thinkph6插件测试', // 插件简介
'status' => 0, // 状态
'author' => 'byron sampson',
'version' => '0.1'
];
/**
* 插件安装方法
* @return bool
*/
public function install()
{
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
return true;
}
/**
* 实现的testhook钩子方法
* @return mixed
*/
public function testhook($param)
{
// 调用钩子时候的参数信息
print_r($param);
// 当前插件的配置信息配置信息存在当前目录的config.php文件中见下方
print_r($this->getConfig());
// 可以返回模板,模板文件默认读取的为插件目录中的文件。模板名不能为空!
return $this->fetch('info');
}
}
```
### 创建插件配置文件
> 在test目录中创建config.php类文件插件配置文件可以省略。
```php
<?php
return [
'display' => [
'title' => '是否显示:',
'type' => 'radio',
'options' => [
'1' => '显示',
'0' => '不显示'
],
'value' => '1'
]
];
```
### 创建钩子模板文件
> 在test->view目录中创建info.html模板文件钩子在使用fetch方法时对应的模板文件。
```html
<h1>hello tpl</h1>
如果插件中需要有链接或提交数据的业务可以在插件中创建controller业务文件
要访问插件中的controller时使用addon_url生成url链接。
如下:
<a href="{:addons_url('Action/link')}">link test</a>
<a href="{:addons_url('test://Action/link')}">link test</a>
格式为:
test为插件名Action为controller中的类名[多级控制器可以用.分割]link为controller中的方法
```
### 创建插件的controller文件
> 在test目录中创建controller目录在controller目录中创建Index.php文件
> controller类的用法与tp6中的controller一致
```php
<?php
namespace addons\test\controller;
class Index
{
public function link()
{
echo 'hello link';
}
}
```
## 使用钩子
> 创建好插件后就可以在正常业务中使用该插件中的钩子了
> 使用钩子的时候第二个参数可以省略
### 模板中使用钩子
```html
<div>{:hook('testhook', ['id'=>1])}</div>
```
### php业务中使用
> 只要是thinkphp6正常流程中的任意位置均可以使用
```php
hook('testhook', ['id'=>1])
```
### 插件公共方法
```php
/**
* 处理插件钩子
* @param string $event 钩子名称
* @param array|null $params 传入参数
* @param bool $once 是否只返回一个结果
* @return mixed
*/
function hook($event, $params = null, bool $once = false);
/**
* 读取插件的基础信息
* @param string $name 插件名
* @return array
*/
function get_addons_info($name);
/**
* 获取插件Plugin的单例
* @param string $name 插件名
* @return mixed|null
*/
function get_addons_instance($name);
/**
* 插件显示内容里生成访问插件的url
* @param $url 在插件控制器中可忽略插件名,在非插件中生成时需指定插件名。例:插件名://控制器/方法
* @param array $param
* @param bool|string $suffix 生成的URL后缀
* @param bool|string $domain 域名
* @return bool|string
*/
function addons_url($url = '', $param = [], $suffix = true, $domain = false);
```
## 插件目录结构
### 最终生成的目录结构为
```html
www WEB部署目录或者子目录
├─addons 插件目录
├─app 应用目录
│ ├─controller 控制器目录
│ ├─model 模型目录
│ ├─ ... 更多类库目录
│ │
│ ├─common.php 公共函数文件
│ └─event.php 事件定义文件
├─config 配置目录
│ ├─app.php 应用配置
│ ├─cache.php 缓存配置
│ ├─console.php 控制台配置
│ ├─cookie.php Cookie配置
│ ├─database.php 数据库配置
│ ├─filesystem.php 文件磁盘配置
│ ├─lang.php 多语言配置
│ ├─log.php 日志配置
│ ├─middleware.php 中间件配置
│ ├─route.php URL和路由配置
│ ├─session.php Session配置
│ ├─trace.php Trace配置
│ └─view.php 视图配置
├─view 视图目录
├─route 路由定义目录
│ ├─route.php 路由定义文件
│ └─ ...
├─public WEB目录对外访问目录
│ ├─index.php 入口文件
│ ├─router.php 快速测试文件
│ └─.htaccess 用于apache的重写
├─extend 扩展类库目录
├─runtime 应用的运行时目录(可写,可定制)
├─vendor Composer类库目录
├─.example.env 环境变量示例文件
├─composer.json composer 定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行入口文件
```

View File

@ -0,0 +1,40 @@
{
"name": "zz-studio/think-addons",
"description": "The ThinkPHP6 Addons Package",
"license": "Apache-2.0",
"authors": [
{
"name": "byron",
"email": "xiaobo.sun@qq.com"
}
],
"require": {
"php": ">=7.1.0",
"topthink/framework": "^6.0",
"topthink/think-view": "^1.0",
"topthink/think-helper": "^3.0.0"
},
"autoload": {
"psr-4": {
"think\\": "src/"
},
"files": [
"src/helper.php"
]
},
"extra": {
"think": {
"services": [
"think\\addons\\Service"
],
"config":{
"addons": "src/config.php"
}
}
},
"scripts" : {
"post-install-cmd": [
"php think addons:config"
]
}
}

View File

@ -0,0 +1,194 @@
<?php
/**
* +----------------------------------------------------------------------
* | think-addons [thinkphp6]
* +----------------------------------------------------------------------
* .--, .--, | FILE: Addons.php
* ( ( \.---./ ) ) | AUTHOR: byron
* '.__/o o\__.' | EMAIL: xiaobo.sun@qq.com
* {= ^ =} | QQ: 150093589
* / \ | DATETIME: 2019/11/5 14:47
* // \\ |
* //| . |\\ |
* "'\ /'"_.-~^`'-. |
* \ _ /--' ` |
* ___)( )(___ |-----------------------------------------
* (((__) (__))) | 高山仰止,景行行止.虽不能至,心向往之。
* +----------------------------------------------------------------------
* | Copyright (c) 2019 http://www.zzstudio.net All rights reserved.
* +----------------------------------------------------------------------
*/
declare(strict_types=1);
namespace think;
use think\App;
use think\helper\Str;
use think\facade\Config;
use think\facade\View;
abstract class Addons
{
// app 容器
protected $app;
// 请求对象
protected $request;
// 当前插件标识
protected $name;
// 插件路径
protected $addon_path;
// 视图模型
protected $view;
// 插件配置
protected $addon_config;
// 插件信息
protected $addon_info;
/**
* 插件构造函数
* Addons constructor.
* @param \think\App $app
*/
public function __construct(App $app)
{
$this->app = $app;
$this->request = $app->request;
$this->name = $this->getName();
$this->addon_path = $app->addons->getAddonsPath() . $this->name . DIRECTORY_SEPARATOR;
$this->addon_config = "addon_{$this->name}_config";
$this->addon_info = "addon_{$this->name}_info";
$this->view = clone View::engine('Think');
$this->view->config([
'view_path' => $this->addon_path . 'view' . DIRECTORY_SEPARATOR
]);
// 控制器初始化
$this->initialize();
}
// 初始化
protected function initialize()
{}
/**
* 获取插件标识
* @return mixed|null
*/
final protected function getName()
{
$class = get_class($this);
list(, $name, ) = explode('\\', $class);
$this->request->addon = $name;
return $name;
}
/**
* 加载模板输出
* @param string $template
* @param array $vars 模板文件名
* @return false|mixed|string 模板输出变量
* @throws \think\Exception
*/
protected function fetch($template = '', $vars = [])
{
return $this->view->fetch($template, $vars);
}
/**
* 渲染内容输出
* @access protected
* @param string $content 模板内容
* @param array $vars 模板输出变量
* @return mixed
*/
protected function display($content = '', $vars = [])
{
return $this->view->display($content, $vars);
}
/**
* 模板变量赋值
* @access protected
* @param mixed $name 要显示的模板变量
* @param mixed $value 变量的值
* @return $this
*/
protected function assign($name, $value = '')
{
$this->view->assign([$name => $value]);
return $this;
}
/**
* 初始化模板引擎
* @access protected
* @param array|string $engine 引擎参数
* @return $this
*/
protected function engine($engine)
{
$this->view->engine($engine);
return $this;
}
/**
* 插件基础信息
* @return array
*/
final public function getInfo()
{
$info = Config::get($this->addon_info, []);
if ($info) {
return $info;
}
// 文件属性
$info = $this->info ?? [];
// 文件配置
$info_file = $this->addon_path . 'info.ini';
if (is_file($info_file)) {
$_info = parse_ini_file($info_file, true, INI_SCANNER_TYPED) ?: [];
$_info['url'] = addons_url();
$info = array_merge($_info, $info);
}
Config::set($info, $this->addon_info);
return isset($info) ? $info : [];
}
/**
* 获取配置信息
* @param bool $type 是否获取完整配置
* @return array|mixed
*/
final public function getConfig($type = false)
{
$config = Config::get($this->addon_config, []);
if ($config) {
return $config;
}
$config_file = $this->addon_path . 'config.php';
if (is_file($config_file)) {
$temp_arr = (array)include $config_file;
if ($type) {
return $temp_arr;
}
foreach ($temp_arr as $key => $value) {
$config[$key] = $value['value'];
}
unset($temp_arr);
}
Config::set($config, $this->addon_config);
return $config;
}
//必须实现安装
abstract public function install();
//必须卸载插件方法
abstract public function uninstall();
}

View File

@ -0,0 +1,93 @@
<?php
/**
* +----------------------------------------------------------------------
* | think-addons [thinkphp6]
* +----------------------------------------------------------------------
* .--, .--, | FILE: Route.php
* ( ( \.---./ ) ) | AUTHOR: byron
* '.__/o o\__.' | EMAIL: xiaobo.sun@qq.com
* {= ^ =} | QQ: 150093589
* / \ | DATETIME: 2019/11/5 09:57
* // \\ |
* //| . |\\ |
* "'\ /'"_.-~^`'-. |
* \ _ /--' ` |
* ___)( )(___ |-----------------------------------------
* (((__) (__))) | 高山仰止,景行行止.虽不能至,心向往之。
* +----------------------------------------------------------------------
* | Copyright (c) 2019 http://www.zzstudio.net All rights reserved.
* +----------------------------------------------------------------------
*/
declare(strict_types=1);
namespace think\addons;
use think\helper\Str;
use think\facade\Event;
use think\facade\Config;
use think\exception\HttpException;
class Route
{
/**
* 插件路由请求
* @param null $addon
* @param null $controller
* @param null $action
* @return mixed
*/
public static function execute($addon = null, $controller = null, $action = null)
{
$app = app();
$request = $app->request;
Event::trigger('addons_begin', $request);
if (empty($addon) || empty($controller) || empty($action)) {
throw new HttpException(500, lang('addon can not be empty'));
}
$request->addon = $addon;
// 设置当前请求的控制器、操作
$request->setController($controller)->setAction($action);
// 获取插件基础信息
$info = get_addons_info($addon);
if (!$info) {
throw new HttpException(404, lang('addon %s not found', [$addon]));
}
if (!$info['status']) {
throw new HttpException(500, lang('addon %s is disabled', [$addon]));
}
// 监听addon_module_init
Event::trigger('addon_module_init', $request);
$class = get_addons_class($addon, 'controller', $controller);
if (!$class) {
throw new HttpException(404, lang('addon controller %s not found', [Str::studly($controller)]));
}
// 重写视图基础路径
$config = Config::get('view');
$config['view_path'] = $app->addons->getAddonsPath() . $addon . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR;
Config::set($config, 'view');
// 生成控制器对象
$instance = new $class($app);
$vars = [];
if (is_callable([$instance, $action])) {
// 执行操作方法
$call = [$instance, $action];
} elseif (is_callable([$instance, '_empty'])) {
// 空操作
$call = [$instance, '_empty'];
$vars = [$action];
} else {
// 操作不存在
throw new HttpException(404, lang('addon action %s not found', [get_class($instance).'->'.$action.'()']));
}
Event::trigger('addons_action_begin', $call);
return call_user_func_array($call, $vars);
}
}

View File

@ -0,0 +1,232 @@
<?php
declare(strict_types=1);
namespace think\addons;
use think\Route;
use think\helper\Str;
use think\facade\Config;
use think\facade\Lang;
use think\facade\Cache;
use think\facade\Event;
use think\addons\middleware\Addons;
/**
* 插件服务
* Class Service
* @package think\addons
*/
class Service extends \think\Service
{
protected $addons_path;
public function register()
{
$this->addons_path = $this->getAddonsPath();
// 加载系统语言包
Lang::load([
$this->app->getRootPath() . '/vendor/zzstudio/think-addons/src/lang/zh-cn.php'
]);
// 自动载入插件
$this->autoload();
// 加载插件事件
$this->loadEvent();
// 加载插件系统服务
$this->loadService();
// 绑定插件容器
$this->app->bind('addons', Service::class);
}
public function boot()
{
$this->registerRoutes(function (Route $route) {
// 路由脚本
$execute = '\\think\\addons\\Route::execute';
// 注册插件公共中间件
if (is_file($this->app->addons->getAddonsPath() . 'middleware.php')) {
$this->app->middleware->import(include $this->app->addons->getAddonsPath() . 'middleware.php', 'route');
}
// 注册控制器路由
$route->rule("addons/:addon/[:controller]/[:action]", $execute)->middleware(Addons::class);
// 自定义路由
$routes = (array) Config::get('addons.route', []);
foreach ($routes as $key => $val) {
if (!$val) {
continue;
}
if (is_array($val)) {
$domain = $val['domain'];
$rules = [];
foreach ($val['rule'] as $k => $rule) {
[$addon, $controller, $action] = explode('/', $rule);
$rules[$k] = [
'addons' => $addon,
'controller' => $controller,
'action' => $action,
'indomain' => 1,
];
}
$route->domain($domain, function () use ($rules, $route, $execute) {
// 动态注册域名的路由规则
foreach ($rules as $k => $rule) {
$route->rule($k, $execute)
->name($k)
->completeMatch(true)
->append($rule);
}
});
} else {
list($addon, $controller, $action) = explode('/', $val);
$route->rule($key, $execute)
->name($key)
->completeMatch(true)
->append([
'addons' => $addon,
'controller' => $controller,
'action' => $action
]);
}
}
});
}
/**
* 插件事件
*/
private function loadEvent()
{
$hooks = $this->app->isDebug() ? [] : Cache::get('hooks', []);
if (empty($hooks)) {
$hooks = (array) Config::get('addons.hooks', []);
// 初始化钩子
foreach ($hooks as $key => $values) {
if (is_string($values)) {
$values = explode(',', $values);
} else {
$values = (array) $values;
}
$hooks[$key] = array_filter(array_map(function ($v) use ($key) {
return [get_addons_class($v), $key];
}, $values));
}
Cache::set('hooks', $hooks);
}
//如果在插件中有定义 AddonsInit则直接执行
if (isset($hooks['AddonsInit'])) {
foreach ($hooks['AddonsInit'] as $k => $v) {
Event::trigger('AddonsInit', $v);
}
}
Event::listenEvents($hooks);
}
/**
* 挂载插件服务
*/
private function loadService()
{
$results = scandir($this->addons_path);
$bind = [];
foreach ($results as $name) {
if ($name === '.' or $name === '..') {
continue;
}
if (is_file($this->addons_path . $name)) {
continue;
}
$addonDir = $this->addons_path . $name . DIRECTORY_SEPARATOR;
if (!is_dir($addonDir)) {
continue;
}
if (!is_file($addonDir . ucfirst($name) . '.php')) {
continue;
}
$service_file = $addonDir . 'service.ini';
if (!is_file($service_file)) {
continue;
}
$info = parse_ini_file($service_file, true, INI_SCANNER_TYPED) ?: [];
$bind = array_merge($bind, $info);
}
$this->app->bind($bind);
}
/**
* 自动载入插件
* @return bool
*/
private function autoload()
{
// 是否处理自动载入
if (!Config::get('addons.autoload', true)) {
return true;
}
$config = Config::get('addons');
// 读取插件目录及钩子列表
$base = get_class_methods("\\think\\Addons");
// 读取插件目录中的php文件
foreach (glob($this->getAddonsPath() . '*/*.php') as $addons_file) {
// 格式化路径信息
$info = pathinfo($addons_file);
// 获取插件目录名
$name = pathinfo($info['dirname'], PATHINFO_FILENAME);
// 找到插件入口文件
if (strtolower($info['filename']) === 'plugin') {
// 读取出所有公共方法
$methods = (array)get_class_methods("\\addons\\" . $name . "\\" . $info['filename']);
// 跟插件基类方法做比对,得到差异结果
$hooks = array_diff($methods, $base);
// 循环将钩子方法写入配置中
foreach ($hooks as $hook) {
if (!isset($config['hooks'][$hook])) {
$config['hooks'][$hook] = [];
}
// 兼容手动配置项
if (is_string($config['hooks'][$hook])) {
$config['hooks'][$hook] = explode(',', $config['hooks'][$hook]);
}
if (!in_array($name, $config['hooks'][$hook])) {
$config['hooks'][$hook][] = $name;
}
}
}
}
Config::set($config, 'addons');
}
/**
* 获取 addons 路径
* @return string
*/
public function getAddonsPath()
{
// 初始化插件目录
$addons_path = $this->app->getRootPath() . 'addons' . DIRECTORY_SEPARATOR;
// 如果插件目录不存在则创建
if (!is_dir($addons_path)) {
@mkdir($addons_path, 0755, true);
}
return $addons_path;
}
/**
* 获取插件的配置信息
* @param string $name
* @return array
*/
public function getAddonsConfig()
{
$name = $this->app->request->addon;
$addon = get_addons_instance($name);
if (!$addon) {
return [];
}
return $addon->getConfig();
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* +----------------------------------------------------------------------
* | think-addons [基于 thinkphp6]
* +----------------------------------------------------------------------
* .--, .--, | FILE: config.php
* ( ( \.---./ ) ) | AUTHOR: byron sampson
* '.__/o o\__.' | EMAIL: xiaobo.sun@qq.com
* {= ^ =} | QQ: 150093589
* > - < | WECHAT: wx5ini99
* / \ | DATETIME: 2019/10/29
* // \\ |
* //| . |\\ |
* "'\ /'"_.-~^`'-. |
* \ _ /--' ` |
* ___)( )(___ |-----------------------------------------
* (((__) (__))) | 高山仰止,景行行止.虽不能至,心向往之。
* +----------------------------------------------------------------------
* | Copyright (c) 2019 http://www.zzstudio.net All rights reserved.
* +----------------------------------------------------------------------
*/
namespace think\addons\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\facade\Env;
class SendConfig extends Command
{
public function configure()
{
$this->setName('addons:config')
->setDescription('send config to config folder');
}
public function execute(Input $input, Output $output)
{
//获取默认配置文件
$content = file_get_contents(root_path() . 'vendor/zzstudio/think-addons/src/config.php');
$configPath = config_path() . '/';
$configFile = $configPath . 'addons.php';
//判断目录是否存在
if (!file_exists($configPath)) {
mkdir($configPath, 0755, true);
}
//判断文件是否存在
if (is_file($configFile)) {
throw new \InvalidArgumentException(sprintf('The config file "%s" already exists', $configFile));
}
if (false === file_put_contents($configFile, $content)) {
throw new \RuntimeException(sprintf('The config file "%s" could not be written to "%s"', $configFile,$configPath));
}
$output->writeln('create addons config ok');
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* +----------------------------------------------------------------------
* | think-addons [thinkphp6]
* +----------------------------------------------------------------------
* .--, .--, | FILE: Addons.php
* ( ( \.---./ ) ) | AUTHOR: byron
* '.__/o o\__.' | EMAIL: xiaobo.sun@qq.com
* {= ^ =} | QQ: 150093589
* / \ | DATETIME: 2019/11/5 09:55
* // \\ |
* //| . |\\ |
* "'\ /'"_.-~^`'-. |
* \ _ /--' ` |
* ___)( )(___ |-----------------------------------------
* (((__) (__))) | 高山仰止,景行行止.虽不能至,心向往之。
* +----------------------------------------------------------------------
* | Copyright (c) 2019 http://www.zzstudio.net All rights reserved.
* +----------------------------------------------------------------------
*/
declare(strict_types=1);
namespace think\addons\middleware;
use think\App;
class Addons
{
protected $app;
public function __construct(App $app)
{
$this->app = $app;
}
/**
* 插件中间件
* @param $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, \Closure $next)
{
hook('addon_middleware', $request);
return $next($request);
}
}

View File

@ -0,0 +1,17 @@
<?php
// +----------------------------------------------------------------------
// | thinkphp5 Addons [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016 http://www.zzstudio.net All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Byron Sampson <xiaobo.sun@qq.com>
// +----------------------------------------------------------------------
return [
'autoload' => true,
'hooks' => [],
'route' => [],
'service' => [],
];

View File

@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
use think\facade\Event;
use think\facade\Route;
use think\helper\{
Str, Arr
};
\think\Console::starting(function (\think\Console $console) {
$console->addCommands([
'addons:config' => '\\think\\addons\\command\\SendConfig'
]);
});
// 插件类库自动载入
spl_autoload_register(function ($class) {
$class = ltrim($class, '\\');
$dir = app()->getRootPath();
$namespace = 'addons';
if (strpos($class, $namespace) === 0) {
$class = substr($class, strlen($namespace));
$path = '';
if (($pos = strripos($class, '\\')) !== false) {
$path = str_replace('\\', '/', substr($class, 0, $pos)) . '/';
$class = substr($class, $pos + 1);
}
$path .= str_replace('_', '/', $class) . '.php';
$dir .= $namespace . $path;
if (file_exists($dir)) {
include $dir;
return true;
}
return false;
}
return false;
});
if (!function_exists('hook')) {
/**
* 处理插件钩子
* @param string $event 钩子名称
* @param array|null $params 传入参数
* @param bool $once 是否只返回一个结果
* @return mixed
*/
function hook($event, $params = null, bool $once = false)
{
$result = Event::trigger($event, $params, $once);
return join('', $result);
}
}
if (!function_exists('get_addons_info')) {
/**
* 读取插件的基础信息
* @param string $name 插件名
* @return array
*/
function get_addons_info($name)
{
$addon = get_addons_instance($name);
if (!$addon) {
return [];
}
return $addon->getInfo();
}
}
if (!function_exists('get_addons_instance')) {
/**
* 获取插件的单例
* @param string $name 插件名
* @return mixed|null
*/
function get_addons_instance($name)
{
static $_addons = [];
if (isset($_addons[$name])) {
return $_addons[$name];
}
$class = get_addons_class($name);
if (class_exists($class)) {
$_addons[$name] = new $class(app());
return $_addons[$name];
} else {
return null;
}
}
}
if (!function_exists('get_addons_class')) {
/**
* 获取插件类的类名
* @param string $name 插件名
* @param string $type 返回命名空间类型
* @param string $class 当前类名
* @return string
*/
function get_addons_class($name, $type = 'hook', $class = null)
{
$name = trim($name);
// 处理多级控制器情况
if (!is_null($class) && strpos($class, '.')) {
$class = explode('.', $class);
$class[count($class) - 1] = Str::studly(end($class));
$class = implode('\\', $class);
} else {
$class = Str::studly(is_null($class) ? $name : $class);
}
switch ($type) {
case 'controller':
$namespace = '\\addons\\' . $name . '\\controller\\' . $class;
break;
default:
$namespace = '\\addons\\' . $name . '\\Plugin';
}
return class_exists($namespace) ? $namespace : '';
}
}
if (!function_exists('addons_url')) {
/**
* 插件显示内容里生成访问插件的url
* @param $url
* @param array $param
* @param bool|string $suffix 生成的URL后缀
* @param bool|string $domain 域名
* @return bool|string
*/
function addons_url($url = '', $param = [], $suffix = true, $domain = false)
{
$request = app('request');
if (empty($url)) {
// 生成 url 模板变量
$addons = $request->addon;
$controller = $request->controller();
$controller = str_replace('/', '.', $controller);
$action = $request->action();
} else {
$url = Str::studly($url);
$url = parse_url($url);
if (isset($url['scheme'])) {
$addons = strtolower($url['scheme']);
$controller = $url['host'];
$action = trim($url['path'], '/');
} else {
$route = explode('/', $url['path']);
$addons = $request->addon;
$action = array_pop($route);
$controller = array_pop($route) ?: $request->controller();
}
$controller = Str::snake((string)$controller);
/* 解析URL带的参数 */
if (isset($url['query'])) {
parse_str($url['query'], $query);
$param = array_merge($query, $param);
}
}
return Route::buildUrl("@addons/{$addons}/{$controller}/{$action}", $param)->suffix($suffix)->domain($domain);
}
}

View File

@ -0,0 +1,97 @@
<?php
// 插件中文语言包
return [
'addon %s not found' => '插件 %s 未找到',
'addon %s is disabled' => '插件 %s 已禁用',
'addon controller %s not found' => '插件控制器 %s 未找到',
'addon action %s not found' => '插件控制器方法 %s 未找到',
'addon can not be empty' => '插件不能为空',
'Keep login' => '保持会话',
'Forgot password' => '忘记密码?',
'Username' => '用户名',
'User id' => '会员ID',
'Nickname' => '昵称',
'Password' => '密码',
'Sign up' => '注 册',
'Sign in' => '登 录',
'Sign out' => '注 销',
'Guest' => '游客',
'Welcome' => '%s你好',
'Add' => '添加',
'Edit' => '编辑',
'Delete' => '删除',
'Move' => '移动',
'Name' => '名称',
'Status' => '状态',
'Weigh' => '权重',
'Operate' => '操作',
'Warning' => '温馨提示',
'Default' => '默认',
'Article' => '文章',
'Page' => '单页',
'OK' => '确定',
'Cancel' => '取消',
'Loading' => '加载中',
'More' => '更多',
'Normal' => '正常',
'Hidden' => '隐藏',
'Submit' => '提交',
'Reset' => '重置',
'Execute' => '执行',
'Close' => '关闭',
'Search' => '搜索',
'Refresh' => '刷新',
'First' => '首页',
'Previous' => '上一页',
'Next' => '下一页',
'Last' => '末页',
'None' => '无',
'Online' => '在线',
'Logout' => '注销',
'Profile' => '个人资料',
'Index' => '首页',
'Hot' => '热门',
'Recommend' => '推荐',
'Dashboard' => '控制台',
'Code' => '编号',
'Message' => '内容',
'Line' => '行号',
'File' => '文件',
'Menu' => '菜单',
'Type' => '类型',
'Title' => '标题',
'Content' => '内容',
'Append' => '追加',
'Memo' => '备注',
'Parent' => '父级',
'Params' => '参数',
'Permission' => '权限',
'Begin time' => '开始时间',
'End time' => '结束时间',
'Create time' => '创建时间',
'Flag' => '标志',
'Home' => '首页',
'Store' => '插件市场',
'Services' => '服务',
'Download' => '下载',
'Demo' => '演示',
'Donation' => '捐赠',
'Forum' => '社区',
'Docs' => '文档',
'Go back' => '返回首页',
'Jump now' => '立即跳转',
'Please login first' => '请登录后再操作',
'Send verification code' => '发送验证码',
'Redirect now' => '立即跳转',
'Operation completed' => '操作成功!',
'Operation failed' => '操作失败!',
'Unknown data format' => '未知的数据格式!',
'Network error' => '网络错误!',
'Advanced search' => '高级搜索',
'Invalid parameters' => '未知参数',
'No results were found' => '记录未找到',
'Parameter %s can not be empty' => '参数 %s 不能为空',
'You have no permission' => '你没有权限访问',
'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中',
'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转',
];