AssetUtilsTrait.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <?php
  2. /**
  3. * @package Grav\Common\Assets\Traits
  4. *
  5. * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common\Assets\Traits;
  9. use Grav\Common\Grav;
  10. use Grav\Common\Utils;
  11. trait AssetUtilsTrait
  12. {
  13. /**
  14. * Determine whether a link is local or remote.
  15. * Understands both "http://" and "https://" as well as protocol agnostic links "//"
  16. *
  17. * @param string $link
  18. * @return bool
  19. */
  20. public static function isRemoteLink($link)
  21. {
  22. $base = Grav::instance()['uri']->rootUrl(true);
  23. // Sanity check for local URLs with absolute URL's enabled
  24. if (Utils::startsWith($link, $base)) {
  25. return false;
  26. }
  27. return (0 === strpos($link, 'http://') || 0 === strpos($link, 'https://') || 0 === strpos($link, '//'));
  28. }
  29. /**
  30. * Download and concatenate the content of several links.
  31. *
  32. * @param array $assets
  33. * @param bool $css
  34. *
  35. * @return string
  36. */
  37. protected function gatherLinks(array $assets, $css = true)
  38. {
  39. $buffer = '';
  40. foreach ($assets as $id => $asset) {
  41. $local = true;
  42. $link = $asset->getAsset();
  43. $relative_path = $link;
  44. if (static::isRemoteLink($link)) {
  45. $local = false;
  46. if (0 === strpos($link, '//')) {
  47. $link = 'http:' . $link;
  48. }
  49. $relative_dir = \dirname($relative_path);
  50. } else {
  51. // Fix to remove relative dir if grav is in one
  52. if (($this->base_url !== '/') && Utils::startsWith($relative_path, $this->base_url)) {
  53. $base_url = '#' . preg_quote($this->base_url, '#') . '#';
  54. $relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/');
  55. }
  56. $relative_dir = \dirname($relative_path);
  57. $link = ROOT_DIR . $relative_path;
  58. }
  59. $file = ($this->fetch_command instanceof \Closure) ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
  60. // No file found, skip it...
  61. if ($file === false) {
  62. continue;
  63. }
  64. // Double check last character being
  65. if (!$css) {
  66. $file = rtrim($file, ' ;') . ';';
  67. }
  68. // If this is CSS + the file is local + rewrite enabled
  69. if ($css && $this->css_rewrite) {
  70. $file = $this->cssRewrite($file, $relative_dir, $local);
  71. }
  72. $file = rtrim($file) . PHP_EOL;
  73. $buffer .= $file;
  74. }
  75. // Pull out @imports and move to top
  76. if ($css) {
  77. $buffer = $this->moveImports($buffer);
  78. }
  79. return $buffer;
  80. }
  81. /**
  82. * Moves @import statements to the top of the file per the CSS specification
  83. *
  84. * @param string $file the file containing the combined CSS files
  85. *
  86. * @return string the modified file with any @imports at the top of the file
  87. */
  88. protected function moveImports($file)
  89. {
  90. $imports = [];
  91. $file = (string)preg_replace_callback(self::CSS_IMPORT_REGEX, function ($matches) use (&$imports) {
  92. $imports[] = $matches[0];
  93. return '';
  94. }, $file);
  95. return implode("\n", $imports) . "\n\n" . $file;
  96. }
  97. /**
  98. *
  99. * Build an HTML attribute string from an array.
  100. *
  101. * @return string
  102. */
  103. protected function renderAttributes()
  104. {
  105. $html = '';
  106. $no_key = ['loading'];
  107. foreach ($this->attributes as $key => $value) {
  108. if (is_numeric($key)) {
  109. $key = $value;
  110. }
  111. if (\is_array($value)) {
  112. $value = implode(' ', $value);
  113. }
  114. if (\in_array($key, $no_key, true)) {
  115. $element = htmlentities($value, ENT_QUOTES, 'UTF-8', false);
  116. } else {
  117. $element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"';
  118. }
  119. $html .= ' ' . $element;
  120. }
  121. return $html;
  122. }
  123. /**
  124. * Render Querystring
  125. *
  126. * @param string $asset
  127. * @return string
  128. */
  129. protected function renderQueryString($asset = null)
  130. {
  131. $querystring = '';
  132. $asset = $asset ?? $this->asset;
  133. if (!empty($this->query)) {
  134. if (Utils::contains($asset, '?')) {
  135. $querystring .= '&' . $this->query;
  136. } else {
  137. $querystring .= '?' . $this->query;
  138. }
  139. }
  140. if ($this->timestamp) {
  141. if (Utils::contains($asset, '?') || $querystring) {
  142. $querystring .= '&' . $this->timestamp;
  143. } else {
  144. $querystring .= '?' . $this->timestamp;
  145. }
  146. }
  147. return $querystring;
  148. }
  149. }