You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

753 lines
28KB

  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\console\controllers;
  8. use Yii;
  9. use yii\console\Exception;
  10. use yii\console\Controller;
  11. use yii\helpers\Console;
  12. use yii\helpers\VarDumper;
  13. use yii\web\AssetBundle;
  14. /**
  15. * Allows you to combine and compress your JavaScript and CSS files.
  16. *
  17. * Usage:
  18. *
  19. * 1. Create a configuration file using the `template` action:
  20. *
  21. * yii asset/template /path/to/myapp/config.php
  22. *
  23. * 2. Edit the created config file, adjusting it for your web application needs.
  24. * 3. Run the 'compress' action, using created config:
  25. *
  26. * yii asset /path/to/myapp/config.php /path/to/myapp/config/assets_compressed.php
  27. *
  28. * 4. Adjust your web application config to use compressed assets.
  29. *
  30. * Note: in the console environment some path aliases like `@webroot` and `@web` may not exist,
  31. * so corresponding paths inside the configuration should be specified directly.
  32. *
  33. * Note: by default this command relies on an external tools to perform actual files compression,
  34. * check [[jsCompressor]] and [[cssCompressor]] for more details.
  35. *
  36. * @property \yii\web\AssetManager $assetManager Asset manager instance. Note that the type of this property
  37. * differs in getter and setter. See [[getAssetManager()]] and [[setAssetManager()]] for details.
  38. *
  39. * @author Qiang Xue <qiang.xue@gmail.com>
  40. * @since 2.0
  41. */
  42. class AssetController extends Controller
  43. {
  44. /**
  45. * @var string controller default action ID.
  46. */
  47. public $defaultAction = 'compress';
  48. /**
  49. * @var array list of asset bundles to be compressed.
  50. */
  51. public $bundles = [];
  52. /**
  53. * @var array list of asset bundles, which represents output compressed files.
  54. * You can specify the name of the output compressed file using 'css' and 'js' keys:
  55. * For example:
  56. *
  57. * ~~~
  58. * 'app\config\AllAsset' => [
  59. * 'js' => 'js/all-{hash}.js',
  60. * 'css' => 'css/all-{hash}.css',
  61. * 'depends' => [ ... ],
  62. * ]
  63. * ~~~
  64. *
  65. * File names can contain placeholder "{hash}", which will be filled by the hash of the resulting file.
  66. *
  67. * You may specify several target bundles in order to compress different groups of assets.
  68. * In this case you should use 'depends' key to specify, which bundles should be covered with particular
  69. * target bundle. You may leave 'depends' to be empty for single bundle, which will compress all remaining
  70. * bundles in this case.
  71. * For example:
  72. *
  73. * ~~~
  74. * 'allShared' => [
  75. * 'js' => 'js/all-shared-{hash}.js',
  76. * 'css' => 'css/all-shared-{hash}.css',
  77. * 'depends' => [
  78. * // Include all assets shared between 'backend' and 'frontend'
  79. * 'yii\web\YiiAsset',
  80. * 'app\assets\SharedAsset',
  81. * ],
  82. * ],
  83. * 'allBackEnd' => [
  84. * 'js' => 'js/all-{hash}.js',
  85. * 'css' => 'css/all-{hash}.css',
  86. * 'depends' => [
  87. * // Include only 'backend' assets:
  88. * 'app\assets\AdminAsset'
  89. * ],
  90. * ],
  91. * 'allFrontEnd' => [
  92. * 'js' => 'js/all-{hash}.js',
  93. * 'css' => 'css/all-{hash}.css',
  94. * 'depends' => [], // Include all remaining assets
  95. * ],
  96. * ~~~
  97. */
  98. public $targets = [];
  99. /**
  100. * @var string|callable JavaScript file compressor.
  101. * If a string, it is treated as shell command template, which should contain
  102. * placeholders {from} - source file name - and {to} - output file name.
  103. * Otherwise, it is treated as PHP callback, which should perform the compression.
  104. *
  105. * Default value relies on usage of "Closure Compiler"
  106. * @see https://developers.google.com/closure/compiler/
  107. */
  108. public $jsCompressor = 'java -jar compiler.jar --js {from} --js_output_file {to}';
  109. /**
  110. * @var string|callable CSS file compressor.
  111. * If a string, it is treated as shell command template, which should contain
  112. * placeholders {from} - source file name - and {to} - output file name.
  113. * Otherwise, it is treated as PHP callback, which should perform the compression.
  114. *
  115. * Default value relies on usage of "YUI Compressor"
  116. * @see https://github.com/yui/yuicompressor/
  117. */
  118. public $cssCompressor = 'java -jar yuicompressor.jar --type css {from} -o {to}';
  119. /**
  120. * @var array|\yii\web\AssetManager [[\yii\web\AssetManager]] instance or its array configuration, which will be used
  121. * for assets processing.
  122. */
  123. private $_assetManager = [];
  124. /**
  125. * Returns the asset manager instance.
  126. * @throws \yii\console\Exception on invalid configuration.
  127. * @return \yii\web\AssetManager asset manager instance.
  128. */
  129. public function getAssetManager()
  130. {
  131. if (!is_object($this->_assetManager)) {
  132. $options = $this->_assetManager;
  133. if (!isset($options['class'])) {
  134. $options['class'] = 'yii\\web\\AssetManager';
  135. }
  136. if (!isset($options['basePath'])) {
  137. throw new Exception("Please specify 'basePath' for the 'assetManager' option.");
  138. }
  139. if (!isset($options['baseUrl'])) {
  140. throw new Exception("Please specify 'baseUrl' for the 'assetManager' option.");
  141. }
  142. $this->_assetManager = Yii::createObject($options);
  143. }
  144. return $this->_assetManager;
  145. }
  146. /**
  147. * Sets asset manager instance or configuration.
  148. * @param \yii\web\AssetManager|array $assetManager asset manager instance or its array configuration.
  149. * @throws \yii\console\Exception on invalid argument type.
  150. */
  151. public function setAssetManager($assetManager)
  152. {
  153. if (is_scalar($assetManager)) {
  154. throw new Exception('"' . get_class($this) . '::assetManager" should be either object or array - "' . gettype($assetManager) . '" given.');
  155. }
  156. $this->_assetManager = $assetManager;
  157. }
  158. /**
  159. * Combines and compresses the asset files according to the given configuration.
  160. * During the process new asset bundle configuration file will be created.
  161. * You should replace your original asset bundle configuration with this file in order to use compressed files.
  162. * @param string $configFile configuration file name.
  163. * @param string $bundleFile output asset bundles configuration file name.
  164. */
  165. public function actionCompress($configFile, $bundleFile)
  166. {
  167. $this->loadConfiguration($configFile);
  168. $bundles = $this->loadBundles($this->bundles);
  169. $targets = $this->loadTargets($this->targets, $bundles);
  170. foreach ($targets as $name => $target) {
  171. $this->stdout("Creating output bundle '{$name}':\n");
  172. if (!empty($target->js)) {
  173. $this->buildTarget($target, 'js', $bundles);
  174. }
  175. if (!empty($target->css)) {
  176. $this->buildTarget($target, 'css', $bundles);
  177. }
  178. $this->stdout("\n");
  179. }
  180. $targets = $this->adjustDependency($targets, $bundles);
  181. $this->saveTargets($targets, $bundleFile);
  182. }
  183. /**
  184. * Applies configuration from the given file to self instance.
  185. * @param string $configFile configuration file name.
  186. * @throws \yii\console\Exception on failure.
  187. */
  188. protected function loadConfiguration($configFile)
  189. {
  190. $this->stdout("Loading configuration from '{$configFile}'...\n");
  191. foreach (require($configFile) as $name => $value) {
  192. if (property_exists($this, $name) || $this->canSetProperty($name)) {
  193. $this->$name = $value;
  194. } else {
  195. throw new Exception("Unknown configuration option: $name");
  196. }
  197. }
  198. $this->getAssetManager(); // check if asset manager configuration is correct
  199. }
  200. /**
  201. * Creates full list of source asset bundles.
  202. * @param string[] $bundles list of asset bundle names
  203. * @return \yii\web\AssetBundle[] list of source asset bundles.
  204. */
  205. protected function loadBundles($bundles)
  206. {
  207. $this->stdout("Collecting source bundles information...\n");
  208. $am = $this->getAssetManager();
  209. $result = [];
  210. foreach ($bundles as $name) {
  211. $result[$name] = $am->getBundle($name);
  212. }
  213. foreach ($result as $bundle) {
  214. $this->loadDependency($bundle, $result);
  215. }
  216. return $result;
  217. }
  218. /**
  219. * Loads asset bundle dependencies recursively.
  220. * @param \yii\web\AssetBundle $bundle bundle instance
  221. * @param array $result already loaded bundles list.
  222. * @throws Exception on failure.
  223. */
  224. protected function loadDependency($bundle, &$result)
  225. {
  226. $am = $this->getAssetManager();
  227. foreach ($bundle->depends as $name) {
  228. if (!isset($result[$name])) {
  229. $dependencyBundle = $am->getBundle($name);
  230. $result[$name] = false;
  231. $this->loadDependency($dependencyBundle, $result);
  232. $result[$name] = $dependencyBundle;
  233. } elseif ($result[$name] === false) {
  234. throw new Exception("A circular dependency is detected for bundle '$name'.");
  235. }
  236. }
  237. }
  238. /**
  239. * Creates full list of output asset bundles.
  240. * @param array $targets output asset bundles configuration.
  241. * @param \yii\web\AssetBundle[] $bundles list of source asset bundles.
  242. * @return \yii\web\AssetBundle[] list of output asset bundles.
  243. * @throws Exception on failure.
  244. */
  245. protected function loadTargets($targets, $bundles)
  246. {
  247. // build the dependency order of bundles
  248. $registered = [];
  249. foreach ($bundles as $name => $bundle) {
  250. $this->registerBundle($bundles, $name, $registered);
  251. }
  252. $bundleOrders = array_combine(array_keys($registered), range(0, count($bundles) - 1));
  253. // fill up the target which has empty 'depends'.
  254. $referenced = [];
  255. foreach ($targets as $name => $target) {
  256. if (empty($target['depends'])) {
  257. if (!isset($all)) {
  258. $all = $name;
  259. } else {
  260. throw new Exception("Only one target can have empty 'depends' option. Found two now: $all, $name");
  261. }
  262. } else {
  263. foreach ($target['depends'] as $bundle) {
  264. if (!isset($referenced[$bundle])) {
  265. $referenced[$bundle] = $name;
  266. } else {
  267. throw new Exception("Target '{$referenced[$bundle]}' and '$name' cannot contain the bundle '$bundle' at the same time.");
  268. }
  269. }
  270. }
  271. }
  272. if (isset($all)) {
  273. $targets[$all]['depends'] = array_diff(array_keys($registered), array_keys($referenced));
  274. }
  275. // adjust the 'depends' order for each target according to the dependency order of bundles
  276. // create an AssetBundle object for each target
  277. foreach ($targets as $name => $target) {
  278. if (!isset($target['basePath'])) {
  279. throw new Exception("Please specify 'basePath' for the '$name' target.");
  280. }
  281. if (!isset($target['baseUrl'])) {
  282. throw new Exception("Please specify 'baseUrl' for the '$name' target.");
  283. }
  284. usort($target['depends'], function ($a, $b) use ($bundleOrders) {
  285. if ($bundleOrders[$a] == $bundleOrders[$b]) {
  286. return 0;
  287. } else {
  288. return $bundleOrders[$a] > $bundleOrders[$b] ? 1 : -1;
  289. }
  290. });
  291. if (!isset($target['class'])) {
  292. $target['class'] = $name;
  293. }
  294. $targets[$name] = Yii::createObject($target);
  295. }
  296. return $targets;
  297. }
  298. /**
  299. * Builds output asset bundle.
  300. * @param \yii\web\AssetBundle $target output asset bundle
  301. * @param string $type either 'js' or 'css'.
  302. * @param \yii\web\AssetBundle[] $bundles source asset bundles.
  303. * @throws Exception on failure.
  304. */
  305. protected function buildTarget($target, $type, $bundles)
  306. {
  307. $tempFile = $target->basePath . '/' . strtr($target->$type, ['{hash}' => 'temp']);
  308. $inputFiles = [];
  309. foreach ($target->depends as $name) {
  310. if (isset($bundles[$name])) {
  311. if (!$this->isBundleExternal($bundles[$name])) {
  312. foreach ($bundles[$name]->$type as $file) {
  313. $inputFiles[] = $bundles[$name]->basePath . '/' . $file;
  314. }
  315. }
  316. } else {
  317. throw new Exception("Unknown bundle: '{$name}'");
  318. }
  319. }
  320. if ($type === 'js') {
  321. $this->compressJsFiles($inputFiles, $tempFile);
  322. } else {
  323. $this->compressCssFiles($inputFiles, $tempFile);
  324. }
  325. $targetFile = strtr($target->$type, ['{hash}' => md5_file($tempFile)]);
  326. $outputFile = $target->basePath . '/' . $targetFile;
  327. rename($tempFile, $outputFile);
  328. $target->$type = [$targetFile];
  329. }
  330. /**
  331. * Adjust dependencies between asset bundles in the way source bundles begin to depend on output ones.
  332. * @param \yii\web\AssetBundle[] $targets output asset bundles.
  333. * @param \yii\web\AssetBundle[] $bundles source asset bundles.
  334. * @return \yii\web\AssetBundle[] output asset bundles.
  335. */
  336. protected function adjustDependency($targets, $bundles)
  337. {
  338. $this->stdout("Creating new bundle configuration...\n");
  339. $map = [];
  340. foreach ($targets as $name => $target) {
  341. foreach ($target->depends as $bundle) {
  342. $map[$bundle] = $name;
  343. }
  344. }
  345. foreach ($targets as $name => $target) {
  346. $depends = [];
  347. foreach ($target->depends as $bn) {
  348. foreach ($bundles[$bn]->depends as $bundle) {
  349. $depends[$map[$bundle]] = true;
  350. }
  351. }
  352. unset($depends[$name]);
  353. $target->depends = array_keys($depends);
  354. }
  355. // detect possible circular dependencies
  356. foreach ($targets as $name => $target) {
  357. $registered = [];
  358. $this->registerBundle($targets, $name, $registered);
  359. }
  360. foreach ($map as $bundle => $target) {
  361. $sourceBundle = $bundles[$bundle];
  362. $depends = $sourceBundle->depends;
  363. if (!$this->isBundleExternal($sourceBundle)) {
  364. $depends[] = $target;
  365. }
  366. $targets[$bundle] = Yii::createObject([
  367. 'class' => strpos($bundle, '\\') !== false ? $bundle : 'yii\\web\\AssetBundle',
  368. 'depends' => $depends,
  369. ]);
  370. }
  371. return $targets;
  372. }
  373. /**
  374. * Registers asset bundles including their dependencies.
  375. * @param \yii\web\AssetBundle[] $bundles asset bundles list.
  376. * @param string $name bundle name.
  377. * @param array $registered stores already registered names.
  378. * @throws Exception if circular dependency is detected.
  379. */
  380. protected function registerBundle($bundles, $name, &$registered)
  381. {
  382. if (!isset($registered[$name])) {
  383. $registered[$name] = false;
  384. $bundle = $bundles[$name];
  385. foreach ($bundle->depends as $depend) {
  386. $this->registerBundle($bundles, $depend, $registered);
  387. }
  388. unset($registered[$name]);
  389. $registered[$name] = true;
  390. } elseif ($registered[$name] === false) {
  391. throw new Exception("A circular dependency is detected for target '$name'.");
  392. }
  393. }
  394. /**
  395. * Saves new asset bundles configuration.
  396. * @param \yii\web\AssetBundle[] $targets list of asset bundles to be saved.
  397. * @param string $bundleFile output file name.
  398. * @throws \yii\console\Exception on failure.
  399. */
  400. protected function saveTargets($targets, $bundleFile)
  401. {
  402. $array = [];
  403. foreach ($targets as $name => $target) {
  404. if (isset($this->targets[$name])) {
  405. $array[$name] = [
  406. 'class' => get_class($target),
  407. 'basePath' => $this->targets[$name]['basePath'],
  408. 'baseUrl' => $this->targets[$name]['baseUrl'],
  409. 'js' => $target->js,
  410. 'css' => $target->css,
  411. ];
  412. } else {
  413. if ($this->isBundleExternal($target)) {
  414. $array[$name] = $this->composeBundleConfig($target);
  415. } else {
  416. $array[$name] = [
  417. 'sourcePath' => null,
  418. 'js' => [],
  419. 'css' => [],
  420. 'depends' => $target->depends,
  421. ];
  422. }
  423. }
  424. }
  425. $array = VarDumper::export($array);
  426. $version = date('Y-m-d H:i:s', time());
  427. $bundleFileContent = <<<EOD
  428. <?php
  429. /**
  430. * This file is generated by the "yii {$this->id}" command.
  431. * DO NOT MODIFY THIS FILE DIRECTLY.
  432. * @version {$version}
  433. */
  434. return {$array};
  435. EOD;
  436. if (!file_put_contents($bundleFile, $bundleFileContent)) {
  437. throw new Exception("Unable to write output bundle configuration at '{$bundleFile}'.");
  438. }
  439. $this->stdout("Output bundle configuration created at '{$bundleFile}'.\n", Console::FG_GREEN);
  440. }
  441. /**
  442. * Compresses given JavaScript files and combines them into the single one.
  443. * @param array $inputFiles list of source file names.
  444. * @param string $outputFile output file name.
  445. * @throws \yii\console\Exception on failure
  446. */
  447. protected function compressJsFiles($inputFiles, $outputFile)
  448. {
  449. if (empty($inputFiles)) {
  450. return;
  451. }
  452. $this->stdout(" Compressing JavaScript files...\n");
  453. if (is_string($this->jsCompressor)) {
  454. $tmpFile = $outputFile . '.tmp';
  455. $this->combineJsFiles($inputFiles, $tmpFile);
  456. $this->stdout(shell_exec(strtr($this->jsCompressor, [
  457. '{from}' => escapeshellarg($tmpFile),
  458. '{to}' => escapeshellarg($outputFile),
  459. ])));
  460. @unlink($tmpFile);
  461. } else {
  462. call_user_func($this->jsCompressor, $this, $inputFiles, $outputFile);
  463. }
  464. if (!file_exists($outputFile)) {
  465. throw new Exception("Unable to compress JavaScript files into '{$outputFile}'.");
  466. }
  467. $this->stdout(" JavaScript files compressed into '{$outputFile}'.\n");
  468. }
  469. /**
  470. * Compresses given CSS files and combines them into the single one.
  471. * @param array $inputFiles list of source file names.
  472. * @param string $outputFile output file name.
  473. * @throws \yii\console\Exception on failure
  474. */
  475. protected function compressCssFiles($inputFiles, $outputFile)
  476. {
  477. if (empty($inputFiles)) {
  478. return;
  479. }
  480. $this->stdout(" Compressing CSS files...\n");
  481. if (is_string($this->cssCompressor)) {
  482. $tmpFile = $outputFile . '.tmp';
  483. $this->combineCssFiles($inputFiles, $tmpFile);
  484. $this->stdout(shell_exec(strtr($this->cssCompressor, [
  485. '{from}' => escapeshellarg($tmpFile),
  486. '{to}' => escapeshellarg($outputFile),
  487. ])));
  488. @unlink($tmpFile);
  489. } else {
  490. call_user_func($this->cssCompressor, $this, $inputFiles, $outputFile);
  491. }
  492. if (!file_exists($outputFile)) {
  493. throw new Exception("Unable to compress CSS files into '{$outputFile}'.");
  494. }
  495. $this->stdout(" CSS files compressed into '{$outputFile}'.\n");
  496. }
  497. /**
  498. * Combines JavaScript files into a single one.
  499. * @param array $inputFiles source file names.
  500. * @param string $outputFile output file name.
  501. * @throws \yii\console\Exception on failure.
  502. */
  503. public function combineJsFiles($inputFiles, $outputFile)
  504. {
  505. $content = '';
  506. foreach ($inputFiles as $file) {
  507. $content .= "/*** BEGIN FILE: $file ***/\n"
  508. . file_get_contents($file)
  509. . "/*** END FILE: $file ***/\n";
  510. }
  511. if (!file_put_contents($outputFile, $content)) {
  512. throw new Exception("Unable to write output JavaScript file '{$outputFile}'.");
  513. }
  514. }
  515. /**
  516. * Combines CSS files into a single one.
  517. * @param array $inputFiles source file names.
  518. * @param string $outputFile output file name.
  519. * @throws \yii\console\Exception on failure.
  520. */
  521. public function combineCssFiles($inputFiles, $outputFile)
  522. {
  523. $content = '';
  524. $outputFilePath = dirname($this->findRealPath($outputFile));
  525. foreach ($inputFiles as $file) {
  526. $content .= "/*** BEGIN FILE: $file ***/\n"
  527. . $this->adjustCssUrl(file_get_contents($file), dirname($this->findRealPath($file)), $outputFilePath)
  528. . "/*** END FILE: $file ***/\n";
  529. }
  530. if (!file_put_contents($outputFile, $content)) {
  531. throw new Exception("Unable to write output CSS file '{$outputFile}'.");
  532. }
  533. }
  534. /**
  535. * Adjusts CSS content allowing URL references pointing to the original resources.
  536. * @param string $cssContent source CSS content.
  537. * @param string $inputFilePath input CSS file name.
  538. * @param string $outputFilePath output CSS file name.
  539. * @return string adjusted CSS content.
  540. */
  541. protected function adjustCssUrl($cssContent, $inputFilePath, $outputFilePath)
  542. {
  543. $inputFilePath = str_replace('\\', '/', $inputFilePath);
  544. $outputFilePath = str_replace('\\', '/', $outputFilePath);
  545. $sharedPathParts = [];
  546. $inputFilePathParts = explode('/', $inputFilePath);
  547. $inputFilePathPartsCount = count($inputFilePathParts);
  548. $outputFilePathParts = explode('/', $outputFilePath);
  549. $outputFilePathPartsCount = count($outputFilePathParts);
  550. for ($i =0; $i < $inputFilePathPartsCount && $i < $outputFilePathPartsCount; $i++) {
  551. if ($inputFilePathParts[$i] == $outputFilePathParts[$i]) {
  552. $sharedPathParts[] = $inputFilePathParts[$i];
  553. } else {
  554. break;
  555. }
  556. }
  557. $sharedPath = implode('/', $sharedPathParts);
  558. $inputFileRelativePath = trim(str_replace($sharedPath, '', $inputFilePath), '/');
  559. $outputFileRelativePath = trim(str_replace($sharedPath, '', $outputFilePath), '/');
  560. if (empty($inputFileRelativePath)) {
  561. $inputFileRelativePathParts = [];
  562. } else {
  563. $inputFileRelativePathParts = explode('/', $inputFileRelativePath);
  564. }
  565. if (empty($outputFileRelativePath)) {
  566. $outputFileRelativePathParts = [];
  567. } else {
  568. $outputFileRelativePathParts = explode('/', $outputFileRelativePath);
  569. }
  570. $callback = function ($matches) use ($inputFileRelativePathParts, $outputFileRelativePathParts) {
  571. $fullMatch = $matches[0];
  572. $inputUrl = $matches[1];
  573. if (strpos($inputUrl, '/') === 0 || preg_match('/^https?:\/\//is', $inputUrl) || preg_match('/^data:/is', $inputUrl)) {
  574. return $fullMatch;
  575. }
  576. if ($inputFileRelativePathParts === $outputFileRelativePathParts) {
  577. return $fullMatch;
  578. }
  579. if (empty($outputFileRelativePathParts)) {
  580. $outputUrlParts = [];
  581. } else {
  582. $outputUrlParts = array_fill(0, count($outputFileRelativePathParts), '..');
  583. }
  584. $outputUrlParts = array_merge($outputUrlParts, $inputFileRelativePathParts);
  585. if (strpos($inputUrl, '/') !== false) {
  586. $inputUrlParts = explode('/', $inputUrl);
  587. foreach ($inputUrlParts as $key => $inputUrlPart) {
  588. if ($inputUrlPart == '..') {
  589. array_pop($outputUrlParts);
  590. unset($inputUrlParts[$key]);
  591. }
  592. }
  593. $outputUrlParts[] = implode('/', $inputUrlParts);
  594. } else {
  595. $outputUrlParts[] = $inputUrl;
  596. }
  597. $outputUrl = implode('/', $outputUrlParts);
  598. return str_replace($inputUrl, $outputUrl, $fullMatch);
  599. };
  600. $cssContent = preg_replace_callback('/url\(["\']?([^)^"^\']*)["\']?\)/is', $callback, $cssContent);
  601. return $cssContent;
  602. }
  603. /**
  604. * Creates template of configuration file for [[actionCompress]].
  605. * @param string $configFile output file name.
  606. * @return integer CLI exit code
  607. * @throws \yii\console\Exception on failure.
  608. */
  609. public function actionTemplate($configFile)
  610. {
  611. $jsCompressor = VarDumper::export($this->jsCompressor);
  612. $cssCompressor = VarDumper::export($this->cssCompressor);
  613. $template = <<<EOD
  614. <?php
  615. /**
  616. * Configuration file for the "yii asset" console command.
  617. */
  618. // In the console environment, some path aliases may not exist. Please define these:
  619. // Yii::setAlias('@webroot', __DIR__ . '/../web');
  620. // Yii::setAlias('@web', '/');
  621. return [
  622. // Adjust command/callback for JavaScript files compressing:
  623. 'jsCompressor' => {$jsCompressor},
  624. // Adjust command/callback for CSS files compressing:
  625. 'cssCompressor' => {$cssCompressor},
  626. // The list of asset bundles to compress:
  627. 'bundles' => [
  628. // 'app\assets\AppAsset',
  629. // 'yii\web\YiiAsset',
  630. // 'yii\web\JqueryAsset',
  631. ],
  632. // Asset bundle for compression output:
  633. 'targets' => [
  634. 'all' => [
  635. 'class' => 'yii\web\AssetBundle',
  636. 'basePath' => '@webroot/assets',
  637. 'baseUrl' => '@web/assets',
  638. 'js' => 'js/all-{hash}.js',
  639. 'css' => 'css/all-{hash}.css',
  640. ],
  641. ],
  642. // Asset manager configuration:
  643. 'assetManager' => [
  644. //'basePath' => '@webroot/assets',
  645. //'baseUrl' => '@web/assets',
  646. ],
  647. ];
  648. EOD;
  649. if (file_exists($configFile)) {
  650. if (!$this->confirm("File '{$configFile}' already exists. Do you wish to overwrite it?")) {
  651. return self::EXIT_CODE_NORMAL;
  652. }
  653. }
  654. if (!file_put_contents($configFile, $template)) {
  655. throw new Exception("Unable to write template file '{$configFile}'.");
  656. } else {
  657. $this->stdout("Configuration file template created at '{$configFile}'.\n\n", Console::FG_GREEN);
  658. return self::EXIT_CODE_NORMAL;
  659. }
  660. }
  661. /**
  662. * Returns canonicalized absolute pathname.
  663. * Unlike regular `realpath()` this method does not expand symlinks and does not check path existence.
  664. * @param string $path raw path
  665. * @return string canonicalized absolute pathname
  666. */
  667. private function findRealPath($path)
  668. {
  669. $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
  670. $pathParts = explode(DIRECTORY_SEPARATOR, $path);
  671. $realPathParts = [];
  672. foreach ($pathParts as $pathPart) {
  673. if ($pathPart === '..') {
  674. array_pop($realPathParts);
  675. } else {
  676. array_push($realPathParts, $pathPart);
  677. }
  678. }
  679. return implode(DIRECTORY_SEPARATOR, $realPathParts);
  680. }
  681. /**
  682. * @param AssetBundle $bundle
  683. * @return boolean whether asset bundle external or not.
  684. */
  685. private function isBundleExternal($bundle)
  686. {
  687. return (empty($bundle->sourcePath) && empty($bundle->basePath));
  688. }
  689. /**
  690. * @param AssetBundle $bundle asset bundle instance.
  691. * @return array bundle configuration.
  692. */
  693. private function composeBundleConfig($bundle)
  694. {
  695. $config = Yii::getObjectVars($bundle);
  696. $config['class'] = get_class($bundle);
  697. return $config;
  698. }
  699. }