Addon.php 13 KB

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