PageRenderTime 60ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/src/View/Helper/AssetCompressHelper.php

http://github.com/markstory/asset_compress
PHP | 422 lines | 201 code | 49 blank | 172 comment | 24 complexity | 47cc81f50666a74240490ab546017d0e MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. declare(strict_types=1);
  3. namespace AssetCompress\View\Helper;
  4. use AssetCompress\Config\ConfigFinder;
  5. use AssetCompress\Factory;
  6. use Cake\Core\Configure;
  7. use Cake\Core\Plugin;
  8. use Cake\Routing\Router;
  9. use Cake\Utility\Inflector;
  10. use Cake\View\Helper;
  11. use Cake\View\View;
  12. use MiniAsset\AssetTarget;
  13. use RuntimeException;
  14. /**
  15. * AssetCompress Helper.
  16. *
  17. * Handle inclusion assets using the AssetCompress features for concatenating and
  18. * compressing asset files.
  19. *
  20. * @property \Cake\View\Helper\HtmlHelper $Html
  21. */
  22. class AssetCompressHelper extends Helper
  23. {
  24. /**
  25. * Helpers used.
  26. *
  27. * @var array
  28. */
  29. public $helpers = ['Html'];
  30. /**
  31. * Configuration object
  32. *
  33. * @var \MiniAsset\AssetConfig
  34. */
  35. protected $config;
  36. /**
  37. * Factory for other AssetCompress objects.
  38. *
  39. * @var \AssetCompress\Factory
  40. */
  41. protected $factory;
  42. /**
  43. * AssetCollection for the current config set.
  44. *
  45. * @var \MiniAsset\AssetCollection
  46. */
  47. protected $collection;
  48. /**
  49. * AssetWriter instance
  50. *
  51. * @var \MiniAsset\Output\AssetWriter
  52. */
  53. protected $writer;
  54. /**
  55. * Constructor - finds and parses the ini file the plugin uses.
  56. *
  57. * @param \Cake\View\View $view The view instance to use.
  58. * @param array $settings The settings for the helper.
  59. * @return void
  60. */
  61. public function __construct(View $view, $settings = [])
  62. {
  63. parent::__construct($view, $settings);
  64. if (empty($settings['noconfig'])) {
  65. $configFinder = new ConfigFinder();
  66. $this->assetConfig($configFinder->loadAll());
  67. }
  68. }
  69. /**
  70. * Modify the runtime configuration of the helper.
  71. * Used as a get/set for the ini file values.
  72. *
  73. * @param \MiniAsset\AssetConfig $config The config instance to set.
  74. * @return \MiniAsset\AssetConfig|null Either the current config object or null.
  75. */
  76. public function assetConfig($config = null)
  77. {
  78. if ($config === null) {
  79. return $this->config;
  80. }
  81. $this->config = $config;
  82. return null;
  83. }
  84. /**
  85. * Get the AssetCompress factory based on the config object.
  86. *
  87. * @return \AssetCompress\Factory
  88. */
  89. protected function factory()
  90. {
  91. if (empty($this->factory)) {
  92. $this->config->theme($this->getView()->getTheme());
  93. $this->factory = new Factory($this->config);
  94. }
  95. return $this->factory;
  96. }
  97. /**
  98. * Get the AssetCollection
  99. *
  100. * @return \MiniAsset\AssetCollection
  101. */
  102. protected function collection()
  103. {
  104. if (empty($this->collection)) {
  105. $this->collection = $this->factory()->assetCollection();
  106. }
  107. return $this->collection;
  108. }
  109. /**
  110. * Get the AssetWriter
  111. *
  112. * @return \MiniAsset\Output\AssetWriter
  113. */
  114. protected function writer()
  115. {
  116. if (empty($this->writer)) {
  117. $this->writer = $this->factory()->writer();
  118. }
  119. return $this->writer;
  120. }
  121. /**
  122. * Adds an extension if the file doesn't already end with it.
  123. *
  124. * @param string $file Filename
  125. * @param string $ext Extension with .
  126. * @return string
  127. */
  128. protected function _addExt($file, $ext)
  129. {
  130. if (substr($file, strlen($ext) * -1) !== $ext) {
  131. $file .= $ext;
  132. }
  133. return $file;
  134. }
  135. /**
  136. * Create a CSS file. Will generate link tags
  137. * for either the dynamic build controller, or the generated file if it exists.
  138. *
  139. * To create build files without configuration use addCss()
  140. *
  141. * Options:
  142. *
  143. * - All options supported by HtmlHelper::css() are supported.
  144. * - `raw` - Set to true to get one link element for each file in the build.
  145. *
  146. * @param string $file A build target to include.
  147. * @param array $options An array of options for the stylesheet tag.
  148. * @throws \RuntimeException
  149. * @return string A stylesheet tag
  150. */
  151. public function css($file, $options = [])
  152. {
  153. $file = $this->_addExt($file, '.css');
  154. if (!$this->collection()->contains($file)) {
  155. throw new RuntimeException(
  156. "Cannot create a stylesheet tag for a '$file'. That build is not defined."
  157. );
  158. }
  159. $output = '';
  160. if (!empty($options['raw'])) {
  161. unset($options['raw']);
  162. $target = $this->collection()->get($file);
  163. foreach ($target->files() as $part) {
  164. $path = $this->_relativizePath($part->path());
  165. $path = str_replace(DS, '/', $path);
  166. $output .= $this->Html->css($path, $options);
  167. }
  168. return $output;
  169. }
  170. $url = $this->url($file, $options);
  171. unset($options['full']);
  172. return $this->Html->css($url, $options);
  173. }
  174. /**
  175. * Create a script tag for a script asset. Will generate script tags
  176. * for either the dynamic build controller, or the generated file if it exists.
  177. *
  178. * To create build files without configuration use addScript()
  179. *
  180. * Options:
  181. *
  182. * - All options supported by HtmlHelper::css() are supported.
  183. * - `raw` - Set to true to get one script element for each file in the build.
  184. *
  185. * @param string $file A build target to include.
  186. * @param array $options An array of options for the script tag.
  187. * @throws \RuntimeException
  188. * @return string A script tag
  189. */
  190. public function script($file, $options = [])
  191. {
  192. $file = $this->_addExt($file, '.js');
  193. if (!$this->collection()->contains($file)) {
  194. throw new RuntimeException(
  195. "Cannot create a script tag for a '$file'. That build is not defined."
  196. );
  197. }
  198. $output = '';
  199. if (!empty($options['raw'])) {
  200. unset($options['raw']);
  201. $target = $this->collection()->get($file);
  202. foreach ($target->files() as $part) {
  203. $path = $this->_relativizePath($part->path());
  204. $path = str_replace(DS, '/', $path);
  205. $output .= $this->Html->script($path, $options);
  206. }
  207. return $output;
  208. }
  209. $url = $this->url($file, $options);
  210. unset($options['full']);
  211. return $this->Html->script($url, $options);
  212. }
  213. /**
  214. * Converts an absolute path into a web relative one.
  215. *
  216. * @param string $path The path to convert
  217. * @return string A webroot relative string.
  218. */
  219. protected function _relativizePath($path)
  220. {
  221. $plugins = Plugin::loaded();
  222. $index = array_search('AssetCompress', $plugins);
  223. unset($plugins[$index]);
  224. foreach ($plugins as $plugin) {
  225. $pluginPath = Plugin::path($plugin) . 'webroot';
  226. if (strpos($path, $pluginPath) === 0) {
  227. return str_replace($pluginPath, '/' . Inflector::underscore($plugin), $path);
  228. }
  229. }
  230. $path = str_replace(WWW_ROOT, '/', $path);
  231. return str_replace(DS, '/', $path);
  232. }
  233. /**
  234. * Get the URL for a given asset name.
  235. *
  236. * Takes an build filename, and returns the URL
  237. * to that build file.
  238. *
  239. * @param string $file The build file that you want a URL for.
  240. * @param bool|array $full Whether or not the URL should have the full base path.
  241. * @return string The generated URL.
  242. * @throws \RuntimeException when the build file does not exist.
  243. */
  244. public function url($file = null, $full = false)
  245. {
  246. $collection = $this->collection();
  247. if (!$collection->contains($file)) {
  248. throw new RuntimeException('Cannot get URL for build file that does not exist.');
  249. }
  250. $options = $full;
  251. if (!is_array($full)) {
  252. $options = ['full' => $full];
  253. }
  254. /** @var array $options */
  255. $options += ['full' => false];
  256. $target = $collection->get($file);
  257. $type = $target->ext();
  258. $config = $this->assetConfig();
  259. $baseUrl = $config->get($type . '.baseUrl');
  260. $devMode = Configure::read('debug');
  261. // CDN routes.
  262. if ($baseUrl && !$devMode) {
  263. return $baseUrl . $this->_getBuildName($target);
  264. }
  265. $root = str_replace('\\', '/', WWW_ROOT);
  266. $path = str_replace('\\', '/', $target->outputDir());
  267. $path = str_replace($root, '/', $path);
  268. $route = null;
  269. if (!$devMode) {
  270. $path = rtrim($path, '/') . '/';
  271. $route = $path . $this->_getBuildName($target);
  272. }
  273. if ($devMode || $config->general('alwaysEnableController')) {
  274. $route = $this->_getRoute($target, $path);
  275. }
  276. $route = str_replace(DS, '/', $route);
  277. if ($options['full']) {
  278. $base = Router::fullBaseUrl();
  279. return $base . $route;
  280. }
  281. return $route;
  282. }
  283. /**
  284. * Get the build file name.
  285. *
  286. * Generates filenames that are intended for production use
  287. * with statically generated files.
  288. *
  289. * @param \MiniAsset\AssetTarget $build The build being resolved.
  290. * @return string The resolved build name.
  291. */
  292. protected function _getBuildName(AssetTarget $build)
  293. {
  294. return $this->writer()->buildFileName($build);
  295. }
  296. /**
  297. * Get the dynamic build path for an asset.
  298. *
  299. * This generates URLs that work with the development dispatcher filter.
  300. *
  301. * @param \MiniAsset\AssetTarget $file The build file you want to make a url for.
  302. * @param string $base The base path to fetch a url with.
  303. * @return string Generated URL.
  304. */
  305. protected function _getRoute(AssetTarget $file, $base)
  306. {
  307. $query = [];
  308. if ($file->isThemed()) {
  309. $query['theme'] = $this->getView()->getTheme();
  310. }
  311. $base = rtrim($base, '/') . '/';
  312. $query = empty($query) ? '' : '?' . http_build_query($query);
  313. return $base . $file->name() . $query;
  314. }
  315. /**
  316. * Check if a build exists (is defined and have at least one file) in the ini file.
  317. *
  318. * @param string $file Name of the build that will be checked if exists.
  319. * @return bool True if the build file exists.
  320. */
  321. public function exists($file)
  322. {
  323. return $this->collection()->contains($file);
  324. }
  325. /**
  326. * Create a CSS file. Will generate inline style tags
  327. * in production, or reference the dynamic build file in development
  328. *
  329. * To create build files without configuration use addCss()
  330. *
  331. * Options:
  332. *
  333. * - All options supported by HtmlHelper::css() are supported.
  334. *
  335. * @param string $file A build target to include.
  336. * @throws \RuntimeException
  337. * @return string style tag
  338. */
  339. public function inlineCss($file)
  340. {
  341. $collection = $this->collection();
  342. if (!$collection->contains($file)) {
  343. throw new RuntimeException('Cannot create a stylesheet for a build that does not exist.');
  344. }
  345. $compiler = $this->factory()->compiler();
  346. $results = $compiler->generate($collection->get($file));
  347. return $this->Html->tag('style', $results, ['type' => 'text/css']);
  348. }
  349. /**
  350. * Create an inline script tag for a script asset. Will generate inline script tags
  351. * in production, or reference the dynamic build file in development.
  352. *
  353. * To create build files without configuration use addScript()
  354. *
  355. * Options:
  356. *
  357. * - All options supported by HtmlHelper::css() are supported.
  358. *
  359. * @param string $file A build target to include.
  360. * @throws \RuntimeException
  361. * @return string script tag
  362. */
  363. public function inlineScript($file)
  364. {
  365. $collection = $this->collection();
  366. if (!$collection->contains($file)) {
  367. throw new RuntimeException('Cannot create a script tag for a build that does not exist.');
  368. }
  369. $compiler = $this->factory()->compiler();
  370. $results = $compiler->generate($collection->get($file));
  371. return $this->Html->tag('script', $results);
  372. }
  373. }