Addon.php 13 KB

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