Helper.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. <?php
  2. namespace Utils;
  3. use Typecho\Common;
  4. use Typecho\Db;
  5. use Typecho\I18n;
  6. use Typecho\Plugin;
  7. use Typecho\Widget;
  8. use Widget\Base\Options as BaseOptions;
  9. use Widget\Options;
  10. use Widget\Plugins\Edit;
  11. use Widget\Security;
  12. use Widget\Service;
  13. /**
  14. * 插件帮手将默认出现在所有的typecho发行版中.
  15. * 因此你可以放心使用它的功能, 以方便你的插件安装在用户的系统里.
  16. *
  17. * @package Helper
  18. * @author qining
  19. * @version 1.0.0
  20. * @link http://typecho.org
  21. */
  22. class Helper
  23. {
  24. /**
  25. * 获取Security对象
  26. *
  27. * @return Security
  28. */
  29. public static function security(): Security
  30. {
  31. return Security::alloc();
  32. }
  33. /**
  34. * 根据ID获取单个Widget对象
  35. *
  36. * @param string $table 表名, 支持 contents, comments, metas, users
  37. * @param int $pkId
  38. * @return Widget|null
  39. */
  40. public static function widgetById(string $table, int $pkId): ?Widget
  41. {
  42. $table = ucfirst($table);
  43. if (!in_array($table, ['Contents', 'Comments', 'Metas', 'Users'])) {
  44. return null;
  45. }
  46. $keys = [
  47. 'Contents' => 'cid',
  48. 'Comments' => 'coid',
  49. 'Metas' => 'mid',
  50. 'Users' => 'uid'
  51. ];
  52. $className = '\Widget\Base\\' . $table;
  53. $key = $keys[$table];
  54. $db = Db::get();
  55. $widget = Widget::widget($className . '@' . $pkId);
  56. $db->fetchRow(
  57. $widget->select()->where("{$key} = ?", $pkId)->limit(1),
  58. [$widget, 'push']
  59. );
  60. return $widget;
  61. }
  62. /**
  63. * 请求异步服务
  64. *
  65. * @param $method
  66. * @param $params
  67. */
  68. public static function requestService($method, $params)
  69. {
  70. Service::alloc()->requestService($method, $params);
  71. }
  72. /**
  73. * 强行删除某个插件
  74. *
  75. * @param string $pluginName 插件名称
  76. */
  77. public static function removePlugin(string $pluginName)
  78. {
  79. try {
  80. /** 获取插件入口 */
  81. [$pluginFileName, $className] = Plugin::portal(
  82. $pluginName,
  83. __TYPECHO_ROOT_DIR__ . '/' . __TYPECHO_PLUGIN_DIR__
  84. );
  85. /** 获取已启用插件 */
  86. $plugins = Plugin::export();
  87. $activatedPlugins = $plugins['activated'];
  88. /** 载入插件 */
  89. require_once $pluginFileName;
  90. /** 判断实例化是否成功 */
  91. if (
  92. !isset($activatedPlugins[$pluginName]) || !class_exists($className)
  93. || !method_exists($className, 'deactivate')
  94. ) {
  95. throw new Widget\Exception(_t('无法禁用插件'), 500);
  96. }
  97. call_user_func([$className, 'deactivate']);
  98. } catch (\Exception $e) {
  99. //nothing to do
  100. }
  101. $db = Db::get();
  102. try {
  103. Plugin::deactivate($pluginName);
  104. $db->query($db->update('table.options')
  105. ->rows(['value' => serialize(Plugin::export())])
  106. ->where('name = ?', 'plugins'));
  107. } catch (Plugin\Exception $e) {
  108. //nothing to do
  109. }
  110. $db->query($db->delete('table.options')->where('name = ?', 'plugin:' . $pluginName));
  111. }
  112. /**
  113. * 导入语言项
  114. *
  115. * @param string $domain
  116. */
  117. public static function lang(string $domain)
  118. {
  119. $currentLang = I18n::getLang();
  120. if ($currentLang) {
  121. $currentLang = basename($currentLang);
  122. $fileName = dirname(__FILE__) . '/' . $domain . '/lang/' . $currentLang;
  123. if (file_exists($fileName)) {
  124. I18n::addLang($fileName);
  125. }
  126. }
  127. }
  128. /**
  129. * 增加路由
  130. *
  131. * @param string $name 路由名称
  132. * @param string $url 路由路径
  133. * @param string $widget 组件名称
  134. * @param string|null $action 组件动作
  135. * @param string|null $after 在某个路由后面
  136. * @return integer
  137. */
  138. public static function addRoute(
  139. string $name,
  140. string $url,
  141. string $widget,
  142. ?string $action = null,
  143. ?string $after = null
  144. ): int {
  145. $routingTable = self::options()->routingTable;
  146. if (isset($routingTable[0])) {
  147. unset($routingTable[0]);
  148. }
  149. $pos = 0;
  150. foreach ($routingTable as $key => $val) {
  151. $pos++;
  152. if ($key == $after) {
  153. break;
  154. }
  155. }
  156. $pre = array_slice($routingTable, 0, $pos);
  157. $next = array_slice($routingTable, $pos);
  158. $routingTable = array_merge($pre, [
  159. $name => [
  160. 'url' => $url,
  161. 'widget' => $widget,
  162. 'action' => $action
  163. ]
  164. ], $next);
  165. self::options()->routingTable = $routingTable;
  166. return BaseOptions::alloc()->update(
  167. ['value' => serialize($routingTable)],
  168. Db::get()->sql()->where('name = ?', 'routingTable')
  169. );
  170. }
  171. /**
  172. * 获取Options对象
  173. *
  174. * @return Options
  175. */
  176. public static function options(): Options
  177. {
  178. return Options::alloc();
  179. }
  180. /**
  181. * 移除路由
  182. *
  183. * @param string $name 路由名称
  184. * @return integer
  185. */
  186. public static function removeRoute(string $name): int
  187. {
  188. $routingTable = self::options()->routingTable;
  189. if (isset($routingTable[0])) {
  190. unset($routingTable[0]);
  191. }
  192. unset($routingTable[$name]);
  193. self::options()->routingTable = $routingTable;
  194. $db = Db::get();
  195. return BaseOptions::alloc()->update(
  196. ['value' => serialize($routingTable)],
  197. $db->sql()->where('name = ?', 'routingTable')
  198. );
  199. }
  200. /**
  201. * 增加action扩展
  202. *
  203. * @param string $actionName 需要扩展的action名称
  204. * @param string $widgetName 需要扩展的widget名称
  205. * @return integer
  206. */
  207. public static function addAction(string $actionName, string $widgetName): int
  208. {
  209. $actionTable = unserialize(self::options()->actionTable);
  210. $actionTable = empty($actionTable) ? [] : $actionTable;
  211. $actionTable[$actionName] = $widgetName;
  212. return BaseOptions::alloc()->update(
  213. ['value' => (self::options()->actionTable = serialize($actionTable))],
  214. Db::get()->sql()->where('name = ?', 'actionTable')
  215. );
  216. }
  217. /**
  218. * 删除action扩展
  219. *
  220. * @param string $actionName
  221. * @return int
  222. */
  223. public static function removeAction(string $actionName): int
  224. {
  225. $actionTable = unserialize(self::options()->actionTable);
  226. $actionTable = empty($actionTable) ? [] : $actionTable;
  227. if (isset($actionTable[$actionName])) {
  228. unset($actionTable[$actionName]);
  229. reset($actionTable);
  230. }
  231. return BaseOptions::alloc()->update(
  232. ['value' => (self::options()->actionTable = serialize($actionTable))],
  233. Db::get()->sql()->where('name = ?', 'actionTable')
  234. );
  235. }
  236. /**
  237. * 增加一个菜单
  238. *
  239. * @param string $menuName 菜单名
  240. * @return integer
  241. */
  242. public static function addMenu(string $menuName): int
  243. {
  244. $panelTable = unserialize(self::options()->panelTable);
  245. $panelTable['parent'] = empty($panelTable['parent']) ? [] : $panelTable['parent'];
  246. $panelTable['parent'][] = $menuName;
  247. BaseOptions::alloc()->update(
  248. ['value' => (self::options()->panelTable = serialize($panelTable))],
  249. Db::get()->sql()->where('name = ?', 'panelTable')
  250. );
  251. end($panelTable['parent']);
  252. return key($panelTable['parent']) + 10;
  253. }
  254. /**
  255. * 移除一个菜单
  256. *
  257. * @param string $menuName 菜单名
  258. * @return integer
  259. */
  260. public static function removeMenu(string $menuName): int
  261. {
  262. $panelTable = unserialize(self::options()->panelTable);
  263. $panelTable['parent'] = empty($panelTable['parent']) ? [] : $panelTable['parent'];
  264. if (false !== ($index = array_search($menuName, $panelTable['parent']))) {
  265. unset($panelTable['parent'][$index]);
  266. }
  267. BaseOptions::alloc()->update(
  268. ['value' => (self::options()->panelTable = serialize($panelTable))],
  269. Db::get()->sql()->where('name = ?', 'panelTable')
  270. );
  271. return $index + 10;
  272. }
  273. /**
  274. * 增加一个面板
  275. *
  276. * @param integer $index 菜单索引
  277. * @param string $fileName 文件名称
  278. * @param string $title 面板标题
  279. * @param string $subTitle 面板副标题
  280. * @param string $level 进入权限
  281. * @param boolean $hidden 是否隐藏
  282. * @param string $addLink 新增项目链接, 会显示在页面标题之后
  283. * @return integer
  284. */
  285. public static function addPanel(
  286. int $index,
  287. string $fileName,
  288. string $title,
  289. string $subTitle,
  290. string $level,
  291. bool $hidden = false,
  292. string $addLink = ''
  293. ): int {
  294. $panelTable = unserialize(self::options()->panelTable);
  295. $panelTable['child'] = empty($panelTable['child']) ? [] : $panelTable['child'];
  296. $panelTable['child'][$index] = empty($panelTable['child'][$index]) ? [] : $panelTable['child'][$index];
  297. $fileName = urlencode(trim($fileName, '/'));
  298. $panelTable['child'][$index][]
  299. = [$title, $subTitle, 'extending.php?panel=' . $fileName, $level, $hidden, $addLink];
  300. $panelTable['file'] = empty($panelTable['file']) ? [] : $panelTable['file'];
  301. $panelTable['file'][] = $fileName;
  302. $panelTable['file'] = array_unique($panelTable['file']);
  303. BaseOptions::alloc()->update(
  304. ['value' => (self::options()->panelTable = serialize($panelTable))],
  305. Db::get()->sql()->where('name = ?', 'panelTable')
  306. );
  307. end($panelTable['child'][$index]);
  308. return key($panelTable['child'][$index]);
  309. }
  310. /**
  311. * 移除一个面板
  312. *
  313. * @param integer $index 菜单索引
  314. * @param string $fileName 文件名称
  315. * @return integer
  316. */
  317. public static function removePanel(int $index, string $fileName): int
  318. {
  319. $panelTable = unserialize(self::options()->panelTable);
  320. $panelTable['child'] = empty($panelTable['child']) ? [] : $panelTable['child'];
  321. $panelTable['child'][$index] = empty($panelTable['child'][$index]) ? [] : $panelTable['child'][$index];
  322. $panelTable['file'] = empty($panelTable['file']) ? [] : $panelTable['file'];
  323. $fileName = urlencode(trim($fileName, '/'));
  324. if (false !== ($key = array_search($fileName, $panelTable['file']))) {
  325. unset($panelTable['file'][$key]);
  326. }
  327. $return = 0;
  328. foreach ($panelTable['child'][$index] as $key => $val) {
  329. if ($val[2] == 'extending.php?panel=' . $fileName) {
  330. unset($panelTable['child'][$index][$key]);
  331. $return = $key;
  332. }
  333. }
  334. BaseOptions::alloc()->update(
  335. ['value' => (self::options()->panelTable = serialize($panelTable))],
  336. Db::get()->sql()->where('name = ?', 'panelTable')
  337. );
  338. return $return;
  339. }
  340. /**
  341. * 获取面板url
  342. *
  343. * @param string $fileName
  344. * @return string
  345. */
  346. public static function url(string $fileName): string
  347. {
  348. return Common::url('extending.php?panel=' . (trim($fileName, '/')), self::options()->adminUrl);
  349. }
  350. /**
  351. * 手动配置插件变量
  352. *
  353. * @param mixed $pluginName 插件名称
  354. * @param array $settings 变量键值对
  355. * @param bool $isPersonal . (default: false) 是否为私人变量
  356. */
  357. public static function configPlugin($pluginName, array $settings, bool $isPersonal = false)
  358. {
  359. if (empty($settings)) {
  360. return;
  361. }
  362. Edit::configPlugin($pluginName, $settings, $isPersonal);
  363. }
  364. /**
  365. * 评论回复按钮
  366. *
  367. * @access public
  368. * @param string $theId 评论元素id
  369. * @param integer $coid 评论id
  370. * @param string $word 按钮文字
  371. * @param string $formId 表单id
  372. * @param integer $style 样式类型
  373. * @return void
  374. */
  375. public static function replyLink(
  376. string $theId,
  377. int $coid,
  378. string $word = 'Reply',
  379. string $formId = 'respond',
  380. int $style = 2
  381. ) {
  382. if (self::options()->commentsThreaded) {
  383. echo '<a href="#' . $formId . '" rel="nofollow" onclick="return typechoAddCommentReply(\'' .
  384. $theId . '\', ' . $coid . ', \'' . $formId . '\', ' . $style . ');">' . $word . '</a>';
  385. }
  386. }
  387. /**
  388. * 评论取消按钮
  389. *
  390. * @param string $word 按钮文字
  391. * @param string $formId 表单id
  392. */
  393. public static function cancelCommentReplyLink(string $word = 'Cancel', string $formId = 'respond')
  394. {
  395. if (self::options()->commentsThreaded) {
  396. echo '<a href="#' . $formId . '" rel="nofollow" onclick="return typechoCancelCommentReply(\'' .
  397. $formId . '\');">' . $word . '</a>';
  398. }
  399. }
  400. /**
  401. * 评论回复js脚本
  402. */
  403. public static function threadedCommentsScript()
  404. {
  405. if (self::options()->commentsThreaded) {
  406. echo
  407. <<<EOF
  408. <script type="text/javascript">
  409. var typechoAddCommentReply = function (cid, coid, cfid, style) {
  410. var _ce = document.getElementById(cid), _cp = _ce.parentNode;
  411. var _cf = document.getElementById(cfid);
  412. var _pi = document.getElementById('comment-parent');
  413. if (null == _pi) {
  414. _pi = document.createElement('input');
  415. _pi.setAttribute('type', 'hidden');
  416. _pi.setAttribute('name', 'parent');
  417. _pi.setAttribute('id', 'comment-parent');
  418. var _form = 'form' == _cf.tagName ? _cf : _cf.getElementsByTagName('form')[0];
  419. _form.appendChild(_pi);
  420. }
  421. _pi.setAttribute('value', coid);
  422. if (null == document.getElementById('comment-form-place-holder')) {
  423. var _cfh = document.createElement('div');
  424. _cfh.setAttribute('id', 'comment-form-place-holder');
  425. _cf.parentNode.insertBefore(_cfh, _cf);
  426. }
  427. 1 == style ? (null == _ce.nextSibling ? _cp.appendChild(_cf)
  428. : _cp.insertBefore(_cf, _ce.nextSibling)) : _ce.appendChild(_cf);
  429. return false;
  430. };
  431. var typechoCancelCommentReply = function (cfid) {
  432. var _cf = document.getElementById(cfid),
  433. _cfh = document.getElementById('comment-form-place-holder');
  434. var _pi = document.getElementById('comment-parent');
  435. if (null != _pi) {
  436. _pi.parentNode.removeChild(_pi);
  437. }
  438. if (null == _cfh) {
  439. return true;
  440. }
  441. _cfh.parentNode.insertBefore(_cf, _cfh);
  442. return false;
  443. };
  444. </script>
  445. EOF;
  446. }
  447. }
  448. }