Plugin.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 海豚PHP框架 [ DolphinPHP ]
  4. // +----------------------------------------------------------------------
  5. // | 版权所有 2016~2017 河源市卓锐科技有限公司 [ http://www.zrthink.com ]
  6. // +----------------------------------------------------------------------
  7. // | 官方网站: http://dolphinphp.com
  8. // +----------------------------------------------------------------------
  9. // | 开源协议 ( http://www.apache.org/licenses/LICENSE-2.0 )
  10. // +----------------------------------------------------------------------
  11. namespace app\admin\controller;
  12. use app\common\builder\ZBuilder;
  13. use app\admin\model\Plugin as PluginModel;
  14. use app\admin\model\HookPlugin as HookPluginModel;
  15. use think\Cache;
  16. use util\Sql;
  17. use think\Db;
  18. use think\Hook;
  19. /**
  20. * 插件管理控制器
  21. * @package app\admin\controller
  22. */
  23. class Plugin extends Admin
  24. {
  25. /**
  26. * 首页
  27. * @param string $group 分组
  28. * @param string $type 显示类型
  29. * @author 蔡伟明 <314013107@qq.com>
  30. * @return mixed
  31. */
  32. public function index($group = 'local', $type = '')
  33. {
  34. // 配置分组信息
  35. $list_group = ['local' => '本地插件'];
  36. foreach ($list_group as $key => $value) {
  37. $tab_list[$key]['title'] = $value;
  38. $tab_list[$key]['url'] = url('index', ['group' => $key]);
  39. }
  40. // 监听tab钩子
  41. Hook::listen('plugin_index_tab_list', $tab_list);
  42. switch ($group) {
  43. case 'local':
  44. // 查询条件
  45. $keyword = $this->request->get('keyword', '');
  46. if (input('?param.status') && input('param.status') != '_all') {
  47. $status = input('param.status');
  48. } else {
  49. $status = '';
  50. }
  51. $PluginModel = new PluginModel;
  52. $result = $PluginModel->getAll($keyword, $status);
  53. if ($result['plugins'] === false) {
  54. $this->error($PluginModel->getError());
  55. }
  56. $type_show = Cache::get('plugin_type_show');
  57. $type_show = $type != '' ? $type : ($type_show == false ? 'block' : $type_show);
  58. Cache::set('plugin_type_show', $type_show);
  59. $type = $type_show == 'block' ? 'list' : 'block';
  60. $this->assign('page_title', '插件管理');
  61. $this->assign('plugins', $result['plugins']);
  62. $this->assign('total', $result['total']);
  63. $this->assign('tab_nav', ['tab_list' => $tab_list, 'curr_tab' => $group]);
  64. $this->assign('type', $type);
  65. return $this->fetch();
  66. break;
  67. case 'online':
  68. break;
  69. }
  70. }
  71. /**
  72. * 安装插件
  73. * @param string $name 插件标识
  74. * @author 蔡伟明 <314013107@qq.com>
  75. */
  76. public function install($name = '')
  77. {
  78. $plug_name = trim($name);
  79. if ($plug_name == '') $this->error('插件不存在!');
  80. $plugin_class = get_plugin_class($plug_name);
  81. if (!class_exists($plugin_class)) {
  82. $this->error('插件不存在!');
  83. }
  84. // 实例化插件
  85. $plugin = new $plugin_class;
  86. // 插件预安装
  87. if(!$plugin->install()) {
  88. $this->error('插件预安装失败!原因:'. $plugin->getError());
  89. }
  90. // 添加钩子
  91. if (isset($plugin->hooks) && !empty($plugin->hooks)) {
  92. if (!HookPluginModel::addHooks($plugin->hooks, $name)) {
  93. $this->error('安装插件钩子时出现错误,请重新安装');
  94. }
  95. cache('hook_plugins', null);
  96. }
  97. // 执行安装插件sql文件
  98. $sql_file = realpath(config('plugin_path').$name.'/install.sql');
  99. if (file_exists($sql_file)) {
  100. if (isset($plugin->database_prefix) && $plugin->database_prefix != '') {
  101. $sql_statement = Sql::getSqlFromFile($sql_file, false, [$plugin->database_prefix => config('database.prefix')]);
  102. } else {
  103. $sql_statement = Sql::getSqlFromFile($sql_file);
  104. }
  105. if (!empty($sql_statement)) {
  106. foreach ($sql_statement as $value) {
  107. Db::execute($value);
  108. }
  109. }
  110. }
  111. // 插件配置信息
  112. $plugin_info = $plugin->info;
  113. // 验证插件信息
  114. $result = $this->validate($plugin_info, 'Plugin');
  115. // 验证失败 输出错误信息
  116. if(true !== $result) $this->error($result);
  117. // 并入插件配置值
  118. $plugin_info['config'] = $plugin->getConfigValue();
  119. // 将插件信息写入数据库
  120. if (PluginModel::create($plugin_info)) {
  121. cache('plugin_all', null);
  122. $this->success('插件安装成功');
  123. } else {
  124. $this->error('插件安装失败');
  125. }
  126. }
  127. /**
  128. * 卸载插件
  129. * @param string $name 插件标识
  130. * @author 蔡伟明 <314013107@qq.com>
  131. */
  132. public function uninstall($name = '')
  133. {
  134. $plug_name = trim($name);
  135. if ($plug_name == '') $this->error('插件不存在!');
  136. $class = get_plugin_class($plug_name);
  137. if (!class_exists($class)) {
  138. $this->error('插件不存在!');
  139. }
  140. // 实例化插件
  141. $plugin = new $class;
  142. // 插件预卸
  143. if(!$plugin->uninstall()) {
  144. $this->error('插件预卸载失败!原因:'. $plugin->getError());
  145. }
  146. // 卸载插件自带钩子
  147. if (isset($plugin->hooks) && !empty($plugin->hooks)) {
  148. if (false === HookPluginModel::deleteHooks($plug_name)) {
  149. $this->error('卸载插件钩子时出现错误,请重新卸载');
  150. }
  151. cache('hook_plugins', null);
  152. }
  153. // 执行卸载插件sql文件
  154. $sql_file = realpath(config('plugin_path').$plug_name.'/uninstall.sql');
  155. if (file_exists($sql_file)) {
  156. if (isset($plugin->database_prefix) && $plugin->database_prefix != '') {
  157. $sql_statement = Sql::getSqlFromFile($sql_file, true, [$plugin->database_prefix => config('database.prefix')]);
  158. } else {
  159. $sql_statement = Sql::getSqlFromFile($sql_file, true);
  160. }
  161. if (!empty($sql_statement)) {
  162. Db::execute($sql_statement);
  163. }
  164. }
  165. // 删除插件信息
  166. if (PluginModel::where('name', $plug_name)->delete()) {
  167. cache('plugin_all', null);
  168. $this->success('插件卸载成功');
  169. } else {
  170. $this->error('插件卸载失败');
  171. }
  172. }
  173. /**
  174. * 插件管理
  175. * @param string $name 插件名
  176. * @author 蔡伟明 <314013107@qq.com>
  177. * @return mixed
  178. */
  179. public function manage($name = '')
  180. {
  181. cookie('__forward__', $_SERVER['REQUEST_URI']);
  182. // 加载自定义后台页面
  183. if (plugin_action_exists($name, 'Admin', 'index')) {
  184. return plugin_action($name, 'Admin', 'index');
  185. }
  186. // 加载系统的后台页面
  187. $class = get_plugin_class($name);
  188. if (!class_exists($class)) {
  189. $this->error($name.'插件不存在!');
  190. }
  191. // 实例化插件
  192. $plugin = new $class;
  193. // 获取后台字段信息,并分析
  194. if (isset($plugin->admin)) {
  195. $admin = $this->parseAdmin($plugin->admin);
  196. } else {
  197. $admin = $this->parseAdmin();
  198. }
  199. if (!plugin_model_exists($name)) {
  200. $this->error('插件: '.$name.' 缺少模型文件!');
  201. }
  202. // 获取插件模型实例
  203. $PluginModel = get_plugin_model($name);
  204. $order = $this->getOrder();
  205. $map = $this->getMap();
  206. $data_list = $PluginModel->where($map)->order($order)->paginate();
  207. $page = $data_list->render();
  208. // 使用ZBuilder快速创建数据表格
  209. $builder = ZBuilder::make('table')
  210. ->setPageTitle($admin['title']) // 设置页面标题
  211. ->setPluginName($name)
  212. ->setTableName($admin['table_name'])
  213. ->setSearch($admin['search_field'], $admin['search_title']) // 设置搜索框
  214. ->addOrder($admin['order'])
  215. ->addTopButton('back', [
  216. 'title' => '返回插件列表',
  217. 'icon' => 'fa fa-reply',
  218. 'href' => url('index')
  219. ])
  220. ->addTopButtons($admin['top_buttons']) // 批量添加顶部按钮
  221. ->addRightButtons($admin['right_buttons']); // 批量添加右侧按钮
  222. // 自定义顶部按钮
  223. if (!empty($admin['custom_top_buttons'])) {
  224. foreach ($admin['custom_top_buttons'] as $custom) {
  225. $builder->addTopButton('custom', $custom);
  226. }
  227. }
  228. // 自定义右侧按钮
  229. if (!empty($admin['custom_right_buttons'])) {
  230. foreach ($admin['custom_right_buttons'] as $custom) {
  231. $builder->addRightButton('custom', $custom);
  232. }
  233. }
  234. // 表头筛选
  235. if (is_array($admin['filter'])) {
  236. foreach ($admin['filter'] as $column => $params) {
  237. $options = isset($params[0]) ? $params[0] : [];
  238. $default = isset($params[1]) ? $params[1] : [];
  239. $type = isset($params[2]) ? $params[2] : 'checkbox';
  240. $builder->addFilter($column, $options, $default, $type);
  241. }
  242. } else {
  243. $builder->addFilter($admin['filter']);
  244. }
  245. return $builder
  246. ->addColumns($admin['columns']) // 批量添加数据列
  247. ->setRowList($data_list) // 设置表格数据
  248. ->setPages($page) // 设置分页数据
  249. ->fetch(); // 渲染模板
  250. }
  251. /**
  252. * 插件新增方法
  253. * @param string $plugin_name 插件名称
  254. * @author 蔡伟明 <314013107@qq.com>
  255. * @return mixed
  256. */
  257. public function add($plugin_name = '')
  258. {
  259. // 如果存在自定义的新增方法,则优先执行
  260. if (plugin_action_exists($plugin_name, 'Admin', 'add')) {
  261. $params = $this->request->param();
  262. return plugin_action($plugin_name, 'Admin', 'add', $params);
  263. }
  264. // 保存数据
  265. if ($this->request->isPost()) {
  266. $data = $this->request->post();
  267. // 执行插件的验证器(如果存在的话)
  268. if (plugin_validate_exists($plugin_name)) {
  269. $plugin_validate = get_plugin_validate($plugin_name);
  270. if (!$plugin_validate->check($data)) {
  271. // 验证失败 输出错误信息
  272. $this->error($plugin_validate->getError());
  273. }
  274. }
  275. // 实例化模型并添加数据
  276. $PluginModel = get_plugin_model($plugin_name);
  277. if ($PluginModel->data($data)->save()) {
  278. $this->success('新增成功', cookie('__forward__'));
  279. } else {
  280. $this->error('新增失败');
  281. }
  282. }
  283. // 获取插件模型
  284. $class = get_plugin_class($plugin_name);
  285. if (!class_exists($class)) {
  286. $this->error('插件不存在!');
  287. }
  288. // 实例化插件
  289. $plugin = new $class;
  290. if (!isset($plugin->fields)) {
  291. $this->error('插件新增、编辑字段不存在!');
  292. }
  293. // 使用ZBuilder快速创建表单
  294. return ZBuilder::make('form')
  295. ->setPageTitle('新增')
  296. ->addFormItems($plugin->fields)
  297. ->fetch();
  298. }
  299. /**
  300. * 编辑插件方法
  301. * @param string $id 数据id
  302. * @param string $plugin_name 插件名称
  303. * @author 蔡伟明 <314013107@qq.com>
  304. * @return mixed
  305. */
  306. public function edit($id = '', $plugin_name = '')
  307. {
  308. // 如果存在自定义的编辑方法,则优先执行
  309. if (plugin_action_exists($plugin_name, 'Admin', 'edit')) {
  310. $params = $this->request->param();
  311. return plugin_action($plugin_name, 'Admin', 'edit', $params);
  312. }
  313. // 保存数据
  314. if ($this->request->isPost()) {
  315. $data = $this->request->post();
  316. // 执行插件的验证器(如果存在的话)
  317. if (plugin_validate_exists($plugin_name)) {
  318. $plugin_validate = get_plugin_validate($plugin_name);
  319. if (!$plugin_validate->check($data)) {
  320. // 验证失败 输出错误信息
  321. $this->error($plugin_validate->getError());
  322. }
  323. }
  324. // 实例化模型并添加数据
  325. $PluginModel = get_plugin_model($plugin_name);
  326. if (false !== $PluginModel->isUpdate(true)->save($data)) {
  327. $this->success('编辑成功', cookie('__forward__'));
  328. } else {
  329. $this->error('编辑失败');
  330. }
  331. }
  332. // 获取插件类名
  333. $class = get_plugin_class($plugin_name);
  334. if (!class_exists($class)) {
  335. $this->error('插件不存在!');
  336. }
  337. // 实例化插件
  338. $plugin = new $class;
  339. if (!isset($plugin->fields)) {
  340. $this->error('插件新增、编辑字段不存在!');
  341. }
  342. // 获取数据
  343. $PluginModel = get_plugin_model($plugin_name);
  344. $info = $PluginModel->find($id);
  345. if (!$info) {
  346. $this->error('找不到数据!');
  347. }
  348. // 使用ZBuilder快速创建表单
  349. return ZBuilder::make('form')
  350. ->setPageTitle('编辑')
  351. ->addHidden('id')
  352. ->addFormItems($plugin->fields)
  353. ->setFormData($info)
  354. ->fetch();
  355. }
  356. /**
  357. * 插件参数设置
  358. * @param string $name 插件名称
  359. * @author 蔡伟明 <314013107@qq.com>
  360. */
  361. public function config($name = '')
  362. {
  363. // 更新配置
  364. if ($this->request->isPost()) {
  365. $data = $this->request->post();
  366. $data = json_encode($data);
  367. if (false !== PluginModel::where('name', $name)->update(['config' => $data])) {
  368. $this->success('更新成功', 'index');
  369. } else {
  370. $this->error('更新失败');
  371. }
  372. }
  373. $plugin_class = get_plugin_class($name);
  374. // 实例化插件
  375. $plugin = new $plugin_class;
  376. $trigger = isset($plugin->trigger) ? $plugin->trigger : [];
  377. // 插件配置值
  378. $info = PluginModel::where('name', $name)->field('id,name,config')->find();
  379. $db_config = json_decode($info['config'], true);
  380. // 插件配置项
  381. $config = include config('plugin_path'). $name. '/config.php';
  382. // 使用ZBuilder快速创建表单
  383. return ZBuilder::make('form')
  384. ->setPageTitle('插件设置')
  385. ->addFormItems($config)
  386. ->setFormData($db_config)
  387. ->setTrigger($trigger)
  388. ->fetch();
  389. }
  390. /**
  391. * 设置状态
  392. * @param string $type 状态类型:enable/disable
  393. * @param array $record 行为日志内容
  394. * @author 蔡伟明 <314013107@qq.com>
  395. * @return void
  396. */
  397. public function setStatus($type = '', $record = [])
  398. {
  399. $_t = input('param._t', '');
  400. $ids = $this->request->isPost() ? input('post.ids/a') : input('param.ids');
  401. empty($ids) && $this->error('缺少主键');
  402. $status = $type == 'enable' ? 1 : 0;
  403. if ($_t != '') {
  404. parent::setStatus($type, $record);
  405. } else {
  406. $plugins = PluginModel::where('id', 'in', $ids)->value('name');
  407. if ($plugins) {
  408. HookPluginModel::$type($plugins);
  409. }
  410. if (false !== PluginModel::where('id', 'in', $ids)->setField('status', $status)) {
  411. $this->success('操作成功');
  412. } else {
  413. $this->error('操作失败');
  414. }
  415. }
  416. }
  417. /**
  418. * 禁用插件/禁用插件数据
  419. * @param array $record 行为日志内容
  420. * @author 蔡伟明 <314013107@qq.com>
  421. * @return void
  422. */
  423. public function disable($record = [])
  424. {
  425. $this->setStatus('disable');
  426. }
  427. /**
  428. * 启用插件/启用插件数据
  429. * @param array $record 行为日志内容
  430. * @author 蔡伟明 <314013107@qq.com>
  431. * @return void
  432. */
  433. public function enable($record = [])
  434. {
  435. $this->setStatus('enable');
  436. }
  437. /**
  438. * 删除插件数据
  439. * @param array $record
  440. * @author 蔡伟明 <314013107@qq.com>
  441. * @return void
  442. */
  443. public function delete($record = [])
  444. {
  445. $this->setStatus('delete');
  446. }
  447. /**
  448. * 执行插件内部方法
  449. * @author 蔡伟明 <314013107@qq.com>
  450. * @return mixed
  451. */
  452. public function execute()
  453. {
  454. $plugin = input('param._plugin');
  455. $controller = input('param._controller');
  456. $action = input('param._action');
  457. $params = $this->request->except(['_plugin', '_controller', '_action'], 'param');
  458. if (empty($plugin) || empty($controller) || empty($action)) {
  459. $this->error('没有指定插件名称、控制器名称或操作名称');
  460. }
  461. if (!plugin_action_exists($plugin, $controller, $action)) {
  462. $this->error("找不到方法:{$plugin}/{$controller}/{$action}");
  463. }
  464. return plugin_action($plugin, $controller, $action, $params);
  465. }
  466. /**
  467. * 分析后台字段信息
  468. * @param array $data 字段信息
  469. * @author 蔡伟明 <314013107@qq.com>
  470. * @return array
  471. */
  472. private function parseAdmin($data = [])
  473. {
  474. $admin = [
  475. 'title' => '数据列表',
  476. 'search_title' => '',
  477. 'search_field' => [],
  478. 'order' => '',
  479. 'filter' => '',
  480. 'table_name' => '',
  481. 'columns' => [],
  482. 'right_buttons' => [],
  483. 'top_buttons' => [],
  484. 'customs' => [],
  485. ];
  486. if (empty($data)) {
  487. return $admin;
  488. }
  489. // 处理工具栏按钮链接
  490. if (isset($data['top_buttons']) && !empty($data['top_buttons'])) {
  491. $this->parseButton('top_buttons', $data);
  492. }
  493. // 处理右侧按钮链接
  494. if (isset($data['right_buttons']) && !empty($data['right_buttons'])) {
  495. $this->parseButton('right_buttons', $data);
  496. }
  497. return array_merge($admin, $data);
  498. }
  499. /**
  500. * 解析按钮链接
  501. * @param string $button 按钮名称
  502. * @param array $data 字段信息
  503. * @author 蔡伟明 <314013107@qq.com>
  504. * @return array
  505. */
  506. private function parseButton($button = '', &$data)
  507. {
  508. foreach ($data[$button] as $key => &$value) {
  509. // 处理自定义按钮
  510. if ($key === 'customs') {
  511. if (!empty($value)) {
  512. foreach ($value as &$custom) {
  513. if (isset($custom['href']['url']) && $custom['href']['url'] != '') {
  514. $params = isset($custom['href']['params']) ? $custom['href']['params'] : [];
  515. $custom['href'] = plugin_url($custom['href']['url'], $params);
  516. $data['custom_'.$button][] = $custom;
  517. }
  518. }
  519. }
  520. unset($data[$button][$key]);
  521. }
  522. if (!is_numeric($key) && isset($value['href']['url']) && $value['href']['url'] != '') {
  523. $value['href'] = plugin_url($value['href']['url']);
  524. }
  525. }
  526. }
  527. }