Addon.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. <?php
  2. namespace app\admin\command;
  3. use think\addons\AddonException;
  4. use think\addons\Service;
  5. use think\Config;
  6. use think\console\Command;
  7. use think\console\Input;
  8. use think\console\input\Option;
  9. use think\console\Output;
  10. use think\Db;
  11. use think\Exception;
  12. use think\exception\PDOException;
  13. class Addon extends Command
  14. {
  15. protected function configure()
  16. {
  17. $this
  18. ->setName('addon')
  19. ->addOption('name', 'a', Option::VALUE_REQUIRED, 'addon name', null)
  20. ->addOption('action', 'c', Option::VALUE_REQUIRED, 'action(create/enable/disable/install/uninstall/refresh/upgrade/package)', 'create')
  21. ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', null)
  22. ->addOption('release', 'r', Option::VALUE_OPTIONAL, 'addon release version', null)
  23. ->addOption('uid', 'u', Option::VALUE_OPTIONAL, 'fastadmin uid', null)
  24. ->addOption('token', 't', Option::VALUE_OPTIONAL, 'fastadmin token', null)
  25. ->setDescription('Addon manager');
  26. }
  27. protected function execute(Input $input, Output $output)
  28. {
  29. $name = $input->getOption('name') ?: '';
  30. $action = $input->getOption('action') ?: '';
  31. //强制覆盖
  32. $force = $input->getOption('force');
  33. //版本
  34. $release = $input->getOption('release') ?: '';
  35. //uid
  36. $uid = $input->getOption('uid') ?: '';
  37. //token
  38. $token = $input->getOption('token') ?: '';
  39. include dirname(__DIR__) . DS . 'common.php';
  40. if (!$name) {
  41. throw new Exception('Addon name could not be empty');
  42. }
  43. if (!$action || !in_array($action, ['create', 'disable', 'enable', 'install', 'uninstall', 'refresh', 'upgrade', 'package'])) {
  44. throw new Exception('Please input correct action name');
  45. }
  46. // 查询一次SQL,判断连接是否正常
  47. Db::execute("SELECT 1");
  48. $addonDir = ADDON_PATH . $name . DS;
  49. switch ($action) {
  50. case 'create':
  51. //非覆盖模式时如果存在则报错
  52. if (is_dir($addonDir) && !$force) {
  53. throw new Exception("addon already exists!\nIf you need to create again, use the parameter --force=true ");
  54. }
  55. //如果存在先移除
  56. if (is_dir($addonDir)) {
  57. rmdirs($addonDir);
  58. }
  59. mkdir($addonDir);
  60. mkdir($addonDir . DS . 'controller');
  61. $menuList = \app\common\library\Menu::export($name);
  62. $createMenu = $this->getCreateMenu($menuList);
  63. $prefix = Config::get('database.prefix');
  64. $createTableSql = '';
  65. try {
  66. $result = Db::query("SHOW CREATE TABLE `" . $prefix . $name . "`;");
  67. if (isset($result[0]) && isset($result[0]['Create Table'])) {
  68. $createTableSql = $result[0]['Create Table'];
  69. }
  70. } catch (PDOException $e) {
  71. }
  72. $data = [
  73. 'name' => $name,
  74. 'addon' => $name,
  75. 'addonClassName' => ucfirst($name),
  76. 'addonInstallMenu' => $createMenu ? "\$menu = " . var_export_short($createMenu, "\t") . ";\n\tMenu::create(\$menu);" : '',
  77. 'addonUninstallMenu' => $menuList ? 'Menu::delete("' . $name . '");' : '',
  78. 'addonEnableMenu' => $menuList ? 'Menu::enable("' . $name . '");' : '',
  79. 'addonDisableMenu' => $menuList ? 'Menu::disable("' . $name . '");' : '',
  80. ];
  81. $this->writeToFile("addon", $data, $addonDir . ucfirst($name) . '.php');
  82. $this->writeToFile("config", $data, $addonDir . 'config.php');
  83. $this->writeToFile("info", $data, $addonDir . 'info.ini');
  84. $this->writeToFile("controller", $data, $addonDir . 'controller' . DS . 'Index.php');
  85. if ($createTableSql) {
  86. $createTableSql = str_replace("`" . $prefix, '`__PREFIX__', $createTableSql);
  87. file_put_contents($addonDir . 'install.sql', $createTableSql);
  88. }
  89. $output->info("Create Successed!");
  90. break;
  91. case 'disable':
  92. case 'enable':
  93. try {
  94. //调用启用、禁用的方法
  95. Service::$action($name, 0);
  96. } catch (AddonException $e) {
  97. if ($e->getCode() != -3) {
  98. throw new Exception($e->getMessage());
  99. }
  100. if (!$force) {
  101. //如果有冲突文件则提醒
  102. $data = $e->getData();
  103. foreach ($data['conflictlist'] as $k => $v) {
  104. $output->warning($v);
  105. }
  106. $output->info("Are you sure you want to " . ($action == 'enable' ? 'override' : 'delete') . " all those files? Type 'yes' to continue: ");
  107. $line = fgets(STDIN);
  108. if (trim($line) != 'yes') {
  109. throw new Exception("Operation is aborted!");
  110. }
  111. }
  112. //调用启用、禁用的方法
  113. Service::$action($name, 1);
  114. } catch (Exception $e) {
  115. throw new Exception($e->getMessage());
  116. }
  117. $output->info(ucfirst($action) . " Successed!");
  118. break;
  119. case 'install':
  120. //非覆盖模式时如果存在则报错
  121. if (is_dir($addonDir) && !$force) {
  122. throw new Exception("addon already exists!\nIf you need to install again, use the parameter --force=true ");
  123. }
  124. //如果存在先移除
  125. if (is_dir($addonDir)) {
  126. rmdirs($addonDir);
  127. }
  128. try {
  129. Service::install($name, 0, ['version' => $release]);
  130. } catch (AddonException $e) {
  131. if ($e->getCode() != -3) {
  132. throw new Exception($e->getMessage());
  133. }
  134. if (!$force) {
  135. //如果有冲突文件则提醒
  136. $data = $e->getData();
  137. foreach ($data['conflictlist'] as $k => $v) {
  138. $output->warning($v);
  139. }
  140. $output->info("Are you sure you want to override all those files? Type 'yes' to continue: ");
  141. $line = fgets(STDIN);
  142. if (trim($line) != 'yes') {
  143. throw new Exception("Operation is aborted!");
  144. }
  145. }
  146. Service::install($name, 1, ['version' => $release, 'uid' => $uid, 'token' => $token]);
  147. } catch (Exception $e) {
  148. throw new Exception($e->getMessage());
  149. }
  150. $output->info("Install Successed!");
  151. break;
  152. case 'uninstall':
  153. //非覆盖模式时如果存在则报错
  154. if (!$force) {
  155. throw new Exception("If you need to uninstall addon, use the parameter --force=true ");
  156. }
  157. try {
  158. Service::uninstall($name, 0);
  159. } catch (AddonException $e) {
  160. if ($e->getCode() != -3) {
  161. throw new Exception($e->getMessage());
  162. }
  163. if (!$force) {
  164. //如果有冲突文件则提醒
  165. $data = $e->getData();
  166. foreach ($data['conflictlist'] as $k => $v) {
  167. $output->warning($v);
  168. }
  169. $output->info("Are you sure you want to delete all those files? Type 'yes' to continue: ");
  170. $line = fgets(STDIN);
  171. if (trim($line) != 'yes') {
  172. throw new Exception("Operation is aborted!");
  173. }
  174. }
  175. Service::uninstall($name, 1);
  176. } catch (Exception $e) {
  177. throw new Exception($e->getMessage());
  178. }
  179. $output->info("Uninstall Successed!");
  180. break;
  181. case 'refresh':
  182. Service::refresh();
  183. $output->info("Refresh Successed!");
  184. break;
  185. case 'upgrade':
  186. Service::upgrade($name, ['version' => $release, 'uid' => $uid, 'token' => $token]);
  187. $output->info("Upgrade Successed!");
  188. break;
  189. case 'package':
  190. $infoFile = $addonDir . 'info.ini';
  191. if (!is_file($infoFile)) {
  192. throw new Exception(__('Addon info file was not found'));
  193. }
  194. $info = get_addon_info($name);
  195. if (!$info) {
  196. throw new Exception(__('Addon info file data incorrect'));
  197. }
  198. $infoname = isset($info['name']) ? $info['name'] : '';
  199. if (!$infoname || !preg_match("/^[a-z]+$/i", $infoname) || $infoname != $name) {
  200. throw new Exception(__('Addon info name incorrect'));
  201. }
  202. $infoversion = isset($info['version']) ? $info['version'] : '';
  203. if (!$infoversion || !preg_match("/^\d+\.\d+\.\d+$/i", $infoversion)) {
  204. throw new Exception(__('Addon info version incorrect'));
  205. }
  206. $addonTmpDir = RUNTIME_PATH . 'addons' . DS;
  207. if (!is_dir($addonTmpDir)) {
  208. @mkdir($addonTmpDir, 0755, true);
  209. }
  210. $addonFile = $addonTmpDir . $infoname . '-' . $infoversion . '.zip';
  211. if (!class_exists('ZipArchive')) {
  212. throw new Exception(__('ZinArchive not install'));
  213. }
  214. $zip = new \ZipArchive;
  215. $zip->open($addonFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
  216. $files = new \RecursiveIteratorIterator(
  217. new \RecursiveDirectoryIterator($addonDir), \RecursiveIteratorIterator::LEAVES_ONLY
  218. );
  219. foreach ($files as $name => $file) {
  220. if (!$file->isDir()) {
  221. $filePath = $file->getRealPath();
  222. $relativePath = substr($filePath, strlen($addonDir));
  223. if (!in_array($file->getFilename(), ['.git', '.DS_Store', 'Thumbs.db'])) {
  224. $zip->addFile($filePath, $relativePath);
  225. }
  226. }
  227. }
  228. $zip->close();
  229. $output->info("Package Successed!");
  230. break;
  231. default :
  232. break;
  233. }
  234. }
  235. /**
  236. * 获取创建菜单的数组
  237. * @param array $menu
  238. * @return array
  239. */
  240. protected function getCreateMenu($menu)
  241. {
  242. $result = [];
  243. foreach ($menu as $k => & $v) {
  244. $arr = [
  245. 'name' => $v['name'],
  246. 'title' => $v['title'],
  247. ];
  248. if ($v['icon'] != 'fa fa-circle-o') {
  249. $arr['icon'] = $v['icon'];
  250. }
  251. if ($v['ismenu']) {
  252. $arr['ismenu'] = $v['ismenu'];
  253. }
  254. if (isset($v['childlist']) && $v['childlist']) {
  255. $arr['sublist'] = $this->getCreateMenu($v['childlist']);
  256. }
  257. $result[] = $arr;
  258. }
  259. return $result;
  260. }
  261. /**
  262. * 写入到文件
  263. * @param string $name
  264. * @param array $data
  265. * @param string $pathname
  266. * @return mixed
  267. */
  268. protected function writeToFile($name, $data, $pathname)
  269. {
  270. $search = $replace = [];
  271. foreach ($data as $k => $v) {
  272. $search[] = "{%{$k}%}";
  273. $replace[] = $v;
  274. }
  275. $stub = file_get_contents($this->getStub($name));
  276. $content = str_replace($search, $replace, $stub);
  277. if (!is_dir(dirname($pathname))) {
  278. mkdir(strtolower(dirname($pathname)), 0755, true);
  279. }
  280. return file_put_contents($pathname, $content);
  281. }
  282. /**
  283. * 获取基础模板
  284. * @param string $name
  285. * @return string
  286. */
  287. protected function getStub($name)
  288. {
  289. return __DIR__ . '/Addon/stubs/' . $name . '.stub';
  290. }
  291. }