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.

262 lines
6.5 KiB

3 years ago
  1. <?php
  2. /**
  3. * @copyright 2017, Roeland Jago Douma <roeland@famdouma.nl>
  4. *
  5. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  6. * @author Julius Härtl <jus@bitgrid.net>
  7. * @author Lukas Reschke <lukas@statuscode.ch>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Roeland Jago Douma <roeland@famdouma.nl>
  10. *
  11. * @license GNU AGPL version 3 or any later version
  12. *
  13. * This program is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU Affero General Public License as
  15. * published by the Free Software Foundation, either version 3 of the
  16. * License, or (at your option) any later version.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU Affero General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU Affero General Public License
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. *
  26. */
  27. namespace OC\Template;
  28. use OC\SystemConfig;
  29. use OCP\Files\IAppData;
  30. use OCP\Files\NotFoundException;
  31. use OCP\Files\NotPermittedException;
  32. use OCP\Files\SimpleFS\ISimpleFolder;
  33. use OCP\ICache;
  34. use OCP\ICacheFactory;
  35. use OCP\ILogger;
  36. use OCP\IURLGenerator;
  37. class JSCombiner {
  38. /** @var IAppData */
  39. protected $appData;
  40. /** @var IURLGenerator */
  41. protected $urlGenerator;
  42. /** @var ICache */
  43. protected $depsCache;
  44. /** @var SystemConfig */
  45. protected $config;
  46. /** @var ILogger */
  47. protected $logger;
  48. /** @var ICacheFactory */
  49. private $cacheFactory;
  50. /**
  51. * @param IAppData $appData
  52. * @param IURLGenerator $urlGenerator
  53. * @param ICacheFactory $cacheFactory
  54. * @param SystemConfig $config
  55. * @param ILogger $logger
  56. */
  57. public function __construct(IAppData $appData,
  58. IURLGenerator $urlGenerator,
  59. ICacheFactory $cacheFactory,
  60. SystemConfig $config,
  61. ILogger $logger) {
  62. $this->appData = $appData;
  63. $this->urlGenerator = $urlGenerator;
  64. $this->cacheFactory = $cacheFactory;
  65. $this->depsCache = $this->cacheFactory->createDistributed('JS-' . md5($this->urlGenerator->getBaseUrl()));
  66. $this->config = $config;
  67. $this->logger = $logger;
  68. }
  69. /**
  70. * @param string $root
  71. * @param string $file
  72. * @param string $app
  73. * @return bool
  74. */
  75. public function process($root, $file, $app) {
  76. if ($this->config->getValue('debug') || !$this->config->getValue('installed')) {
  77. return false;
  78. }
  79. $path = explode('/', $root . '/' . $file);
  80. $fileName = array_pop($path);
  81. $path = implode('/', $path);
  82. try {
  83. $folder = $this->appData->getFolder($app);
  84. } catch (NotFoundException $e) {
  85. // creating css appdata folder
  86. $folder = $this->appData->newFolder($app);
  87. }
  88. if ($this->isCached($fileName, $folder)) {
  89. return true;
  90. }
  91. return $this->cache($path, $fileName, $folder);
  92. }
  93. /**
  94. * @param string $fileName
  95. * @param ISimpleFolder $folder
  96. * @return bool
  97. */
  98. protected function isCached($fileName, ISimpleFolder $folder) {
  99. $fileName = str_replace('.json', '.js', $fileName);
  100. if (!$folder->fileExists($fileName)) {
  101. return false;
  102. }
  103. $fileName = $fileName . '.deps';
  104. try {
  105. $deps = $this->depsCache->get($folder->getName() . '-' . $fileName);
  106. if ($deps === null || $deps === '') {
  107. $depFile = $folder->getFile($fileName);
  108. $deps = $depFile->getContent();
  109. }
  110. // check again
  111. if ($deps === null || $deps === '') {
  112. $this->logger->info('JSCombiner: deps file empty: ' . $fileName);
  113. return false;
  114. }
  115. $deps = json_decode($deps, true);
  116. if ($deps === null) {
  117. return false;
  118. }
  119. foreach ($deps as $file=>$mtime) {
  120. if (!file_exists($file) || filemtime($file) > $mtime) {
  121. return false;
  122. }
  123. }
  124. return true;
  125. } catch (NotFoundException $e) {
  126. return false;
  127. }
  128. }
  129. /**
  130. * @param string $path
  131. * @param string $fileName
  132. * @param ISimpleFolder $folder
  133. * @return bool
  134. */
  135. protected function cache($path, $fileName, ISimpleFolder $folder) {
  136. $deps = [];
  137. $fullPath = $path . '/' . $fileName;
  138. $data = json_decode(file_get_contents($fullPath));
  139. $deps[$fullPath] = filemtime($fullPath);
  140. $res = '';
  141. foreach ($data as $file) {
  142. $filePath = $path . '/' . $file;
  143. if (is_file($filePath)) {
  144. $res .= file_get_contents($filePath);
  145. $res .= PHP_EOL . PHP_EOL;
  146. $deps[$filePath] = filemtime($filePath);
  147. }
  148. }
  149. $fileName = str_replace('.json', '.js', $fileName);
  150. try {
  151. $cachedfile = $folder->getFile($fileName);
  152. } catch (NotFoundException $e) {
  153. $cachedfile = $folder->newFile($fileName);
  154. }
  155. $depFileName = $fileName . '.deps';
  156. try {
  157. $depFile = $folder->getFile($depFileName);
  158. } catch (NotFoundException $e) {
  159. $depFile = $folder->newFile($depFileName);
  160. }
  161. try {
  162. $gzipFile = $folder->getFile($fileName . '.gzip'); # Safari doesn't like .gz
  163. } catch (NotFoundException $e) {
  164. $gzipFile = $folder->newFile($fileName . '.gzip'); # Safari doesn't like .gz
  165. }
  166. try {
  167. $cachedfile->putContent($res);
  168. $deps = json_encode($deps);
  169. $depFile->putContent($deps);
  170. $this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
  171. $gzipFile->putContent(gzencode($res, 9));
  172. $this->logger->debug('JSCombiner: successfully cached: ' . $fileName);
  173. return true;
  174. } catch (NotPermittedException $e) {
  175. $this->logger->error('JSCombiner: unable to cache: ' . $fileName);
  176. return false;
  177. }
  178. }
  179. /**
  180. * @param string $appName
  181. * @param string $fileName
  182. * @return string
  183. */
  184. public function getCachedJS($appName, $fileName) {
  185. $tmpfileLoc = explode('/', $fileName);
  186. $fileName = array_pop($tmpfileLoc);
  187. $fileName = str_replace('.json', '.js', $fileName);
  188. return substr($this->urlGenerator->linkToRoute('core.Js.getJs', ['fileName' => $fileName, 'appName' => $appName]), strlen(\OC::$WEBROOT) + 1);
  189. }
  190. /**
  191. * @param string $root
  192. * @param string $file
  193. * @return string[]
  194. */
  195. public function getContent($root, $file) {
  196. /** @var array $data */
  197. $data = json_decode(file_get_contents($root . '/' . $file));
  198. if (!is_array($data)) {
  199. return [];
  200. }
  201. $path = explode('/', $file);
  202. array_pop($path);
  203. $path = implode('/', $path);
  204. $result = [];
  205. foreach ($data as $f) {
  206. $result[] = $path . '/' . $f;
  207. }
  208. return $result;
  209. }
  210. /**
  211. * Clear cache with combined javascript files
  212. *
  213. * @throws NotFoundException
  214. */
  215. public function resetCache() {
  216. $this->cacheFactory->createDistributed('JS-')->clear();
  217. $appDirectory = $this->appData->getDirectoryListing();
  218. foreach ($appDirectory as $folder) {
  219. foreach ($folder->getDirectoryListing() as $file) {
  220. $file->delete();
  221. }
  222. }
  223. }
  224. }