FeehiController.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. <?php
  2. /**
  3. * Author: lf
  4. * Blog: https://blog.feehi.com
  5. * Email: job@feehi.com
  6. * Created at: 2018-05-21 20:09
  7. */
  8. namespace console\controllers;
  9. use Yii;
  10. use FilesystemIterator;
  11. use common\helpers\Util;
  12. use common\models\Article;
  13. use common\models\ArticleContent;
  14. use common\models\FriendlyLink;
  15. use backend\models\form\RBACPermissionForm;
  16. use common\services\RBACServiceInterface;
  17. use console\helpers\FileHelper;
  18. use console\controllers\helpers\BackendAuth;
  19. use yii\base\Exception;
  20. use yii\console\Controller;
  21. use yii\helpers\ArrayHelper;
  22. use yii\helpers\Console;
  23. use yii\imagine\Image;
  24. use yii\web\Request;
  25. class FeehiController extends Controller
  26. {
  27. /**
  28. * @var string Directory where you want to generate RBAC Permission rules, often is backend/controllers
  29. */
  30. public $generateRBACControllerPath = "@backend/controllers";
  31. /**
  32. * @var array a list of $generateRBACControllerPath directory that to need generate controllers
  33. */
  34. public $noNeedRBACControllers = ['AssetsController', 'SiteController'];
  35. /**
  36. * Download FeehiCMS demo articles pics, videos.
  37. *
  38. * Execute `/path/to/yii feehi/download-upload-files` will download demo articles attachments(picture...)
  39. *
  40. * Do not forget config the correct frontend uri at ip:port/admin/index.php?r=setting/website
  41. */
  42. public function actionDownloadUploadFiles()
  43. {
  44. ini_set('memory_limit','1024M');
  45. ini_set('default_socket_timeout', 1);
  46. $uploadsZipUrl = "http://resource-1251086492.cossh.myqcloud.com/cms/uploads.zip";
  47. $zipPhpUrl = "http://resource-1251086492.cossh.myqcloud.com/cms/pclzip.lib.php";
  48. $runtime = Yii::getAlias("@frontend/runtime/");
  49. $this->stdout("downloading uploads.zip hold on please..." . PHP_EOL);
  50. $uploadsZip = $runtime . "uploads.zip";
  51. list($bin, $err) = FileHelper::request($uploadsZipUrl);
  52. if($err !== ""){
  53. $this->stdout("download uploads.zip failed " . $err . " please download yourself " . $uploadsZipUrl . PHP_EOL, Console::BG_RED);
  54. return 1;
  55. }
  56. $saveSuccess = file_put_contents($uploadsZip, $bin);
  57. if(!$saveSuccess) {
  58. $this->stdout("download uploads.zip success, save to hard disk failed please download yourself " . $uploadsZipUrl . PHP_EOL, Console::BG_RED);
  59. return 1;
  60. }
  61. $this->stdout("download uploads.zip finished" . PHP_EOL);
  62. $this->stdout("unzip uploads.zip hold on please..." . PHP_EOL);
  63. if( extension_loaded("zip") ){
  64. FileHelper::unzip($uploadsZip, Yii::getAlias("@frontend/web/uploads/"));
  65. }else {
  66. $this->stdout("prepare unzip environment..." . PHP_EOL);
  67. $zipphp = file_get_contents($zipPhpUrl);
  68. file_put_contents($runtime . 'zip.php', $zipphp);
  69. if (md5_file($runtime . 'zip.php') != "2334984a0c7c8bd0a05e3cea80b60aae") {
  70. $this->stdout("signature check failed, please download uploads.zip yourself " . $uploadsZipUrl . PHP_EOL, Console::BG_RED);
  71. @unlink($runtime . 'zip.php');
  72. return 1;
  73. }
  74. require_once $runtime . 'zip.php';
  75. $archive = new \PclZip($uploadsZip);
  76. if ($archive->extract(PCLZIP_OPT_PATH, Yii::getAlias("@frontend/web/uploads"), PCLZIP_OPT_REMOVE_PATH, 'install/release') == 0) {
  77. $this->stdout("get files failed,please download uploads.zip and place to frontend/web/uploads" . PHP_EOL);
  78. }
  79. @unlink($runtime . 'zip.php');
  80. }
  81. @unlink($uploadsZip);
  82. $this->stdout("unzip to " . Yii::getAlias("@frontend/web/uploads/") . "success" . PHP_EOL, Console::FG_GREEN);
  83. }
  84. /**
  85. * Generate RBAC permission items
  86. *
  87. * more info please visit http://doc.feehi.com/rbac.html
  88. */
  89. public function actionPermission()
  90. {
  91. Yii::$app->set("request", new Request());
  92. /** @var BackendAuth $backendAuth */
  93. $backendAuth = Yii::createObject([
  94. 'class' => BackendAuth::className(),
  95. ]);
  96. $backendAuth->setControllerPath($this->generateRBACControllerPath);
  97. $backendAuth->setNoNeedRBACControllers($this->noNeedRBACControllers);
  98. /** @var RBACServiceInterface $service */
  99. $service = Yii::$app->get(RBACServiceInterface::ServiceName);
  100. $authItems = $backendAuth->getAuthItems();
  101. $dbAuthItems = $backendAuth->getDbAuthItems();
  102. $needModifies = [];
  103. $needAdds = [];
  104. foreach ($authItems as $authItem){
  105. if( isset( $dbAuthItems[$authItem['name']] ) ){
  106. $data = json_decode($dbAuthItems[$authItem['name']]->data, true);
  107. if( !(
  108. $authItem['name'] === $dbAuthItems[$authItem['name']]->name
  109. && $authItem['description'] === $dbAuthItems[$authItem['name']]->description
  110. && $authItem['group'] === $data['group']
  111. && $authItem['category'] === $data['category']
  112. && $authItem['sort'] === $data['sort']
  113. ) ){
  114. $needModifies[] = $authItem;
  115. }
  116. }else{
  117. $needAdds[] = $authItem;
  118. }
  119. }
  120. $needRemoves = array_diff(array_keys($dbAuthItems) ,ArrayHelper::getColumn($authItems, 'name'));
  121. if( !empty($needAdds) ) {
  122. if (
  123. !$this->confirm("确定要增加下面这些规则吗?(surely add rules below?)" . PHP_EOL . implode(PHP_EOL, ArrayHelper::getColumn($needAdds, 'name')) . PHP_EOL, false)
  124. ) {
  125. $this->stdout("已取消增加(been canceled add)" . PHP_EOL, Console::FG_GREEN);
  126. } else {
  127. foreach ($needAdds as $k => $v) {
  128. $model = new RBACPermissionForm();
  129. $model->route = $v['route'];
  130. $model->method = $v['method'];
  131. $model->description = $v['description'];
  132. $model->group = $v['group'];
  133. $model->category = $v['category'];
  134. $model->sort = $v['sort'];
  135. $exits = Yii::$app->getAuthManager()->getPermission($model->route . ':' . $model->method);
  136. if (!$exits) {
  137. $postData = ["RBACPermissionForm" => $model->getAttributes()];
  138. $result=$service->createPermission($postData);
  139. if( $result !== true ){
  140. throw new Exception("save permission error" . print_r($result, true));
  141. }
  142. }
  143. $service->updatePermission($model->getName(), $model->getAttributes());
  144. }
  145. }
  146. }
  147. if( !empty($needModifies) ) {
  148. if (
  149. !$this->confirm("确定要修改下面这些规则吗?(surely update rules below?)" . PHP_EOL . implode(PHP_EOL, ArrayHelper::getColumn($needModifies, 'name')) . PHP_EOL, false)
  150. ) {
  151. $this->stdout("已取消修改(been canceled update)" . PHP_EOL, Console::FG_GREEN);
  152. } else {
  153. foreach ($needModifies as $k => $v) {
  154. $model = new RBACPermissionForm();
  155. $model->route = $v['route'];
  156. $model->method = $v['method'];
  157. $model->description = $v['description'];
  158. $model->group = $v['group'];
  159. $model->category = $v['category'];
  160. $model->sort = $v['sort'];
  161. $postData = ["RBACPermissionForm" => $model->getAttributes()];
  162. $result = $service->updatePermission($model->getName(), $postData);
  163. if( $result !== true ){
  164. throw new Exception("update permission error " . print_r($result, true));
  165. } }
  166. }
  167. }
  168. if( !empty($needRemoves) ) {
  169. if (
  170. !$this->confirm("确定要删除下面这些规则吗?(surely delete rules below?)" . PHP_EOL . implode(PHP_EOL, $needRemoves) . PHP_EOL, false)
  171. ) {
  172. $this->stdout("已取消删除(been canceled delete)" . PHP_EOL, Console::FG_GREEN);
  173. } else {
  174. foreach ($needRemoves as $k => $v) {
  175. $service->deletePermission($v);
  176. }
  177. }
  178. }
  179. $this->stdout("以下不受rbac权限控制的控制器(below controllers are not affected by rbac control)" . PHP_EOL);
  180. $this->stdout(implode(PHP_EOL, $backendAuth->getNoNeedRBACControllers()) . PHP_EOL . PHP_EOL, Console::FG_BLUE);
  181. $this->stdout("不受权限控制的路由(below routers are not affected by rbac control)" . PHP_EOL);
  182. $this->stdout(implode(PHP_EOL, $backendAuth->getNoNeedRBACRoutes()) . PHP_EOL, Console::FG_BLUE);
  183. }
  184. /**
  185. * Generate article different width thumbs(determined by Article::$thumbSizes)
  186. */
  187. public function actionGenArticleThumbnails()
  188. {
  189. $path = Yii::getAlias("@uploads/article/thumb/");
  190. $fp = opendir($path);
  191. while (($file = readdir($fp)) != false){
  192. if( $file == '.' || $file == '..' ) continue;
  193. $fullName = $path . $file;
  194. foreach (Article::$thumbSizes as $info){
  195. $thumbFullName = Util::getThumbName($fullName, $info['w'], $info['h']);
  196. $this->stdout(sprintf("generate %s width:%s height:%s\n", $fullName, $info['w'], $info['h']));
  197. Image::thumbnail($fullName, $info['w'], $info['h'])->save($thumbFullName);
  198. }
  199. }
  200. }
  201. /**
  202. * Delete empty directory or unused files.
  203. */
  204. public function actionDeleteUnusedFiles()
  205. {
  206. $rootPath = Yii::getAlias('@uploads');
  207. $articleThumbs = [];
  208. foreach (Article::find()->where(['<>', 'thumb', ''])->each(100) as $article) {
  209. $thumb = $rootPath . DIRECTORY_SEPARATOR . $article->thumb;
  210. $articleThumbs[] = $thumb;
  211. foreach (Article::$thumbSizes as $info){
  212. $articleThumbs[] = Util::getThumbName($thumb, $info['w'], $info['h']);
  213. }
  214. }
  215. $articleContent = [];
  216. foreach (ArticleContent::find()->where(['<>', 'content', ''])->each(100) as $content) {
  217. $content->content = str_replace(Yii::$app->params['site']['url'], Yii::$app->params['site']['sign'], $content->content);
  218. preg_match_all('/<img.*src="(' . Yii::$app->params['site']['sign'] . '.*)"/isU', $content->content, $matches);
  219. if (! empty($matches[1])) {
  220. foreach ($matches[1] as $pic) {
  221. $articleContent[] = str_replace(Yii::$app->params['site']['sign'], $rootPath, $pic);
  222. }
  223. }
  224. }
  225. $friendlyLinks = [];
  226. foreach (FriendlyLink::find()->where(['<>', 'image', ''])->each(100) as $link) {
  227. $friendlyLinks[] = $rootPath . $link->image;
  228. }
  229. $usingFiles = array_merge($articleThumbs, $articleContent, $friendlyLinks);
  230. $this->_deleteFileRecursive($rootPath, $usingFiles);
  231. }
  232. private function _deleteFileRecursive($directory, $usingFiles)
  233. {
  234. $it = new FileSystemIterator($directory);
  235. foreach ($it as $item) {
  236. if ($item->isDir()) {
  237. $nextLevelDir = $item->getPathName();
  238. if (count(scandir($nextLevelDir)) == 2) {
  239. if (@rmdir($nextLevelDir)) {
  240. $this->stdout("Delete directory " . $nextLevelDir . " success.\n", Console::FG_GREEN);
  241. } else {
  242. $this->stdout("Delete directory " . $nextLevelDir . " failed.\n", Console::FG_RED);
  243. }
  244. } else {
  245. $this->_deleteFileRecursive($nextLevelDir, $usingFiles);
  246. }
  247. } else {
  248. if (in_array($item->getFileName(), ['.gitignore', '.DS_Store'])) {
  249. continue;
  250. }
  251. $fullFileName = $directory . DIRECTORY_SEPARATOR . $item->getFileName();
  252. if (! in_array($fullFileName, $usingFiles)) {
  253. if (@unlink($fullFileName)) {
  254. $this->stdout("Delete file " . $fullFileName . " success.\n", Console::FG_GREEN);
  255. } else {
  256. $this->stdout("Delete file " . $fullFileName . " failed.\n", Console::FG_RED);
  257. }
  258. }
  259. }
  260. }
  261. }
  262. /**
  263. * Multi-language translate
  264. *
  265. * Use google translate auto generate multi-language
  266. * You should pass your own google API Key
  267. * Execute like `/path/to/yii feehi/translate apiKeyContent`
  268. *
  269. * @param string $apiKey
  270. * @throws \Exception
  271. */
  272. public function actionTranslate($apiKey="")
  273. {
  274. while( $apiKey == "" ){
  275. yii::$app->controller->stdout("Input your Google API Key :");
  276. $apiKey = trim(fgets(STDIN));
  277. }
  278. $url = "https://translation.googleapis.com/language/translate/v2?key=" . $apiKey;
  279. $dstLanguages = ["zh-TW", "fr", "ru", "es", "de", "it", "ja", "pt", "ko", "nl"];//繁体中文,法语,俄罗斯语,西班牙语,德语,意大利语,日语,葡萄牙,韩语,荷兰
  280. $translateItems = [
  281. [
  282. "messages" => array_flip(require Yii::getAlias("@yii/../../../backend/messages/en/menu.php")),
  283. "saveFile" => Yii::getAlias("@yii/../../../backend/messages/{language}/menu.php"),
  284. ],
  285. [
  286. "messages" => require Yii::getAlias("@yii/../../../backend/messages/zh/app.php"),
  287. "saveFile" => Yii::getAlias("@yii/../../../backend/messages/{language}/app.php"),
  288. ],
  289. [
  290. "messages" => require Yii::getAlias("@yii/../../../frontend/messages/zh/frontend.php"),
  291. "saveFile" => Yii::getAlias("@yii/../../../frontend/messages/{language}/frontend.php"),
  292. ],
  293. [
  294. "messages" => require Yii::getAlias("@yii/../../../install/messages/zh/install.php"),
  295. "saveFile" => Yii::getAlias("@yii/../../../install/messages/{language}/install.php"),
  296. ]
  297. ];
  298. foreach ($dstLanguages as $dstLanguage) {
  299. foreach ($translateItems as $item) {
  300. $fileName = str_replace("{language}", $dstLanguage, $item['saveFile']);
  301. $temp = pathinfo($fileName);
  302. if( !file_exists( $temp['dirname'] ) ){
  303. mkdir($temp['dirname']);
  304. }
  305. $fileResult = [];
  306. foreach ($item['messages'] as $english => $chinese) {
  307. $sourceLanguage = "en";
  308. $message = $english;
  309. if( $dstLanguage == "zh-TW" ){//to traditional chinese use simple chinese as source language
  310. $sourceLanguage = "zh";
  311. $message = $chinese;
  312. }
  313. $data = [
  314. 'q' => $message,
  315. 'source' => $sourceLanguage,
  316. 'target' => $dstLanguage,
  317. 'format' => 'text'
  318. ];
  319. $key = $english;
  320. if( $temp['basename'] == "menu.php" ) {//menu translate source language is simple chinese
  321. $key = $chinese;
  322. }
  323. $query = http_build_query($data);
  324. $res = FileHelper::request($url . "&" . $query);
  325. if (!isset($res[0])) {
  326. $this->stderr(print_r($res, true) . print_r($data, true));
  327. exit;
  328. }
  329. $response = json_decode($res[0], true);
  330. if (!isset($response['data']['translations'][0]['translatedText'])) {
  331. $this->stderr(print_r($response, true) . print_r($data, true));
  332. exit;
  333. }
  334. $dstMessage = $response['data']['translations'][0]['translatedText'];
  335. preg_match_all("/{.+}/isU", $key, $originMessageMatches);
  336. preg_match_all("/{.+}/isU", $dstMessage, $dstMessageMatches);
  337. if( count($originMessageMatches) != count($dstMessageMatches) ){
  338. $this->stderr("match {attribute} failed" . print_r($originMessageMatches, true) . print_r($dstMessageMatches, true));
  339. exit;
  340. }
  341. $dstMessage = str_replace($dstMessageMatches[0], $originMessageMatches[0], $dstMessage);
  342. $fileResult[$key] = $dstMessage;
  343. $this->stdout(sprintf("%s(%s) to %s(%s)\n", $message, $sourceLanguage, $dstMessage, $dstLanguage));
  344. }
  345. $this->stdout("write translate file " . $fileName . PHP_EOL, Console::FG_YELLOW);
  346. file_put_contents($fileName, "<?php \nreturn " . var_export($fileResult, true) . ";");
  347. }
  348. }
  349. }
  350. /**
  351. * Publish FeehiCMS
  352. */
  353. public function actionPublish()
  354. {
  355. $origin = Yii::getAlias('@console/../');
  356. $publishDir = $origin . '..' . DIRECTORY_SEPARATOR . 'publish';
  357. $temp = $publishDir . DIRECTORY_SEPARATOR;
  358. $needEmptyDirectories = [
  359. $temp . '.git',
  360. $temp . '.idea',
  361. $temp . 'tests',
  362. $temp . 'backend' . DIRECTORY_SEPARATOR . 'runtime',
  363. $temp . 'backend' . DIRECTORY_SEPARATOR . 'web' . DIRECTORY_SEPARATOR . 'assets',
  364. $temp . 'frontend' . DIRECTORY_SEPARATOR . 'runtime',
  365. $temp . 'console' . DIRECTORY_SEPARATOR . 'runtime',
  366. $temp . 'install' . DIRECTORY_SEPARATOR . 'runtime',
  367. $temp . 'frontend' . DIRECTORY_SEPARATOR . 'web' . DIRECTORY_SEPARATOR . 'assets',
  368. $temp . 'frontend' . DIRECTORY_SEPARATOR . 'web' . DIRECTORY_SEPARATOR . 'uploads',
  369. $temp . 'frontend' . DIRECTORY_SEPARATOR . 'web' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR . 'assets',
  370. $temp . 'frontend' . DIRECTORY_SEPARATOR . 'web' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR . 'uploads',
  371. $temp . 'api' . DIRECTORY_SEPARATOR . 'runtime',
  372. ];
  373. FileHelper::copyDirectory($origin, $publishDir);
  374. foreach ($needEmptyDirectories as $v) {
  375. FileHelper::removeDirectory($v);
  376. if($v == $temp . 'tests') continue;
  377. FileHelper::createDirectory($v, 0777);
  378. }
  379. FileHelper::removeDirectory($publishDir . DIRECTORY_SEPARATOR . '.git');
  380. FileHelper::removeDirectory($publishDir . DIRECTORY_SEPARATOR . '.idea');
  381. if( file_exists($publishDir . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'install.lock') ) unlink($publishDir . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'install.lock');
  382. file_put_contents($temp . 'common' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'main-local.php', "<?php return [];?>" );
  383. //passthru("composer archive --dir=$publishDir --format=zip");
  384. $this->stdout('Publish Success, files in ' . $publishDir . "\n", Console::FG_GREEN);
  385. }
  386. }