酒店预订平台
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

512 lignes
15 KiB

  1. <?php
  2. use Symfony\Component\VarExporter\VarExporter;
  3. use think\App;
  4. use think\Cache;
  5. use think\Config;
  6. use think\Exception;
  7. use think\Hook;
  8. use think\Loader;
  9. use think\Route;
  10. // 插件目录
  11. define('ADDON_PATH', ROOT_PATH . 'addons' . DS);
  12. // 定义路由
  13. Route::any('addons/:addon/[:controller]/[:action]', "\\think\\addons\\Route@execute");
  14. // 如果插件目录不存在则创建
  15. if (!is_dir(ADDON_PATH)) {
  16. @mkdir(ADDON_PATH, 0755, true);
  17. }
  18. // 注册类的根命名空间
  19. Loader::addNamespace('addons', ADDON_PATH);
  20. // 监听addon_init
  21. Hook::listen('addon_init');
  22. // 闭包自动识别插件目录配置
  23. Hook::add('app_init', function () {
  24. // 获取开关
  25. $autoload = (bool)Config::get('addons.autoload', false);
  26. // 非正是返回
  27. if (!$autoload) {
  28. return;
  29. }
  30. // 当debug时不缓存配置
  31. $config = App::$debug ? [] : Cache::get('addons', []);
  32. if (empty($config)) {
  33. $config = get_addon_autoload_config();
  34. Cache::set('addons', $config);
  35. }
  36. });
  37. // 闭包初始化行为
  38. Hook::add('app_init', function () {
  39. //注册路由
  40. $routeArr = (array)Config::get('addons.route');
  41. $domains = [];
  42. $rules = [];
  43. $execute = "\\think\\addons\\Route@execute?addon=%s&controller=%s&action=%s";
  44. foreach ($routeArr as $k => $v) {
  45. if (is_array($v)) {
  46. $addon = $v['addon'];
  47. $domain = $v['domain'];
  48. $drules = [];
  49. foreach ($v['rule'] as $m => $n) {
  50. list($addon, $controller, $action) = explode('/', $n);
  51. $drules[$m] = sprintf($execute . '&indomain=1', $addon, $controller, $action);
  52. }
  53. //$domains[$domain] = $drules ? $drules : "\\addons\\{$k}\\controller";
  54. $domains[$domain] = $drules ? $drules : [];
  55. $domains[$domain][':controller/[:action]'] = sprintf($execute . '&indomain=1', $addon, ":controller", ":action");
  56. } else {
  57. if (!$v) {
  58. continue;
  59. }
  60. list($addon, $controller, $action) = explode('/', $v);
  61. $rules[$k] = sprintf($execute, $addon, $controller, $action);
  62. }
  63. }
  64. Route::rule($rules);
  65. if ($domains) {
  66. Route::domain($domains);
  67. }
  68. // 获取系统配置
  69. $hooks = App::$debug ? [] : Cache::get('hooks', []);
  70. if (empty($hooks)) {
  71. $hooks = (array)Config::get('addons.hooks');
  72. // 初始化钩子
  73. foreach ($hooks as $key => $values) {
  74. if (is_string($values)) {
  75. $values = explode(',', $values);
  76. } else {
  77. $values = (array)$values;
  78. }
  79. $hooks[$key] = array_filter(array_map('get_addon_class', $values));
  80. }
  81. Cache::set('hooks', $hooks);
  82. }
  83. //如果在插件中有定义app_init,则直接执行
  84. if (isset($hooks['app_init'])) {
  85. foreach ($hooks['app_init'] as $k => $v) {
  86. Hook::exec($v, 'app_init');
  87. }
  88. }
  89. Hook::import($hooks, true);
  90. });
  91. /**
  92. * 处理插件钩子
  93. * @param string $hook 钩子名称
  94. * @param mixed $params 传入参数
  95. * @return void
  96. */
  97. function hook($hook, $params = [])
  98. {
  99. Hook::listen($hook, $params);
  100. }
  101. /**
  102. * 移除空目录
  103. * @param string $dir 目录
  104. */
  105. function remove_empty_folder($dir)
  106. {
  107. try {
  108. $isDirEmpty = !(new \FilesystemIterator($dir))->valid();
  109. if ($isDirEmpty) {
  110. @rmdir($dir);
  111. remove_empty_folder(dirname($dir));
  112. }
  113. } catch (\UnexpectedValueException $e) {
  114. } catch (\Exception $e) {
  115. }
  116. }
  117. /**
  118. * 获得插件列表
  119. * @return array
  120. */
  121. function get_addon_list()
  122. {
  123. $results = scandir(ADDON_PATH);
  124. $list = [];
  125. foreach ($results as $name) {
  126. if ($name === '.' or $name === '..') {
  127. continue;
  128. }
  129. if (is_file(ADDON_PATH . $name)) {
  130. continue;
  131. }
  132. $addonDir = ADDON_PATH . $name . DS;
  133. if (!is_dir($addonDir)) {
  134. continue;
  135. }
  136. if (!is_file($addonDir . ucfirst($name) . '.php')) {
  137. continue;
  138. }
  139. //这里不采用get_addon_info是因为会有缓存
  140. //$info = get_addon_info($name);
  141. $info_file = $addonDir . 'info.ini';
  142. if (!is_file($info_file)) {
  143. continue;
  144. }
  145. $info = Config::parse($info_file, '', "addon-info-{$name}");
  146. if (!isset($info['name'])) {
  147. continue;
  148. }
  149. $info['url'] = addon_url($name);
  150. $list[$name] = $info;
  151. }
  152. return $list;
  153. }
  154. /**
  155. * 获得插件自动加载的配置
  156. * @param bool $truncate 是否清除手动配置的钩子
  157. * @return array
  158. */
  159. function get_addon_autoload_config($truncate = false)
  160. {
  161. // 读取addons的配置
  162. $config = (array)Config::get('addons');
  163. if ($truncate) {
  164. // 清空手动配置的钩子
  165. $config['hooks'] = [];
  166. }
  167. // 伪静态优先级
  168. $priority = isset($config['priority']) && $config['priority'] ? is_array($config['priority']) ? $config['priority'] : explode(',', $config['priority']) : [];
  169. $route = [];
  170. // 读取插件目录及钩子列表
  171. $base = get_class_methods("\\think\\Addons");
  172. $base = array_merge($base, ['install', 'uninstall', 'enable', 'disable']);
  173. $url_domain_deploy = Config::get('url_domain_deploy');
  174. $addons = get_addon_list();
  175. $domain = [];
  176. $priority = array_merge($priority, array_keys($addons));
  177. $orderedAddons = array();
  178. foreach ($priority as $key) {
  179. if (!isset($addons[$key])) {
  180. continue;
  181. }
  182. $orderedAddons[$key] = $addons[$key];
  183. }
  184. foreach ($orderedAddons as $name => $addon) {
  185. if (!$addon['state']) {
  186. continue;
  187. }
  188. // 读取出所有公共方法
  189. $methods = (array)get_class_methods("\\addons\\" . $name . "\\" . ucfirst($name));
  190. // 跟插件基类方法做比对,得到差异结果
  191. $hooks = array_diff($methods, $base);
  192. // 循环将钩子方法写入配置中
  193. foreach ($hooks as $hook) {
  194. $hook = Loader::parseName($hook, 0, false);
  195. if (!isset($config['hooks'][$hook])) {
  196. $config['hooks'][$hook] = [];
  197. }
  198. // 兼容手动配置项
  199. if (is_string($config['hooks'][$hook])) {
  200. $config['hooks'][$hook] = explode(',', $config['hooks'][$hook]);
  201. }
  202. if (!in_array($name, $config['hooks'][$hook])) {
  203. $config['hooks'][$hook][] = $name;
  204. }
  205. }
  206. $conf = get_addon_config($addon['name']);
  207. if ($conf) {
  208. $conf['rewrite'] = isset($conf['rewrite']) && is_array($conf['rewrite']) ? $conf['rewrite'] : [];
  209. $rule = array_map(function ($value) use ($addon) {
  210. return "{$addon['name']}/{$value}";
  211. }, array_flip($conf['rewrite']));
  212. if ($url_domain_deploy && isset($conf['domain']) && $conf['domain']) {
  213. $domain[] = [
  214. 'addon' => $addon['name'],
  215. 'domain' => $conf['domain'],
  216. 'rule' => $rule
  217. ];
  218. } else {
  219. $route = array_merge($route, $rule);
  220. }
  221. }
  222. }
  223. $config['route'] = $route;
  224. $config['route'] = array_merge($config['route'], $domain);
  225. return $config;
  226. }
  227. /**
  228. * 获取插件类的类名
  229. * @param string $name 插件名
  230. * @param string $type 返回命名空间类型
  231. * @param string $class 当前类名
  232. * @return string
  233. */
  234. function get_addon_class($name, $type = 'hook', $class = null)
  235. {
  236. $name = Loader::parseName($name);
  237. // 处理多级控制器情况
  238. if (!is_null($class) && strpos($class, '.')) {
  239. $class = explode('.', $class);
  240. $class[count($class) - 1] = Loader::parseName(end($class), 1);
  241. $class = implode('\\', $class);
  242. } else {
  243. $class = Loader::parseName(is_null($class) ? $name : $class, 1);
  244. }
  245. switch ($type) {
  246. case 'controller':
  247. $namespace = "\\addons\\" . $name . "\\controller\\" . $class;
  248. break;
  249. default:
  250. $namespace = "\\addons\\" . $name . "\\" . $class;
  251. }
  252. return class_exists($namespace) ? $namespace : '';
  253. }
  254. /**
  255. * 读取插件的基础信息
  256. * @param string $name 插件名
  257. * @return array
  258. */
  259. function get_addon_info($name)
  260. {
  261. $addon = get_addon_instance($name);
  262. if (!$addon) {
  263. return [];
  264. }
  265. return $addon->getInfo($name);
  266. }
  267. /**
  268. * 获取插件类的配置数组
  269. * @param string $name 插件名
  270. * @return array
  271. */
  272. function get_addon_fullconfig($name)
  273. {
  274. $addon = get_addon_instance($name);
  275. if (!$addon) {
  276. return [];
  277. }
  278. return $addon->getFullConfig($name);
  279. }
  280. /**
  281. * 获取插件类的配置值值
  282. * @param string $name 插件名
  283. * @return array
  284. */
  285. function get_addon_config($name)
  286. {
  287. $addon = get_addon_instance($name);
  288. if (!$addon) {
  289. return [];
  290. }
  291. return $addon->getConfig($name);
  292. }
  293. /**
  294. * 获取插件的单例
  295. * @param string $name 插件名
  296. * @return mixed|null
  297. */
  298. function get_addon_instance($name)
  299. {
  300. static $_addons = [];
  301. if (isset($_addons[$name])) {
  302. return $_addons[$name];
  303. }
  304. $class = get_addon_class($name);
  305. if (class_exists($class)) {
  306. $_addons[$name] = new $class();
  307. return $_addons[$name];
  308. } else {
  309. return null;
  310. }
  311. }
  312. /**
  313. * 获取插件创建的表
  314. * @param string $name 插件名
  315. * @return array
  316. */
  317. function get_addon_tables($name)
  318. {
  319. $addonInfo = get_addon_info($name);
  320. if (!$addonInfo) {
  321. return [];
  322. }
  323. $regex = "/^CREATE\s+TABLE\s+(IF\s+NOT\s+EXISTS\s+)?`?([a-zA-Z_]+)`?/mi";
  324. $sqlFile = ADDON_PATH . $name . DS . 'install.sql';
  325. $tables = [];
  326. if (is_file($sqlFile)) {
  327. preg_match_all($regex, file_get_contents($sqlFile), $matches);
  328. if ($matches && isset($matches[2]) && $matches[2]) {
  329. $prefix = config('database.prefix');
  330. $tables = array_map(function ($item) use ($prefix) {
  331. return str_replace("__PREFIX__", $prefix, $item);
  332. }, $matches[2]);
  333. }
  334. }
  335. return $tables;
  336. }
  337. /**
  338. * 插件显示内容里生成访问插件的url
  339. * @param string $url 地址 格式:插件名/控制器/方法
  340. * @param array $vars 变量参数
  341. * @param bool|string $suffix 生成的URL后缀
  342. * @param bool|string $domain 域名
  343. * @return bool|string
  344. */
  345. function addon_url($url, $vars = [], $suffix = true, $domain = false)
  346. {
  347. $url = ltrim($url, '/');
  348. $addon = substr($url, 0, stripos($url, '/'));
  349. if (!is_array($vars)) {
  350. parse_str($vars, $params);
  351. $vars = $params;
  352. }
  353. $params = [];
  354. foreach ($vars as $k => $v) {
  355. if (substr($k, 0, 1) === ':') {
  356. $params[$k] = $v;
  357. unset($vars[$k]);
  358. }
  359. }
  360. $val = "@addons/{$url}";
  361. $config = get_addon_config($addon);
  362. $dispatch = think\Request::instance()->dispatch();
  363. $indomain = isset($dispatch['var']['indomain']) && $dispatch['var']['indomain'] && $dispatch['var']['addon'] == $addon ? true : false;
  364. //优先取插件配置中的domain,没有的情况下取全局的域名前缀配置
  365. $domainprefix = $config && isset($config['domain']) && $config['domain'] ? $config['domain'] : Config::get('addons.domain');
  366. $domain = $domainprefix && Config::get('url_domain_deploy') ? $domainprefix : $domain;
  367. $rewrite = $config && isset($config['rewrite']) && $config['rewrite'] ? $config['rewrite'] : [];
  368. if ($rewrite) {
  369. $path = substr($url, stripos($url, '/') + 1);
  370. if (isset($rewrite[$path]) && $rewrite[$path]) {
  371. $val = $rewrite[$path];
  372. array_walk($params, function ($value, $key) use (&$val) {
  373. $val = str_replace("[{$key}]", $value, $val);
  374. });
  375. $val = str_replace(['^', '$'], '', $val);
  376. if (substr($val, -1) === '/') {
  377. $suffix = false;
  378. }
  379. } else {
  380. // 如果采用了域名部署,则需要去掉前两段
  381. if ($indomain && $domainprefix) {
  382. $arr = explode("/", $val);
  383. $val = implode("/", array_slice($arr, 2));
  384. }
  385. }
  386. } else {
  387. // 如果采用了域名部署,则需要去掉前两段
  388. if ($indomain && $domainprefix) {
  389. $arr = explode("/", $val);
  390. $val = implode("/", array_slice($arr, 2));
  391. }
  392. foreach ($params as $k => $v) {
  393. $vars[substr($k, 1)] = $v;
  394. }
  395. }
  396. $url = url($val, [], $suffix, $domain) . ($vars ? '?' . http_build_query($vars) : '');
  397. $url = preg_replace("/\/((?!index)[\w]+)\.php\//i", "/", $url);
  398. return $url;
  399. }
  400. /**
  401. * 设置基础配置信息
  402. * @param string $name 插件名
  403. * @param array $array 配置数据
  404. * @return boolean
  405. * @throws Exception
  406. */
  407. function set_addon_info($name, $array)
  408. {
  409. $file = ADDON_PATH . $name . DS . 'info.ini';
  410. $addon = get_addon_instance($name);
  411. $array = $addon->setInfo($name, $array);
  412. if (!isset($array['name']) || !isset($array['title']) || !isset($array['version'])) {
  413. throw new Exception("插件配置写入失败");
  414. }
  415. $res = array();
  416. foreach ($array as $key => $val) {
  417. if (is_array($val)) {
  418. $res[] = "[$key]";
  419. foreach ($val as $skey => $sval) {
  420. $res[] = "$skey = " . (is_numeric($sval) ? $sval : $sval);
  421. }
  422. } else {
  423. $res[] = "$key = " . (is_numeric($val) ? $val : $val);
  424. }
  425. }
  426. if (file_put_contents($file, implode("\n", $res) . "\n", LOCK_EX)) {
  427. //清空当前配置缓存
  428. Config::set($name, null, 'addoninfo');
  429. } else {
  430. throw new Exception("文件没有写入权限");
  431. }
  432. return true;
  433. }
  434. /**
  435. * 写入配置文件
  436. * @param string $name 插件名
  437. * @param array $config 配置数据
  438. * @param boolean $writefile 是否写入配置文件
  439. * @return bool
  440. * @throws Exception
  441. */
  442. function set_addon_config($name, $config, $writefile = true)
  443. {
  444. $addon = get_addon_instance($name);
  445. $addon->setConfig($name, $config);
  446. $fullconfig = get_addon_fullconfig($name);
  447. foreach ($fullconfig as $k => &$v) {
  448. if (isset($config[$v['name']])) {
  449. $value = $v['type'] !== 'array' && is_array($config[$v['name']]) ? implode(',', $config[$v['name']]) : $config[$v['name']];
  450. $v['value'] = $value;
  451. }
  452. }
  453. if ($writefile) {
  454. // 写入配置文件
  455. set_addon_fullconfig($name, $fullconfig);
  456. }
  457. return true;
  458. }
  459. /**
  460. * 写入配置文件
  461. *
  462. * @param string $name 插件名
  463. * @param array $array 配置数据
  464. * @return boolean
  465. * @throws Exception
  466. */
  467. function set_addon_fullconfig($name, $array)
  468. {
  469. $file = ADDON_PATH . $name . DS . 'config.php';
  470. $ret = file_put_contents($file, "<?php\n\n" . "return " . VarExporter::export($array) . ";\n", LOCK_EX);
  471. if (!$ret) {
  472. throw new Exception("配置写入失败");
  473. }
  474. return true;
  475. }