/vendor/devfactory/minify/src/Providers/BaseProvider.php

https://github.com/freescout-helpdesk/freescout · PHP · 360 lines · 207 code · 54 blank · 99 comment · 23 complexity · 71fca8b773e06beadcbbccecbd38fb78 MD5 · raw file

  1. <?php namespace Devfactory\Minify\Providers;
  2. use Devfactory\Minify\Exceptions\CannotRemoveFileException;
  3. use Devfactory\Minify\Exceptions\CannotSaveFileException;
  4. use Devfactory\Minify\Exceptions\DirNotExistException;
  5. use Devfactory\Minify\Exceptions\DirNotWritableException;
  6. use Devfactory\Minify\Exceptions\FileNotExistException;
  7. use Illuminate\Filesystem\Filesystem;
  8. use Countable;
  9. abstract class BaseProvider implements Countable
  10. {
  11. /**
  12. * @var string
  13. */
  14. protected $outputDir;
  15. /**
  16. * @var string
  17. */
  18. protected $appended = '';
  19. /**
  20. * @var string
  21. */
  22. protected $filename = '';
  23. /**
  24. * @var array
  25. */
  26. protected $files = array();
  27. /**
  28. * @var array
  29. */
  30. protected $headers = array();
  31. /**
  32. * @var string
  33. */
  34. private $publicPath;
  35. /**
  36. * @var Illuminate\Foundation\Filesystem
  37. */
  38. protected $file;
  39. /**
  40. * @var boolean
  41. */
  42. private $disable_mtime;
  43. /**
  44. * @var string
  45. */
  46. private $hash_salt;
  47. /**
  48. * @param null $publicPath
  49. */
  50. public function __construct($publicPath = null, $config = null, Filesystem $file = null)
  51. {
  52. $this->file = $file ?: new Filesystem;
  53. $this->publicPath = $publicPath ?: $_SERVER['DOCUMENT_ROOT'];
  54. $this->disable_mtime = $config['disable_mtime'] ?: false;
  55. $this->hash_salt = $config['hash_salt'] ?: '';
  56. $value = function($key)
  57. {
  58. return isset($_SERVER[$key]) ? $_SERVER[$key] : '';
  59. };
  60. $this->headers = array(
  61. 'User-Agent' => $value('HTTP_USER_AGENT'),
  62. 'Accept' => $value('HTTP_ACCEPT'),
  63. 'Accept-Language' => $value('HTTP_ACCEPT_LANGUAGE'),
  64. 'Accept-Encoding' => 'identity',
  65. 'Connection' => 'close',
  66. );
  67. }
  68. /**
  69. * @param $outputDir
  70. * @return bool
  71. */
  72. public function make($outputDir)
  73. {
  74. $this->outputDir = $this->publicPath . $outputDir;
  75. $this->checkDirectory();
  76. if ($this->checkExistingFiles())
  77. {
  78. return false;
  79. }
  80. $this->removeOldFiles();
  81. $this->appendFiles();
  82. return true;
  83. }
  84. /**
  85. * @param $file
  86. * @return void
  87. * @throws \Devfactory\Minify\Exceptions\FileNotExistException
  88. */
  89. public function add($file)
  90. {
  91. if (is_array($file))
  92. {
  93. foreach ($file as $value) $this->add($value);
  94. }
  95. else if ($this->checkExternalFile($file))
  96. {
  97. $this->files[] = $file;
  98. }
  99. else {
  100. $file = $this->publicPath . $file;
  101. if (!file_exists($file))
  102. {
  103. throw new FileNotExistException("File '{$file}' does not exist");
  104. }
  105. $this->files[] = $file;
  106. }
  107. }
  108. /**
  109. * @param $baseUrl
  110. * @param $attributes
  111. *
  112. * @return string
  113. */
  114. public function tags($baseUrl, $attributes)
  115. {
  116. $html = '';
  117. foreach($this->files as $file)
  118. {
  119. $file = $baseUrl . str_replace($this->publicPath, '', $file);
  120. $html .= $this->tag($file, $attributes);
  121. }
  122. return $html;
  123. }
  124. /**
  125. * @return int
  126. */
  127. public function count()
  128. {
  129. return count($this->files);
  130. }
  131. /**
  132. * @throws \Devfactory\Minify\Exceptions\FileNotExistException
  133. */
  134. protected function appendFiles()
  135. {
  136. foreach ($this->files as $file) {
  137. if ($this->checkExternalFile($file))
  138. {
  139. if (strpos($file, '//') === 0) $file = 'http:' . $file;
  140. $headers = $this->headers;
  141. foreach ($headers as $key => $value)
  142. {
  143. $headers[$key] = $key . ': ' . $value;
  144. }
  145. $context = stream_context_create(array('http' => array(
  146. 'ignore_errors' => true,
  147. 'header' => implode("\r\n", $headers),
  148. )));
  149. $http_response_header = array(false);
  150. $contents = file_get_contents($file, false, $context);
  151. if (strpos($http_response_header[0], '200') === false)
  152. {
  153. throw new FileNotExistException("File '{$file}' does not exist");
  154. }
  155. } else {
  156. $contents = file_get_contents($file);
  157. }
  158. $this->appended .= $contents . "\n";
  159. }
  160. }
  161. /**
  162. * @return bool
  163. */
  164. protected function checkExistingFiles()
  165. {
  166. $this->buildMinifiedFilename();
  167. return file_exists($this->outputDir . $this->filename);
  168. }
  169. /**
  170. * @throws \Devfactory\Minify\Exceptions\DirNotWritableException
  171. * @throws \Devfactory\Minify\Exceptions\DirNotExistException
  172. */
  173. protected function checkDirectory()
  174. {
  175. if (!file_exists($this->outputDir))
  176. {
  177. // Try to create the directory
  178. if (!$this->file->makeDirectory($this->outputDir, 0775, true)) {
  179. throw new DirNotExistException("Buildpath '{$this->outputDir}' does not exist");
  180. }
  181. }
  182. if (!is_writable($this->outputDir))
  183. {
  184. throw new DirNotWritableException("Buildpath '{$this->outputDir}' is not writable");
  185. }
  186. }
  187. /**
  188. * @param string $file
  189. * @return bool
  190. */
  191. protected function checkExternalFile($file)
  192. {
  193. return preg_match('/^(https?:)?\/\//', $file);
  194. }
  195. /**
  196. * @return string
  197. */
  198. protected function buildMinifiedFilename()
  199. {
  200. $this->filename = $this->getHashedFilename() . (($this->disable_mtime) ? '' : $this->countModificationTime()) . static::EXTENSION;
  201. }
  202. /**
  203. * Build an HTML attribute string from an array.
  204. *
  205. * @param array $attributes
  206. * @return string
  207. */
  208. protected function attributes($attributes)
  209. {
  210. $html = array();
  211. foreach ((array) $attributes as $key => $value)
  212. {
  213. $element = $this->attributeElement($key, $value);
  214. if ( ! is_null($element)) $html[] = $element;
  215. }
  216. $output = count($html) > 0 ? ' '.implode(' ', $html) : '';
  217. return trim($output);
  218. }
  219. /**
  220. * Build a single attribute element.
  221. *
  222. * @param string|integer $key
  223. * @param string|boolean $value
  224. * @return string|null
  225. */
  226. protected function attributeElement($key, $value)
  227. {
  228. if (is_numeric($key)) $key = $value;
  229. if(is_bool($value))
  230. return $key;
  231. if ( ! is_null($value))
  232. return $key.'="'.htmlentities($value, ENT_QUOTES, 'UTF-8', false).'"';
  233. return null;
  234. }
  235. /**
  236. * @return string
  237. */
  238. protected function getHashedFilename()
  239. {
  240. $publicPath = $this->publicPath;
  241. return md5(implode('-', array_map(function($file) use ($publicPath) { return str_replace($publicPath, '', $file); }, $this->files)) . $this->hash_salt);
  242. }
  243. /**
  244. * @return int
  245. */
  246. protected function countModificationTime()
  247. {
  248. $time = 0;
  249. foreach ($this->files as $file)
  250. {
  251. if ($this->checkExternalFile($file))
  252. {
  253. $userAgent = isset($this->headers['User-Agent']) ? $this->headers['User-Agent'] : '';
  254. $time += hexdec(substr(md5($file . $userAgent), 0, 8));
  255. }
  256. else {
  257. $time += filemtime($file);
  258. }
  259. }
  260. return $time;
  261. }
  262. /**
  263. * @throws \Devfactory\Minify\Exceptions\CannotRemoveFileException
  264. */
  265. protected function removeOldFiles()
  266. {
  267. $pattern = $this->outputDir . $this->getHashedFilename() . '*';
  268. $find = glob($pattern);
  269. if( is_array($find) && count($find) )
  270. {
  271. foreach ($find as $file)
  272. {
  273. if ( ! unlink($file) ) {
  274. throw new CannotRemoveFileException("File '{$file}' cannot be removed");
  275. }
  276. }
  277. }
  278. }
  279. /**
  280. * @param $minified
  281. * @return string
  282. * @throws \Devfactory\Minify\Exceptions\CannotSaveFileException
  283. */
  284. protected function put($minified)
  285. {
  286. if(file_put_contents($this->outputDir . $this->filename, $minified) === false)
  287. {
  288. throw new CannotSaveFileException("File '{$this->outputDir}{$this->filename}' cannot be saved");
  289. }
  290. return $this->filename;
  291. }
  292. /**
  293. * @return string
  294. */
  295. public function getAppended()
  296. {
  297. return $this->appended;
  298. }
  299. /**
  300. * @return string
  301. */
  302. public function getFilename()
  303. {
  304. return $this->filename;
  305. }
  306. }