Plugin.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <?php
  2. namespace Typecho;
  3. use Typecho\Plugin\Exception as PluginException;
  4. /**
  5. * 插件处理类
  6. *
  7. * @category typecho
  8. * @package Plugin
  9. * @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
  10. * @license GNU General Public License 2.0
  11. */
  12. class Plugin
  13. {
  14. /**
  15. * 所有启用的插件
  16. *
  17. * @var array
  18. */
  19. private static $plugin = [];
  20. /**
  21. * 实例化的插件对象
  22. *
  23. * @var array
  24. */
  25. private static $instances;
  26. /**
  27. * 临时存储变量
  28. *
  29. * @var array
  30. */
  31. private static $tmp = [];
  32. /**
  33. * 唯一句柄
  34. *
  35. * @var string
  36. */
  37. private $handle;
  38. /**
  39. * 组件
  40. *
  41. * @var string
  42. */
  43. private $component;
  44. /**
  45. * 是否触发插件的信号
  46. *
  47. * @var boolean
  48. */
  49. private $signal;
  50. /**
  51. * 插件初始化
  52. *
  53. * @param string $handle 插件
  54. */
  55. public function __construct(string $handle)
  56. {
  57. if (defined('__TYPECHO_CLASS_ALIASES__')) {
  58. $alias = array_search('\\' . ltrim($handle, '\\'), __TYPECHO_CLASS_ALIASES__);
  59. $handle = $alias ?: $handle;
  60. }
  61. $this->handle = Common::nativeClassName($handle);
  62. }
  63. /**
  64. * 插件初始化
  65. *
  66. * @param array $plugins 插件列表
  67. */
  68. public static function init(array $plugins)
  69. {
  70. $plugins['activated'] = array_key_exists('activated', $plugins) ? $plugins['activated'] : [];
  71. $plugins['handles'] = array_key_exists('handles', $plugins) ? $plugins['handles'] : [];
  72. /** 初始化变量 */
  73. self::$plugin = $plugins;
  74. }
  75. /**
  76. * 获取实例化插件对象
  77. *
  78. * @param string $handle 插件
  79. * @return Plugin
  80. */
  81. public static function factory(string $handle): Plugin
  82. {
  83. return self::$instances[$handle] ?? (self::$instances[$handle] = new self($handle));
  84. }
  85. /**
  86. * 启用插件
  87. *
  88. * @param string $pluginName 插件名称
  89. */
  90. public static function activate(string $pluginName)
  91. {
  92. self::$plugin['activated'][$pluginName] = self::$tmp;
  93. self::$tmp = [];
  94. }
  95. /**
  96. * 禁用插件
  97. *
  98. * @param string $pluginName 插件名称
  99. */
  100. public static function deactivate(string $pluginName)
  101. {
  102. /** 去掉所有相关回调函数 */
  103. if (
  104. isset(self::$plugin['activated'][$pluginName]['handles'])
  105. && is_array(self::$plugin['activated'][$pluginName]['handles'])
  106. ) {
  107. foreach (self::$plugin['activated'][$pluginName]['handles'] as $handle => $handles) {
  108. self::$plugin['handles'][$handle] = self::pluginHandlesDiff(
  109. empty(self::$plugin['handles'][$handle]) ? [] : self::$plugin['handles'][$handle],
  110. empty($handles) ? [] : $handles
  111. );
  112. if (empty(self::$plugin['handles'][$handle])) {
  113. unset(self::$plugin['handles'][$handle]);
  114. }
  115. }
  116. }
  117. /** 禁用当前插件 */
  118. unset(self::$plugin['activated'][$pluginName]);
  119. }
  120. /**
  121. * 插件handle比对
  122. *
  123. * @param array $pluginHandles
  124. * @param array $otherPluginHandles
  125. * @return array
  126. */
  127. private static function pluginHandlesDiff(array $pluginHandles, array $otherPluginHandles): array
  128. {
  129. foreach ($otherPluginHandles as $handle) {
  130. while (false !== ($index = array_search($handle, $pluginHandles))) {
  131. unset($pluginHandles[$index]);
  132. }
  133. }
  134. return $pluginHandles;
  135. }
  136. /**
  137. * 导出当前插件设置
  138. *
  139. * @return array
  140. */
  141. public static function export(): array
  142. {
  143. return self::$plugin;
  144. }
  145. /**
  146. * 获取插件文件的头信息
  147. *
  148. * @param string $pluginFile 插件文件路径
  149. * @return array
  150. */
  151. public static function parseInfo(string $pluginFile): array
  152. {
  153. $tokens = token_get_all(file_get_contents($pluginFile));
  154. $isDoc = false;
  155. $isFunction = false;
  156. $isClass = false;
  157. $isInClass = false;
  158. $isInFunction = false;
  159. $isDefined = false;
  160. $current = null;
  161. /** 初始信息 */
  162. $info = [
  163. 'description' => '',
  164. 'title' => '',
  165. 'author' => '',
  166. 'homepage' => '',
  167. 'version' => '',
  168. 'since' => '',
  169. 'activate' => false,
  170. 'deactivate' => false,
  171. 'config' => false,
  172. 'personalConfig' => false
  173. ];
  174. $map = [
  175. 'package' => 'title',
  176. 'author' => 'author',
  177. 'link' => 'homepage',
  178. 'since' => 'since',
  179. 'version' => 'version'
  180. ];
  181. foreach ($tokens as $token) {
  182. /** 获取doc comment */
  183. if (!$isDoc && is_array($token) && T_DOC_COMMENT == $token[0]) {
  184. /** 分行读取 */
  185. $described = false;
  186. $lines = preg_split("(\r|\n)", $token[1]);
  187. foreach ($lines as $line) {
  188. $line = trim($line);
  189. if (!empty($line) && '*' == $line[0]) {
  190. $line = trim(substr($line, 1));
  191. if (!$described && !empty($line) && '@' == $line[0]) {
  192. $described = true;
  193. }
  194. if (!$described && !empty($line)) {
  195. $info['description'] .= $line . "\n";
  196. } elseif ($described && !empty($line) && '@' == $line[0]) {
  197. $info['description'] = trim($info['description']);
  198. $line = trim(substr($line, 1));
  199. $args = explode(' ', $line);
  200. $key = array_shift($args);
  201. if (isset($map[$key])) {
  202. $info[$map[$key]] = trim(implode(' ', $args));
  203. }
  204. }
  205. }
  206. }
  207. $isDoc = true;
  208. }
  209. if (is_array($token)) {
  210. switch ($token[0]) {
  211. case T_FUNCTION:
  212. $isFunction = true;
  213. break;
  214. case T_IMPLEMENTS:
  215. $isClass = true;
  216. break;
  217. case T_WHITESPACE:
  218. case T_COMMENT:
  219. case T_DOC_COMMENT:
  220. break;
  221. case T_STRING:
  222. $string = strtolower($token[1]);
  223. switch ($string) {
  224. case 'typecho_plugin_interface':
  225. case 'plugininterface':
  226. $isInClass = $isClass;
  227. break;
  228. case 'activate':
  229. case 'deactivate':
  230. case 'config':
  231. case 'personalconfig':
  232. if ($isFunction) {
  233. $current = ('personalconfig' == $string ? 'personalConfig' : $string);
  234. }
  235. break;
  236. default:
  237. if (!empty($current) && $isInFunction && $isInClass) {
  238. $info[$current] = true;
  239. }
  240. break;
  241. }
  242. break;
  243. default:
  244. if (!empty($current) && $isInFunction && $isInClass) {
  245. $info[$current] = true;
  246. }
  247. break;
  248. }
  249. } else {
  250. $token = strtolower($token);
  251. switch ($token) {
  252. case '{':
  253. if ($isDefined) {
  254. $isInFunction = true;
  255. }
  256. break;
  257. case '(':
  258. if ($isFunction && !$isDefined) {
  259. $isDefined = true;
  260. }
  261. break;
  262. case '}':
  263. case ';':
  264. $isDefined = false;
  265. $isFunction = false;
  266. $isInFunction = false;
  267. $current = null;
  268. break;
  269. default:
  270. if (!empty($current) && $isInFunction && $isInClass) {
  271. $info[$current] = true;
  272. }
  273. break;
  274. }
  275. }
  276. }
  277. return $info;
  278. }
  279. /**
  280. * 获取插件路径和类名
  281. * 返回值为一个数组
  282. * 第一项为插件路径,第二项为类名
  283. *
  284. * @param string $pluginName 插件名
  285. * @param string $path 插件目录
  286. * @return array
  287. * @throws PluginException
  288. */
  289. public static function portal(string $pluginName, string $path): array
  290. {
  291. switch (true) {
  292. case file_exists($pluginFileName = $path . '/' . $pluginName . '/Plugin.php'):
  293. $className = "\\" . PLUGIN_NAMESPACE . "\\{$pluginName}\\Plugin";
  294. break;
  295. case file_exists($pluginFileName = $path . '/' . $pluginName . '.php'):
  296. $className = "\\" . PLUGIN_NAMESPACE . "\\" . $pluginName;
  297. break;
  298. default:
  299. throw new PluginException('Missing Plugin ' . $pluginName, 404);
  300. }
  301. return [$pluginFileName, $className];
  302. }
  303. /**
  304. * 版本依赖性检测
  305. *
  306. * @param string|null $version 插件版本
  307. * @return boolean
  308. */
  309. public static function checkDependence(?string $version): bool
  310. {
  311. //如果没有检测规则,直接掠过
  312. if (empty($version)) {
  313. return true;
  314. }
  315. return version_compare(Common::VERSION, $version, '>=');
  316. }
  317. /**
  318. * 判断插件是否存在
  319. *
  320. * @param string $pluginName 插件名称
  321. * @return mixed
  322. */
  323. public static function exists(string $pluginName)
  324. {
  325. return array_key_exists($pluginName, self::$plugin['activated']);
  326. }
  327. /**
  328. * 插件调用后的触发器
  329. *
  330. * @param boolean|null $signal 触发器
  331. * @return Plugin
  332. */
  333. public function trigger(?bool &$signal): Plugin
  334. {
  335. $signal = false;
  336. $this->signal = &$signal;
  337. return $this;
  338. }
  339. /**
  340. * 通过魔术函数设置当前组件位置
  341. *
  342. * @param string $component 当前组件
  343. * @return Plugin
  344. */
  345. public function __get(string $component)
  346. {
  347. $this->component = $component;
  348. return $this;
  349. }
  350. /**
  351. * 设置回调函数
  352. *
  353. * @param string $component 当前组件
  354. * @param callable $value 回调函数
  355. */
  356. public function __set(string $component, callable $value)
  357. {
  358. $weight = 0;
  359. if (strpos($component, '_') > 0) {
  360. $parts = explode('_', $component, 2);
  361. [$component, $weight] = $parts;
  362. $weight = intval($weight) - 10;
  363. }
  364. $component = $this->handle . ':' . $component;
  365. if (!isset(self::$plugin['handles'][$component])) {
  366. self::$plugin['handles'][$component] = [];
  367. }
  368. if (!isset(self::$tmp['handles'][$component])) {
  369. self::$tmp['handles'][$component] = [];
  370. }
  371. foreach (self::$plugin['handles'][$component] as $key => $val) {
  372. $key = floatval($key);
  373. if ($weight > $key) {
  374. break;
  375. } elseif ($weight == $key) {
  376. $weight += 0.001;
  377. }
  378. }
  379. self::$plugin['handles'][$component][strval($weight)] = $value;
  380. self::$tmp['handles'][$component][] = $value;
  381. ksort(self::$plugin['handles'][$component], SORT_NUMERIC);
  382. }
  383. /**
  384. * 回调处理函数
  385. *
  386. * @param string $component 当前组件
  387. * @param array $args 参数
  388. * @return mixed
  389. */
  390. public function __call(string $component, array $args)
  391. {
  392. $component = $this->handle . ':' . $component;
  393. $last = count($args);
  394. $args[$last] = $last > 0 ? $args[0] : false;
  395. if (isset(self::$plugin['handles'][$component])) {
  396. $args[$last] = null;
  397. $this->signal = true;
  398. foreach (self::$plugin['handles'][$component] as $callback) {
  399. $args[$last] = call_user_func_array($callback, $args);
  400. }
  401. }
  402. return $args[$last];
  403. }
  404. }