Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

common.php 13 KiB

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