PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/bitrix/modules/main/lib/data/appcachemanifest.php

https://gitlab.com/Rad1calDreamer/honey
PHP | 611 lines | 512 code | 49 blank | 50 comment | 32 complexity | f2633b2e5ba168d0ad091f143e2d4752 MD5 | raw file
  1. <?php
  2. namespace Bitrix\Main\Data;
  3. use Bitrix\Main\Application;
  4. use Bitrix\Main\Page\Asset;
  5. class AppCacheManifest
  6. {
  7. const MANIFEST_CHECK_FILE = "/bitrix/tools/check_appcache.php";
  8. private static $debug;
  9. private static $instance;
  10. private static $isEnabled = false;
  11. private static $customCheckFile = null;
  12. private $files = Array();
  13. private $pageURI = "";
  14. private $network = Array();
  15. private $fallbackPages = Array();
  16. private $params = Array();
  17. private $isSided = false;
  18. private $isModified = false;
  19. private $receivedManifest = "";
  20. private $receivedCacheParams = Array();
  21. private function __construct()
  22. {
  23. //use CAppCacheManifest::getInstance();
  24. }
  25. /**
  26. * @return boolean
  27. */
  28. public static function getDebug()
  29. {
  30. return self::$debug;
  31. }
  32. /**
  33. * @return boolean
  34. */
  35. public function isEnabled()
  36. {
  37. return self::$isEnabled;
  38. }
  39. private function __clone()
  40. {
  41. //you can't clone it
  42. }
  43. public static function getInstance()
  44. {
  45. if (is_null(self::$instance))
  46. {
  47. self::$instance = new AppCacheManifest();
  48. self::$debug = (defined("BX_APPCACHE_DEBUG") && BX_APPCACHE_DEBUG);
  49. }
  50. return self::$instance;
  51. }
  52. /**
  53. * Creates or updates the manifest file for the page with usage its content.
  54. *
  55. * @param bool $isEnable
  56. *
  57. * @internal param $content
  58. */
  59. public static function setEnabled($isEnabled = true)
  60. {
  61. self::$isEnabled = (bool)$isEnabled;
  62. }
  63. public function generate(&$content)
  64. {
  65. $manifest = AppCacheManifest::getInstance();
  66. $files = $manifest->getFilesFromContent($content);
  67. $this->isModified = false;
  68. $manifestId = $this->getCurrentManifestID();
  69. if ($this->isSided)
  70. {
  71. $curManifestId = $this->getManifestID($this->pageURI, $this->receivedCacheParams);
  72. if ($curManifestId != $manifestId)
  73. {
  74. self::removeManifestById($curManifestId);
  75. }
  76. }
  77. $currentHashSum = md5(serialize($files["FULL_FILE_LIST"]) . serialize($this->fallbackPages) . serialize($this->network));
  78. $manifestCache = $this->readManifestCache($manifestId);
  79. if (!$manifestCache || $manifestCache["FILE_HASH"] != $currentHashSum || self::$debug)
  80. {
  81. $this->isModified = true;
  82. $this->setFiles($files["FULL_FILE_LIST"]);
  83. $this->setNetworkFiles(Array("*"));
  84. $arFields = array(
  85. "ID" => $manifestId,
  86. "TEXT" => $this->getManifestContent(),
  87. "FILE_HASH" => $currentHashSum,
  88. "FILE_DATA" => Array(
  89. "FILE_TIMESTAMPS" => $files["FILE_TIMESTAMPS"],
  90. "CSS_FILE_IMAGES" => $files["CSS_FILE_IMAGES"]
  91. )
  92. );
  93. if (!self::$debug)
  94. {
  95. $this->writeManifestCache($arFields);
  96. }
  97. else
  98. {
  99. $jsFields = json_encode($arFields);
  100. $fileCount = count($this->files);
  101. $params = json_encode($this->params);
  102. $detailInfo = json_encode($arFields["FILE_DATA"]);
  103. $fileCountImages = 0;
  104. foreach ($arFields["FILE_DATA"]["CSS_FILE_IMAGES"] as $file=>$images)
  105. {
  106. $fileCountImages += count($images);
  107. }
  108. $debugOutput = <<<JS
  109. console.log("-------APPLICATION CACHE DEBUG INFO------");
  110. console.log("File count:", $fileCount);
  111. console.log("Image file count:", $fileCountImages);
  112. console.log("Params:", $params);
  113. console.log("Detail:", $jsFields);
  114. console.log("--------------------------------------------");
  115. JS;
  116. $jsContent = str_replace(array("\n", "\t"), "", $debugOutput);
  117. $content = str_replace("__DEBUG_HOLDER__", $jsContent, $content);
  118. }
  119. }
  120. return $this->getIsModified();
  121. }
  122. /**
  123. * OnBeforeEndBufferContent handler
  124. * @return array|mixed
  125. */
  126. public static function onBeforeEndBufferContent()
  127. {
  128. global $APPLICATION;
  129. $selfObject = self::getInstance();
  130. $server = \Bitrix\Main\Context::getCurrent()->getServer();
  131. $params = Array();
  132. $appCacheUrl = $server->get("HTTP_BX_APPCACHE_URL");
  133. $appCacheParams = $server->get("HTTP_BX_APPCACHE_PARAMS");
  134. if (strlen($appCacheUrl) > 0)
  135. {
  136. //TODO compare $_SERVER["REQUEST_URI"] and $_SERVER["HTTP_BX_APPCACHE_URL"]
  137. $selfObject->setIsSided(true);
  138. $selfObject->setPageURI($appCacheUrl);
  139. if ($appCacheParams)
  140. {
  141. $params = json_decode($appCacheParams, true);
  142. if (!is_array($params))
  143. {
  144. $params = array();
  145. }
  146. $selfObject->setReceivedCacheParams($params);
  147. }
  148. }
  149. else
  150. {
  151. $selfObject->setPageURI($server->get("REQUEST_URI"));
  152. if(!self::$debug)
  153. {
  154. $APPLICATION->SetPageProperty("manifest", " manifest=\"" . self::getManifestCheckFile() . "?manifest_id=" . $selfObject->getCurrentManifestID() . "\"");
  155. }
  156. else
  157. {
  158. Asset::getInstance()->addString("<script type=\"text/javascript\">__DEBUG_HOLDER__</script>");
  159. }
  160. $params = Array(
  161. "PAGE_URL" => $selfObject->getPageURI(),
  162. "PARAMS" => $selfObject->getAdditionalParams(),
  163. "MODE" => "APPCACHE"
  164. );
  165. }
  166. return (is_array($params) ? $params : array());
  167. }
  168. /**Gets file for getting of manifest content
  169. * @return string
  170. */
  171. public function getManifestCheckFile()
  172. {
  173. $checkFile = self::MANIFEST_CHECK_FILE;
  174. if(self::$customCheckFile != null && strlen(self::$customCheckFile)>0)
  175. $checkFile = self::$customCheckFile;
  176. return $checkFile;
  177. }
  178. /**
  179. * Sets custom file for getting of manifest content
  180. * self::MANIFEST_CHECK_FILE uses by default
  181. *@param string $customManifestCheckFile
  182. */
  183. public function setManifestCheckFile($customManifestCheckFile)
  184. {
  185. self::$customCheckFile = $customManifestCheckFile;
  186. }
  187. /*
  188. * OnEndBufferContent handler
  189. */
  190. public static function onEndBufferContent(&$content)
  191. {
  192. AppCacheManifest::getInstance()->generate($content);
  193. }
  194. /**
  195. * Creates, rewrites the manifest file
  196. * @return bool|string
  197. */
  198. public function getManifestContent()
  199. {
  200. $manifestText = "CACHE MANIFEST\n\n";
  201. $manifestText .= $this->getManifestDescription();
  202. $manifestText .= "#files" . "\n\n";
  203. $manifestText .= implode("\n", $this->files) . "\n\n";
  204. $manifestText .= "NETWORK:\n";
  205. $manifestText .= implode("\n", $this->network) . "\n\n";
  206. $manifestText .= "FALLBACK:\n\n";
  207. $countFallback = count($this->fallbackPages);
  208. for ($i = 0; $i < $countFallback; $i++)
  209. {
  210. $manifestText .= $this->fallbackPages[$i]["online"] . " " . $this->fallbackPages[$i]["offline"] . "\n";
  211. }
  212. return $manifestText;
  213. }
  214. /**
  215. * Parses the passed content to find css, js and images. Returns the array of files.
  216. *
  217. * @param $content
  218. *
  219. * @return array
  220. */
  221. public function getFilesFromContent($content)
  222. {
  223. $files = Array();
  224. $arFilesByType = Array();
  225. $arExtensions = Array("js", "css");
  226. $extension_regex = "(?:" . implode("|", $arExtensions) . ")";
  227. $regex = "/
  228. ((?i:
  229. href=
  230. |src=
  231. |BX\\.loadCSS\\(
  232. |BX\\.loadScript\\(
  233. |jsUtils\\.loadJSFile\\(
  234. |background\\s*:\\s*url\\(
  235. )) #attribute
  236. (\"|') #open_quote
  237. ([^?'\"]+\\.) #href body
  238. (" . $extension_regex . ") #extentions
  239. (|\\?\\d+|\\?v=\\d+) #params
  240. (\\2) #close_quote
  241. /x";
  242. $match = Array();
  243. preg_match_all($regex, $content, $match);
  244. $link = $match[3];
  245. $extension = $match[4];
  246. $params = $match[5];
  247. $linkCount = count($link);
  248. $fileData = array(
  249. "FULL_FILE_LIST" => array(),
  250. "FILE_TIMESTAMPS" => array(),
  251. "CSS_FILE_IMAGES" => array()
  252. );
  253. for ($i = 0; $i < $linkCount; $i++)
  254. {
  255. $fileData["FULL_FILE_LIST"][] = $files[] = $link[$i] . $extension[$i] . $params[$i];
  256. $fileData["FILE_TIMESTAMPS"][$link[$i] . $extension[$i]] = $params[$i];
  257. $arFilesByType[$extension[$i]][] = $link[$i] . $extension[$i];
  258. }
  259. $manifestCache = $this->readManifestCache($this->getCurrentManifestID());
  260. if (array_key_exists("css", $arFilesByType))
  261. {
  262. $cssCount = count($arFilesByType["css"]);
  263. for ($j = 0; $j < $cssCount; $j++)
  264. {
  265. $cssFilePath = $arFilesByType["css"][$j];
  266. if ($manifestCache["FILE_DATA"]["FILE_TIMESTAMPS"][$cssFilePath] != $fileData["FILE_TIMESTAMPS"][$cssFilePath])
  267. {
  268. $fileContent = false;
  269. $fileUrl = parse_url($cssFilePath);
  270. $file = new \Bitrix\Main\IO\File(Application::getDocumentRoot() . $fileUrl['path']);
  271. if ($file->isExists() && $file->isReadable())
  272. {
  273. $fileContent = $file->getContents();
  274. }
  275. elseif ($fileUrl["scheme"])
  276. {
  277. $req = new \CHTTP();
  278. $req->http_timeout = 20;
  279. $fileContent = $req->Get($cssFilePath);
  280. }
  281. if ($fileContent != false)
  282. {
  283. $regex = '#([;\s:]*(?:url|@import)\s*\(\s*)(\'|"|)(.+?)(\2)\s*\)#si';
  284. $cssFileRelative = new \Bitrix\Main\IO\File($cssFilePath);
  285. $cssPath = $cssFileRelative->getDirectoryName();
  286. preg_match_all($regex, $fileContent, $match);
  287. $matchCount = count($match[3]);
  288. for ($k = 0; $k < $matchCount; $k++)
  289. {
  290. $file = self::replaceUrlCSS($match[3][$k], addslashes($cssPath));
  291. if (!in_array($file, $files) && !strpos($file, ";base64"))
  292. {
  293. $fileData["FULL_FILE_LIST"][] = $files[] = $file;
  294. $fileData["CSS_FILE_IMAGES"][$cssFilePath][] = $file;
  295. }
  296. }
  297. }
  298. }
  299. else
  300. {
  301. $fileData["CSS_FILE_IMAGES"][$cssFilePath] = $manifestCache["FILE_DATA"]["CSS_FILE_IMAGES"][$cssFilePath];
  302. if (is_array($manifestCache["FILE_DATA"]["CSS_FILE_IMAGES"][$cssFilePath]))
  303. {
  304. $fileData["FULL_FILE_LIST"] = array_merge($fileData["FULL_FILE_LIST"], $manifestCache["FILE_DATA"]["CSS_FILE_IMAGES"][$cssFilePath]);
  305. }
  306. }
  307. }
  308. }
  309. return $fileData;
  310. }
  311. /**
  312. * Replaces url to css-file with absolute path.
  313. *
  314. * @param $url
  315. * @param $cssPath
  316. *
  317. * @return string
  318. */
  319. private static function replaceUrlCSS($url, $cssPath)
  320. {
  321. if (strpos($url, "://") !== false || strpos($url, "data:") !== false)
  322. {
  323. return $url;
  324. }
  325. $url = trim(stripslashes($url), "'\" \r\n\t");
  326. if (substr($url, 0, 1) == "/")
  327. {
  328. return $url;
  329. }
  330. return $cssPath . '/' . $url;
  331. }
  332. /**
  333. * Sets received cache params
  334. *
  335. * @param $receivedCacheParams
  336. */
  337. public function setReceivedCacheParams($receivedCacheParams)
  338. {
  339. $this->receivedCacheParams = $receivedCacheParams;
  340. }
  341. /**
  342. * Gets received cache parameters
  343. * @return array
  344. */
  345. public function getReceivedCacheParams()
  346. {
  347. return $this->receivedCacheParams;
  348. }
  349. /**
  350. * Sets received path to manifest
  351. *
  352. * @param $receivedManifest
  353. */
  354. public function setReceivedManifest($receivedManifest)
  355. {
  356. $this->receivedManifest = $receivedManifest;
  357. }
  358. public function getReceivedManifest()
  359. {
  360. return $this->receivedManifest;
  361. }
  362. public function setIsSided($isSided)
  363. {
  364. $this->isSided = $isSided;
  365. }
  366. public function getIsSided()
  367. {
  368. return $this->isSided;
  369. }
  370. public function setPageURI($pageURI = "")
  371. {
  372. $this->pageURI = $pageURI;
  373. }
  374. public function getPageURI()
  375. {
  376. return $this->pageURI;
  377. }
  378. public function setFiles($arFiles)
  379. {
  380. if (count($this->files) > 0)
  381. {
  382. $this->files = array_merge($this->files, $arFiles);
  383. }
  384. else
  385. {
  386. $this->files = $arFiles;
  387. }
  388. }
  389. public function addFile($filePath)
  390. {
  391. $this->files[] = $filePath;
  392. }
  393. public function addAdditionalParam($name, $value)
  394. {
  395. $this->params[$name] = $value;
  396. }
  397. public function getAdditionalParams()
  398. {
  399. return $this->params;
  400. }
  401. public function setNetworkFiles($network)
  402. {
  403. $this->network = $network;
  404. }
  405. public function getNetworkFiles()
  406. {
  407. return $this->network;
  408. }
  409. public function addFallbackPage($onlinePage, $offlinePage)
  410. {
  411. $this->fallbackPages[] = Array(
  412. "online" => $onlinePage,
  413. "offline" => $offlinePage
  414. );
  415. }
  416. public function getFallbackPages()
  417. {
  418. return $this->fallbackPages;
  419. }
  420. public function getCurrentManifestID()
  421. {
  422. return $this->getManifestID($this->pageURI, $this->params);
  423. }
  424. public function getIsModified()
  425. {
  426. return $this->isModified && !self::$debug;
  427. }
  428. private function getManifestDescription()
  429. {
  430. $manifestParams = "";
  431. $arCacheParams = $this->params;
  432. if (count($arCacheParams) > 0)
  433. {
  434. foreach ($arCacheParams as $key => $value)
  435. {
  436. $manifestParams .= "#" . $key . "=" . $value . "\n";
  437. }
  438. }
  439. $desc = "#Date: " . date("r") . "\n";
  440. $desc .= "#Page: " . $this->pageURI . "\n";
  441. $desc .= "#Count: " . count($this->files) . "\n";
  442. $desc .= "#Params: \n" . $manifestParams . "\n\n";
  443. return $desc;
  444. }
  445. private function writeManifestCache($arFields)
  446. {
  447. $cache = new \CPHPCache();
  448. $manifestId = $arFields["ID"];
  449. $this->removeManifestById($manifestId);
  450. $cachePath = self::getCachePath($manifestId);
  451. $cache->StartDataCache(3600 * 24 * 365, $manifestId, $cachePath);
  452. $cache->EndDataCache($arFields);
  453. return true;
  454. }
  455. public function readManifestCache($manifestId)
  456. {
  457. $cache = new \CPHPCache();
  458. $cachePath = self::getCachePath($manifestId);
  459. if ($cache->InitCache(3600 * 24 * 365, $manifestId, $cachePath))
  460. {
  461. return $cache->getVars();
  462. }
  463. return false;
  464. }
  465. private static function removeManifestById($manifestId)
  466. {
  467. $cache = new \CPHPCache();
  468. $cachePath = self::getCachePath($manifestId);
  469. return $cache->CleanDir($cachePath);
  470. }
  471. /**
  472. * @param $manifestId
  473. *
  474. * @return string
  475. */
  476. public static function getCachePath($manifestId)
  477. {
  478. $cachePath = "/appcache/" . substr($manifestId, 0, 2) . "/" . substr($manifestId, 2, 4) . "/";
  479. return $cachePath;
  480. }
  481. private function getManifestID($pageURI, $arParams)
  482. {
  483. $id = $pageURI;
  484. if (count($arParams) > 0)
  485. {
  486. $strCacheParams = "";
  487. foreach ($arParams as $key => $value)
  488. {
  489. $strCacheParams .= $key . "=" . $value;
  490. }
  491. $id .= $strCacheParams;
  492. }
  493. return md5($id);
  494. }
  495. public static function checkObsoleteManifest()
  496. {
  497. $server = \Bitrix\Main\Context::getCurrent()->getServer();
  498. $appCacheUrl = $server->get("HTTP_BX_APPCACHE_URL");
  499. $appCacheParams = $server->get("HTTP_BX_APPCACHE_PARAMS");
  500. if ($appCacheUrl)
  501. {
  502. $params = json_decode($appCacheParams, true);
  503. if (!is_array($params))
  504. {
  505. $params = array();
  506. }
  507. \Bitrix\Main\Data\AppCacheManifest::clear($appCacheUrl, $params);
  508. }
  509. }
  510. private static function clear($url, $params)
  511. {
  512. $manifestId = self::getManifestID($url, $params);
  513. if (self::readManifestCache($manifestId))
  514. {
  515. self::removeManifestById($manifestId);
  516. self::getInstance()->isModified = true;
  517. }
  518. }
  519. }