Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
asset.php
1<?php
2
3namespace Bitrix\Main\Page;
4
8
9class Asset
10{
11 private static $instance;
12
14 private $targetList;
15
17 private $target;
18
20 private $css = [];
21
23 private $js = [];
24
26 private $strings = [
27 AssetLocation::BEFORE_CSS => [],
28 AssetLocation::AFTER_CSS => [],
29 AssetLocation::AFTER_JS_KERNEL => [],
30 AssetLocation::AFTER_JS => [],
31 ];
32
34 private $moduleInfo = ['CSS' => [], 'JS' => []];
35 private $kernelAsset = ['CSS' => [], 'JS' => []];
36 private $assetList = ['CSS' => [], 'SOURCE_CSS' => [], 'JS' => [], 'SOURCE_JS' => []];
37 private $fileList = ['CSS' => [], 'JS' => []];
38 private $mode = AssetMode::STANDARD;
39
40 private $ajax;
41 private $xhtmlStyle = '/';
42
43 private $optimizeCss = true;
44 private $optimizeJs = true;
45
46 private $headString = false;
47 private $headScript = false;
48 private $bodyScript = false;
49 private $moveJsToBody = null;
50
51 private $templateExists = false;
52 private $siteTemplateID = '';
53 private $templatePath = '';
54 private $documentRoot = '';
55 private $dbType = 'MYSQL';
56 private $assetCSSCnt = 0;
57 private $assetJSCnt = 0;
58
59 const SOURCE_MAP_TAG = "\n//# sourceMappingURL=";
60 const HEADER_START_TAG = "; /* Start:\"";
61 const HEADER_END_TAG = "\"*/";
62 const version = 1;
63
64 private function __construct()
65 {
66 //use self::getInstance()
67 $this->targetList['KERNEL'] = [
68 'NAME' => 'KERNEL',
69 'START' => true,
70 'CSS_RES' => [],
71 'JS_RES' => [],
72 'CSS_LIST' => [],
73 'JS_LIST' => [],
74 'STRING_LIST' => [],
75 'UNIQUE' => true,
76 'PREFIX' => 'kernel',
77 'BODY' => false,
78 'MODE' => AssetMode::ALL
79 ];
80
81 $this->targetList['BODY'] = $this->targetList['TEMPLATE'] = $this->targetList['PAGE'] = $this->targetList['KERNEL'];
82 $this->targetList['PAGE']['NAME'] = 'PAGE';
83 $this->targetList['PAGE']['UNIQUE'] = false;
84 $this->targetList['PAGE']['PREFIX'] = 'page';
85 $this->targetList['TEMPLATE']['NAME'] = 'TEMPLATE';
86 $this->targetList['TEMPLATE']['UNIQUE'] = false;
87 $this->targetList['TEMPLATE']['PREFIX'] = 'template';
88 $this->targetList['BODY']['NAME'] = 'BODY';
89 $this->targetList['BODY']['UNIQUE'] = false;
90 $this->targetList['BODY']['PREFIX'] = 'body';
91
93 $this->targetList['KERNEL']['CSS_LIST']['KERNEL_main'] = [];
94 $this->targetList['KERNEL']['JS_LIST']['KERNEL_main'] = [];
95
96 $this->target = &$this->targetList['TEMPLATE'];
97 $this->documentRoot = Main\Loader::getDocumentRoot();
98 }
99
104 private function __clone()
105 {
106 //you can't clone it
107 }
108
114 public static function getInstance()
115 {
116 if (is_null(self::$instance))
117 {
118 self::$instance = new Asset();
119 }
120
121 return self::$instance;
122 }
123
129 public function setMode($mode = AssetMode::STANDARD)
130 {
131 $this->mode = $mode;
132 }
133
138 public static function gzipEnabled()
139 {
140 static $gzip = null;
141 if ($gzip === null)
142 {
143 $gzip = (
144 Option::get('main','compres_css_js_files', 'N') == 'Y'
145 && extension_loaded('zlib')
146 && function_exists('gzopen')
147 );
148 }
149 return $gzip;
150 }
151
156 public function enableOptimizeCss()
157 {
158 $this->optimizeCss = true;
159 }
160
165 public function disableOptimizeCss()
166 {
167 $this->optimizeCss = false;
168 }
169
174 public function enableOptimizeJs()
175 {
176 $this->optimizeJs = true;
177 }
178
183 public function disableOptimizeJs()
184 {
185 $this->optimizeJs = false;
186 }
187
192 public function setXhtml($value)
193 {
194 $this->xhtmlStyle = ($value === true ? '/':'');
195 }
196
202 public function setMaxCss($value)
203 {
204
205 }
206
212 public function setShowHeadString($value = true)
213 {
214 $this->headString = $value;
215 }
216
221 public function getShowHeadString()
222 {
223 return $this->headString;
224 }
225
231 public function setShowHeadScript($value = true)
232 {
233 $this->headScript = $value;
234 }
235
241 public function setShowBodyScript($value = true)
242 {
243 $this->bodyScript = $value;
244 }
245
250 public function setAjax()
251 {
252 $newInstance = self::$instance = new Asset();
253 $newInstance->ajax = true;
254 return $newInstance;
255 }
256
260 public function getTargetName()
261 {
262 return $this->target['NAME'];
263 }
264
268 public function getTarget()
269 {
270 return $this->target;
271 }
272
279 public function startSet($id = '', $mode = AssetMode::ALL)
280 {
281 return $this->startTarget($id, $mode);
282 }
283
290 public function startTarget($id = '', $mode = AssetMode::ALL)
291 {
292 $id = strtoupper(trim($id));
293 if ($id == '')
294 {
295 return false;
296 }
297
298 if ($id == 'TEMPLATE')
299 {
300 $this->templateExists = true;
301 }
302
303 if (
304 ($this->target['NAME'] == 'TEMPLATE' || $this->target['NAME'] == 'PAGE')
305 && ($id == 'TEMPLATE' || $id == 'PAGE')
306 )
307 {
308 $this->target['START'] = false;
309 $this->targetList[$id]['START'] = true;
310 $this->target = &$this->targetList[$id];
311 }
312 elseif ($id != 'TEMPLATE' && $id != 'PAGE')
313 {
314 if (isset($this->targetList[$id]))
315 {
316 return false;
317 }
318
319 $this->stopTarget();
320 $this->targetList[$id] = [
321 'NAME' => $id,
322 'START' => true,
323 'JS_RES' => [],
324 'CSS_RES' => [],
325 'JS_LIST' => [],
326 'CSS_LIST' => [],
327 'STRING_LIST' => [],
328 'BODY' => false,
329 'UNIQUE' => false,
330 'MODE' => $mode
331 ];
332 $this->target = &$this->targetList[$id];
333 }
334 return true;
335 }
336
342 public function stopTarget($id = '')
343 {
344 $id = strtoupper(trim($id));
345 if ($id == 'TEMPLATE')
346 {
347 if($this->target['NAME'] == 'TEMPLATE')
348 {
349 $this->target['START'] = false;
350 $this->target = &$this->targetList['PAGE'];
351 }
352 else
353 {
354 $this->targetList['TEMPLATE']['START'] = false;
355 }
356 }
357 else
358 {
359 if ($this->target['NAME'] == 'TEMPLATE')
360 {
361 return false;
362 }
363 elseif ($this->targetList['TEMPLATE']['START'])
364 {
365 $this->target['START'] = false;
366 $this->target = &$this->targetList['TEMPLATE'];
367 }
368 else
369 {
370 $this->target['START'] = false;
371 $this->target = &$this->targetList['PAGE'];
372 }
373 }
374
375 return true;
376 }
377
384 public function getAssetInfo($id, $mode)
385 {
386 $id = strtoupper(trim($id));
387 $emptyData = ['JS' => [], 'BUNDLE_JS' => [], 'CSS' => [], 'BUNDLE_CSS' => [], 'STRINGS' => []];
388
389 if (!isset($this->targetList[$id]))
390 {
391 return $emptyData;
392 }
393
394 static $cacheInfo = [
395 AssetMode::STANDARD => null,
396 AssetMode::COMPOSITE => null,
397 AssetMode::ALL => null,
398 AssetMode::SPECIAL => null
399 ];
400
401 if ($cacheInfo[$mode] === null)
402 {
403 $cacheInfo[$mode] = $emptyData;
404
405 foreach ($this->strings as $location)
406 {
407 foreach ($location as $item)
408 {
409 if ($mode == $item['MODE'])
410 {
411 $cacheInfo[$mode]['STRINGS'][$item['TARGET'][0]][] = $item['CONTENT'];
412 }
413 }
414 }
415
416 foreach (['JS', 'CSS'] as $type)
417 {
418 foreach ($this->getTargetList($type) as $set)
419 {
420 $cache = &$cacheInfo[$mode][$type][$set['NAME']];
421 $cacheFull = &$cacheInfo[$mode]['BUNDLE_'.$type][$set['NAME']];
422
423 if (!is_array($cache))
424 {
425 $cache = [];
426 }
427
428 if (!is_array($cacheFull))
429 {
430 $cacheFull = [];
431 }
432
433 $fileList = $this->fileList[$type][$set['NAME']] ?? [];
434 $targetList = $this->targetList['KERNEL'][$type.'_LIST'][$set['NAME']] ?? [];
435
436 $items = [];
437 if ($mode === $set['MODE'] && isset($fileList['FILES']))
438 {
439 $items = $fileList['FILES'];
440 }
441 elseif (isset($fileList['UP_NEW_FILES']))
442 {
443 $items = $fileList['UP_NEW_FILES'];
444 }
445
446 if (empty($items))
447 {
448 continue;
449 }
450
451 foreach ($items as $item)
452 {
453 $cache[] = $item;
454
455 if (isset($fileList['FULL_FILES'][$item]))
456 {
457 $cacheFull = array_merge($cacheFull, $fileList['FULL_FILES'][$item]);
458 }
459
460 if ($set['PARENT_NAME'] == 'KERNEL')
461 {
462 foreach ($targetList['WHERE_USED'] as $target => $tmp)
463 {
464 $cacheInfo[$mode][$type][$target][] = $item;
465
466 if (isset($fileList['FULL_FILES'][$item]))
467 {
468 if (!isset($cacheInfo[$mode]['BUNDLE_'.$type][$target]))
469 {
470 $cacheInfo[$mode]['BUNDLE_'.$type][$target] = [];
471 }
472
473 $cacheInfo[$mode]['BUNDLE_'.$type][$target] = array_merge(
474 $cacheInfo[$mode]['BUNDLE_'.$type][$target],
475 $fileList['FULL_FILES'][$item]
476 );
477 }
478 }
479 }
480 }
481 }
482 }
483 }
484
485 return [
486 'JS' => $cacheInfo[$mode]['JS'][$id] ?? [],
487 'BUNDLE_JS' => $cacheInfo[$mode]['BUNDLE_JS'][$id] ?? [],
488 'CSS' => $cacheInfo[$mode]['CSS'][$id] ?? [],
489 'BUNDLE_CSS' => $cacheInfo[$mode]['BUNDLE_CSS'][$id] ?? [],
490 'STRINGS' => $cacheInfo[$mode]['STRINGS'][$id] ?? []
491 ];
492 }
493
499 public function compositeTarget($id = '')
500 {
501 $id = strtoupper(trim($id));
502 if ($id == '' || !isset($this->targetList[$id]))
503 {
504 return false;
505 }
506 else
507 {
508 $this->targetList[$id]['MODE'] = AssetMode::COMPOSITE;
509 }
510 return true;
511 }
512
518 public function getTargetList($type = 'CSS')
519 {
520 static $res = ['CSS_LIST' => null, 'JS_LIST' => null];
521 $key = ($type == 'CSS' ? 'CSS_LIST' : 'JS_LIST');
522
523 if ($res[$key] === null)
524 {
525 foreach ($this->targetList as $targetName => $targetInfo)
526 {
527 $res[$key][] = [
528 'NAME' => $targetName,
529 'PARENT_NAME' => $targetName,
530 'UNIQUE' => $targetInfo['UNIQUE'],
531 'PREFIX' => ($targetInfo['PREFIX'] ?? ''),
532 'MODE' => $targetInfo['MODE'],
533 'MODULE_NAME' => ($targetInfo['MODULE_NAME'] ?? ''),
534 ];
535
536 if (!empty($targetInfo[$key]))
537 {
538 foreach ($targetInfo[$key] as $subSetName => $val)
539 {
540 $res[$key][] = [
541 'NAME' => $subSetName,
542 'PARENT_NAME' => $targetName,
543 'UNIQUE' => ($val['UNIQUE'] ?? ''),
544 'PREFIX' => ($val['PREFIX'] ?? ''),
545 'MODE' => ($val['MODE'] ?? 0),
546 'MODULE_NAME' => ($val['MODULE_NAME'] ?? ''),
547 ];
548 }
549 }
550 }
551 }
552 return $res[$key];
553 }
554
563 function addString($str, $unique = false, $location = AssetLocation::AFTER_JS_KERNEL, $mode = null)
564 {
565 if ($str == '')
566 {
567 return false;
568 }
569
570 if ($unique)
571 {
572 $chkSum = md5($str);
573 $this->strings[$location][$chkSum]['CONTENT'] = $str;
574 $this->strings[$location][$chkSum]['TARGET'][] = $this->getTargetName();
575 $this->strings[$location][$chkSum]['MODE'] = $mode;
576 }
577 else
578 {
579 $this->strings[$location][] = ['CONTENT' => $str, 'MODE' => $mode, 'TARGET' => [$this->getTargetName()]];
580 }
581 return true;
582 }
583
589 public function getStrings($location = AssetLocation::AFTER_JS_KERNEL)
590 {
591 static $firstExec = true;
592 if ($firstExec)
593 {
594 $this->prepareString();
595 $firstExec = false;
596 }
597
598 $res = '';
599 if ($location == AssetLocation::AFTER_CSS && \CJSCore::IsCoreLoaded())
600 {
601 $res = "<script>if(!window.BX)window.BX={};if(!window.BX.message)window.BX.message=function(mess){if(typeof mess==='object'){for(let i in mess) {BX.message[i]=mess[i];} return true;}};</script>\n";
602 }
603
604 if (isset($this->strings[$location]))
605 {
606 foreach ($this->strings[$location] as $item)
607 {
608 if ($this->mode & $item['MODE'])
609 {
610 $res .= $item['CONTENT']."\n";
611 }
612 }
613 }
614
615 return ($res == '') ? '' : $res."\n";
616 }
617
624 public function addCss($path, $additional = false)
625 {
626 if ($path == '')
627 {
628 return false;
629 }
630
631 $css = $this->getAssetPath($path);
632 $this->css[$css]['TARGET'][] = $this->getTargetName();
633 $this->css[$css]['ADDITIONAL'] = (isset($this->css[$css]['ADDITIONAL']) && $this->css[$css]['ADDITIONAL'] ? true : $additional);
634 return true;
635 }
636
643 public function addJs($path, $additional = false)
644 {
645 if ($path == '')
646 {
647 return false;
648 }
649
650 $js = $this->getAssetPath($path);
651 $this->js[$js]['TARGET'][] = $this->getTargetName();
652 $this->js[$js]['ADDITIONAL'] = (isset($this->js[$js]['ADDITIONAL']) && $this->js[$js]['ADDITIONAL'] ? true : $additional);
653 return true;
654 }
655
662 public static function fixCssIncludes($content, $path)
663 {
664 $path = IO\Path::getDirectory($path);
665 $content = preg_replace_callback(
666 '#([;\s:]*(?:url|@import)\s*\‍(\s*)(\'|"|)(.+?)(\2)\s*\‍)#si',
667 function ($matches) use ($path)
668 {
669 return $matches[1].Asset::replaceUrlCSS($matches[3], $matches[2], addslashes($path)).")";
670 },
671 $content
672 );
673
674 $content = preg_replace_callback(
675 '#(\s*@import\s*)([\'"])([^\'"]+)(\2)#si',
676 function ($matches) use ($path)
677 {
678 return $matches[1].Asset::replaceUrlCSS($matches[3], $matches[2], addslashes($path));
679 },
680 $content
681 );
682
683 return $content;
684 }
685
692 public function groupJs($from = '', $to = '')
693 {
694 if (empty($from) || empty($to))
695 {
696 return;
697 }
698
699 $to = $this->movedJsTo($to);
700 if (array_key_exists($from, $this->moduleInfo['JS']))
701 {
702 $this->moduleInfo['JS'][$from]['MODULE_ID'] = $to;
703 }
704 else
705 {
706 $this->moduleInfo['JS'][$from] = ['MODULE_ID' => $to, 'FILES_INFO' => false, 'BODY' => false];
707 }
708
709 foreach ($this->moduleInfo['JS'] as $moduleID => $moduleInfo)
710 {
711 if ($moduleInfo['MODULE_ID'] == $from)
712 {
713 $this->moduleInfo['JS'][$moduleID]["MODULE_ID"] = $to;
714 }
715 }
716 }
717
724 public function groupCss($from = '', $to = '')
725 {
726 if (empty($from) || empty($to))
727 {
728 return;
729 }
730
731 $to = $this->movedCssTo($to);
732 if (array_key_exists($from, $this->moduleInfo['CSS']))
733 {
734 $this->moduleInfo['CSS'][$from]['MODULE_ID'] = $to;
735 }
736 else
737 {
738 $this->moduleInfo['CSS'][$from] = ['MODULE_ID' => $to, 'FILES_INFO' => false];
739 }
740
741 foreach ($this->moduleInfo['CSS'] as $moduleID => $moduleInfo)
742 {
743 if($moduleInfo['MODULE_ID'] == $from)
744 {
745 $this->moduleInfo['CSS'][$moduleID]["MODULE_ID"] = $to;
746 }
747 }
748 }
749
754 private function movedJsTo($to)
755 {
756 if (isset($this->moduleInfo['JS'][$to]['MODULE_ID']) && $this->moduleInfo['JS'][$to]['MODULE_ID'] != $to)
757 {
758 $to = $this->movedJsTo($this->moduleInfo['JS'][$to]['MODULE_ID']);
759 }
760
761 return $to;
762 }
763
768 private function movedCssTo($to)
769 {
770 if (isset($this->moduleInfo['CSS'][$to]['MODULE_ID']) && $this->moduleInfo['CSS'][$to]['MODULE_ID'] != $to)
771 {
772 $to = $this->movedCssTo($this->moduleInfo['JS'][$to]['MODULE_ID']);
773 }
774
775 return $to;
776 }
777
783 public function moveJs($module = '')
784 {
785 if (empty($module) || $module === "main")
786 {
787 return;
788 }
789
790 if (array_key_exists($module, $this->moduleInfo['JS']))
791 {
792 $this->moduleInfo['JS'][$module]['BODY'] = true;
793 }
794 else
795 {
796 $this->moduleInfo['JS'][$module] = ['MODULE_ID' => $module, 'FILES_INFO' => false, 'BODY' => true];
797 }
798 }
799
805 public function setJsToBody($flag)
806 {
807 $this->moveJsToBody = (bool)$flag;
808 }
809
813 protected function getJsToBody()
814 {
815 if ($this->moveJsToBody === null)
816 {
817 $this->moveJsToBody = Option::get("main", "move_js_to_body") === "Y" && (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true);
818 }
819 return $this->moveJsToBody;
820 }
821
828 public function moveJsToBody(&$content)
829 {
830 if (!$this->getJsToBody())
831 {
832 return;
833 }
834
835 $js = "";
836 $offset = 0;
837 $newContent = "";
838 $areas = $this->getScriptAreas($content);
839 foreach ($areas as $area)
840 {
841 if (str_contains($area->attrs, "data-skip-moving") || !self::isValidScriptType($area->attrs))
842 {
843 continue;
844 }
845
846 $js .= substr($content, $area->openTagStart, $area->closingTagEnd - $area->openTagStart);
847 $newContent .= substr($content, $offset, $area->openTagStart - $offset);
848 $offset = $area->closingTagEnd;
849 }
850
851 if ($js === "")
852 {
853 return;
854 }
855
856 $newContent .= substr($content, $offset);
857 $bodyEnd = strripos($newContent, "</body>");
858 if ($bodyEnd === false)
859 {
860 $content = $newContent.$js;
861 }
862 else
863 {
864 $content = substr_replace($newContent, $js, $bodyEnd, 0);
865 }
866 }
867
873 private function getScriptAreas($content)
874 {
875 $openTag = "<script";
876 $closingTag = "</script";
877 $ending = ">";
878
879 $offset = 0;
880 $areas = [];
881 $content = strtolower($content);
882 while (($openTagStart = strpos($content, $openTag, $offset)) !== false)
883 {
884 $endingPos = strpos($content, $ending, $openTagStart);
885 if ($endingPos === false)
886 {
887 break;
888 }
889
890 $attrsStart = $openTagStart + strlen($openTag);
891 $attrs = substr($content, $attrsStart, $endingPos - $attrsStart);
892 $openTagEnd = $endingPos + strlen($ending);
893
894 $realClosingTag = $closingTag.$ending;
895 $closingTagStart = strpos($content, $realClosingTag, $openTagEnd);
896 if ($closingTagStart === false)
897 {
898 $offset = $openTagEnd;
899 continue;
900 }
901
902 $closingTagEnd = $closingTagStart + strlen($realClosingTag);
903 while (isset($content[$closingTagEnd]) && $content[$closingTagEnd] === "\n")
904 {
905 $closingTagEnd++;
906 }
907
908 $area = new \stdClass();
909 $area->attrs = $attrs;
910 $area->openTagStart = $openTagStart;
911 $area->openTagEnd = $openTagEnd;
912 $area->closingTagStart = $closingTagStart;
913 $area->closingTagEnd = $closingTagEnd;
914 $areas[] = $area;
915
916 $offset = $closingTagEnd;
917 }
918
919 return $areas;
920 }
921
925 public function canMoveJsToBody()
926 {
927 return
928 $this->getJsToBody() &&
929 !Main\Application::getInstance()->getContext()->getRequest()->isAjaxRequest() &&
930 !defined("BX_BUFFER_SHUTDOWN");
931 }
932
939 private static function isValidScriptType($attrs)
940 {
941 if ($attrs === "" || !preg_match("/type\\s*=\\s*(['\"]?)(.*?)\\1/i", $attrs, $match))
942 {
943 return true;
944 }
945
946 $type = mb_strtolower($match[2]);
947 return $type === "" || $type === "text/javascript" || $type === "application/javascript";
948 }
949
950
958 public static function replaceUrlCss($url, $quote, $path)
959 {
960 if (
961 str_contains($url, "://")
962 || str_contains($url, "data:")
963 || str_starts_with($url, "#")
964 )
965 {
966 return $quote.$url.$quote;
967 }
968
969 $url = trim(stripslashes($url), "'\" \r\n\t");
970 if (mb_substr($url, 0, 1) == "/")
971 {
972 return $quote.$url.$quote;
973 }
974
975 return $quote.$path.'/'.$url.$quote;
976 }
977
983 public static function getAssetPath($src)
984 {
985 if (($p = mb_strpos($src, "?")) > 0 && !\CMain::IsExternalLink($src))
986 {
987 $src = mb_substr($src, 0, $p);
988 }
989 return $src;
990 }
991
995 public function optimizeCss()
996 {
997 $optimize = $this->optimizeCss
998 && (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true)
999 && Option::get('main', 'optimize_css_files', 'N') == 'Y'
1000 && !$this->ajax;
1001
1002 return $optimize;
1003 }
1004
1008 public function optimizeJs()
1009 {
1010 $optimize = $this->optimizeJs
1011 && (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true)
1012 && Option::get('main', 'optimize_js_files', 'N') == 'Y'
1013 && !$this->ajax;
1014
1015 return $optimize;
1016 }
1017
1021 public static function canUseMinifiedAssets()
1022 {
1023 static $canLoad = null;
1024 if ($canLoad === null)
1025 {
1026 $canLoad = Option::get("main","use_minified_assets", "Y") == "Y";
1027 }
1028
1029 return $canLoad;
1030 }
1031
1035 public function sliceKernel()
1036 {
1037 return (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true);
1038 }
1039
1047 public function insertCss($css, $label = false, $inline = false)
1048 {
1049 if ($label === true)
1050 {
1051 $label = ' data-template-style="true" ';
1052 }
1053 elseif ($label === false)
1054 {
1055 $label = '';
1056 }
1057
1058 if ($inline)
1059 {
1060 return "<style type=\"text/css\" {$label}>\n{$css}\n</style>\n";
1061 }
1062 else
1063 {
1064 return "<link href=\"{$css}\" type=\"text/css\" {$label} rel=\"stylesheet\" {$this->xhtmlStyle}>\n";
1065 }
1066 }
1067
1075 public function insertJs($js, $label = '', $inline = false)
1076 {
1077 if ($inline)
1078 {
1079 return "<script {$label}>\n{$js}\n</script>\n";
1080 }
1081 else
1082 {
1083 return "<script {$label} src=\"$js\"></script>\n";
1084 }
1085 }
1086
1091 private function setTemplateID()
1092 {
1093 static $firstExec = true;
1094 if ($firstExec && !$this->ajax && (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true))
1095 {
1096 if (defined("SITE_TEMPLATE_PREVIEW_MODE"))
1097 {
1098 $this->templatePath = BX_PERSONAL_ROOT.'/tmp/templates/__bx_preview';
1099 }
1100 elseif (defined('SITE_TEMPLATE_ID'))
1101 {
1102 $this->siteTemplateID = SITE_TEMPLATE_ID;
1103 $this->templatePath = SITE_TEMPLATE_PATH;
1104 }
1105 else
1106 {
1107 $this->siteTemplateID = '.default';
1108 $this->templatePath = BX_PERSONAL_ROOT."/templates/.default";
1109 }
1110 $firstExec = false;
1111 }
1112 }
1113
1118 private function addTemplateCss()
1119 {
1120 if (
1121 !$this->ajax
1122 && (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true)
1123 && $this->templateExists
1124 )
1125 {
1126 $this->css[$this->templatePath . '/styles.css']['TARGET'][] = 'TEMPLATE';
1127 $this->css[$this->templatePath . '/styles.css']['ADDITIONAL'] = false;
1128
1129 $this->css[$this->templatePath . '/template_styles.css']['TARGET'][] = 'TEMPLATE';
1130 $this->css[$this->templatePath . '/template_styles.css']['ADDITIONAL'] = false;
1131 }
1132 }
1133
1138 private function prepareString()
1139 {
1140 foreach ($this->strings as $location => $stringLocation)
1141 {
1142 foreach ($stringLocation as $key => $item)
1143 {
1145 $this->strings[$location][$key]['MODE'] = ($item['MODE'] === null ? $this->targetList[$item['TARGET'][0]]['MODE'] : $item['MODE']);
1146 }
1147 }
1148 }
1149
1157 private function getAssetPaths($assetPath)
1158 {
1159 $paths = [$assetPath];
1160 if (self::canUseMinifiedAssets() && preg_match("/(.+)\\.(js|css)$/i", $assetPath, $matches))
1161 {
1162 array_unshift($paths, $matches[1].".min.".$matches[2]);
1163 }
1164
1165 $result = null;
1166 $maxMtime = 0;
1167 foreach ($paths as $path)
1168 {
1169 $filePath = $this->documentRoot.$path;
1170 if (file_exists($filePath) && ($mtime = filemtime($filePath)) > $maxMtime && filesize($filePath) > 0)
1171 {
1172 $maxMtime = $mtime;
1173 $result = [
1174 "PATH" => $path,
1175 "FILE_PATH" => $filePath,
1176 "FULL_PATH" => \CUtil::GetAdditionalFileURL($path, true),
1177 ];
1178 }
1179 }
1180
1181 return $result;
1182 }
1183
1192 public function getFullAssetPath($sourcePath)
1193 {
1194 $result = $this->getAssetPaths($sourcePath);
1195
1196 if (is_array($result))
1197 {
1198 return $result["FULL_PATH"];
1199 }
1200 if (\CMain::IsExternalLink($sourcePath))
1201 {
1202 return $sourcePath;
1203 }
1204
1205 return null;
1206 }
1207
1214 private function prepareCss() : void
1215 {
1216 $additional = [];
1217
1218 foreach ($this->css as $css => $set)
1219 {
1221 $assetTID = $set['ADDITIONAL'] ? 'TEMPLATE' : $set['TARGET'][0];
1222 $cssInfo = [
1223 'PATH' => $css,
1224 'FULL_PATH' => false,
1225 'FILE_PATH' => false,
1226 'SKIP' => false,
1227 'TARGET' => $assetTID,
1228 'EXTERNAL' => \CMain::IsExternalLink($css),
1229 'ADDITIONAL' => $set['ADDITIONAL']
1230 ];
1231
1232 if ($cssInfo['EXTERNAL'])
1233 {
1234 if ($set['ADDITIONAL'])
1235 {
1236 $tmpKey = 'TEMPLATE';
1237 $tmpPrefix = 'template';
1238 }
1239 else
1240 {
1241 $tmpKey = 'KERNEL';
1242 $tmpPrefix = 'kernel';
1243 }
1244
1245 $cssInfo['MODULE_ID'] = $this->assetCSSCnt;
1246 $cssInfo['TARGET'] = $tmpKey.'_'.$this->assetCSSCnt;
1247 $cssInfo['PREFIX'] = $tmpPrefix.'_'.$this->assetCSSCnt;
1248 $cssInfo['FULL_PATH'] = $cssInfo['PATH'];
1249 $cssInfo['SKIP'] = true;
1250 $this->assetCSSCnt++;
1251
1252 $this->targetList[$tmpKey]['CSS_LIST'][$cssInfo['TARGET']] = [
1253 'TARGET' => $cssInfo['TARGET'],
1254 'PREFIX' => $cssInfo['PREFIX'],
1255 'MODE' => $this->targetList[$assetTID]['MODE'],
1256 'UNIQUE' => false,
1257 'WHERE_USED' => []
1258 ];
1259 }
1260 else
1261 {
1262 if (($paths = $this->getAssetPaths($css)) !== null)
1263 {
1264 $cssInfo["PATH"] = $css;
1265 $cssInfo["FILE_PATH"] = $paths["FILE_PATH"];
1266 $cssInfo["FULL_PATH"] = $paths["FULL_PATH"];
1267 }
1268 else
1269 {
1270 unset($this->css[$css]);
1271 continue;
1272 }
1273
1274 $moduleInfo = $this->isKernelCSS($cssInfo['PATH']);
1275 if ($moduleInfo)
1276 {
1277 if ($this->sliceKernel() && $this->optimizeCss() && is_array($moduleInfo))
1278 {
1279 $cssInfo['MODULE_ID'] = $moduleInfo['MODULE_ID'];
1280 $cssInfo['TARGET'] = 'KERNEL_'.$moduleInfo['MODULE_ID'];
1281 $cssInfo['PREFIX'] = 'kernel_'.$moduleInfo['MODULE_ID'];
1282 $cssInfo['SKIP'] = $moduleInfo['SKIP'] ?? false;
1283 }
1284 else
1285 {
1286 $cssInfo['MODULE_ID'] = $this->assetCSSCnt;
1287 $cssInfo['TARGET'] = 'KERNEL_'.$this->assetCSSCnt;
1288 $cssInfo['PREFIX'] = 'kernel_'.$this->assetCSSCnt;
1289 $cssInfo['SKIP'] = true;
1290 $this->assetCSSCnt++;
1291 }
1292
1293 if (isset($this->targetList['KERNEL']['CSS_LIST'][$cssInfo['TARGET']]['MODE']))
1294 {
1295 $this->targetList['KERNEL']['CSS_LIST'][$cssInfo['TARGET']]['MODE'] |= $this->targetList[$assetTID]['MODE'];
1296 }
1297 else
1298 {
1299 $this->targetList['KERNEL']['CSS_LIST'][$cssInfo['TARGET']] = [
1300 'TARGET' => $cssInfo['TARGET'],
1301 'PREFIX' => $cssInfo['PREFIX'],
1302 'MODE' => $set['ADDITIONAL'] ? $this->targetList[$set['TARGET'][0]]['MODE'] : $this->targetList[$assetTID]['MODE'],
1303 'UNIQUE' => true,
1304 'WHERE_USED' => []
1305 ];
1306 }
1307
1308 if (is_array($moduleInfo))
1309 {
1310 $this->targetList['KERNEL']['CSS_LIST'][$cssInfo['TARGET']]['MODULE_NAME'] = $moduleInfo['MODULE_ID'];
1311 }
1312
1313 // Add information about sets where used
1314 foreach ($set['TARGET'] as $setID)
1315 {
1316 $this->targetList['KERNEL']['CSS_LIST'][$cssInfo['TARGET']]['WHERE_USED'][$setID] = true;
1317 }
1318 }
1319 elseif (strncmp($cssInfo['PATH'], '/bitrix/js/', 11) != 0 /*||*/ )
1320 {
1321 $cssInfo['SKIP'] = !(
1322 strncmp($cssInfo['PATH'], '/bitrix/panel/', 14) != 0
1323 && strncmp($cssInfo['PATH'], '/bitrix/themes/', 15) != 0
1324 && strncmp($cssInfo['PATH'], '/bitrix/modules/', 16) != 0
1325 );
1326 }
1327 }
1328
1329 if ($cssInfo['ADDITIONAL'])
1330 {
1331 $additional[] = $cssInfo;
1332 }
1333 else
1334 {
1335 $this->css[$cssInfo['TARGET']][] = $cssInfo;
1336 }
1337
1338 unset($this->css[$css]);
1339 }
1340
1341 foreach ($additional as $cssInfo)
1342 {
1343 $this->css[$cssInfo['TARGET']][] = $cssInfo;
1344 }
1345 }
1346
1353 private function prepareJs()
1354 {
1355 $additional = [];
1356 foreach ($this->js as $js => $set)
1357 {
1359 $assetTID = $set['ADDITIONAL'] ? 'TEMPLATE' : $set['TARGET'][0];
1360 $jsInfo = [
1361 'PATH' => $js,
1362 'FULL_PATH' => false,
1363 'FILE_PATH' => false,
1364 'SKIP' => false,
1365 'TARGET' => $assetTID,
1366 'EXTERNAL' => \CMain::IsExternalLink($js),
1367 'BODY' => false,
1368 'ADDITIONAL' => $set['ADDITIONAL']
1369 ];
1370
1371 if ($jsInfo['EXTERNAL'])
1372 {
1373 if ($set['ADDITIONAL'])
1374 {
1375 $tmpKey = 'TEMPLATE';
1376 $tmpPrefix = 'template';
1377 }
1378 else
1379 {
1380 $tmpKey = 'KERNEL';
1381 $tmpPrefix = 'kernel';
1382 }
1383
1384 $jsInfo['MODULE_ID'] = $this->assetJSCnt;
1385 $jsInfo['TARGET'] = $tmpKey.'_'.$this->assetJSCnt;
1386 $jsInfo['PREFIX'] = $tmpPrefix.'_'.$this->assetJSCnt;
1387 $jsInfo['FULL_PATH'] = $jsInfo['PATH'];
1388 $jsInfo['SKIP'] = true;
1389 $this->assetJSCnt++;
1390
1391 $this->targetList[$tmpKey]['JS_LIST'][$jsInfo['TARGET']] = [
1392 'TARGET' => $jsInfo['TARGET'],
1393 'PREFIX' => $jsInfo['PREFIX'],
1394 'MODE' => $this->targetList[$assetTID]['MODE'],
1395 'UNIQUE' => false,
1396 'WHERE_USED' => []
1397 ];
1398 }
1399 else
1400 {
1401 if (($paths = $this->getAssetPaths($js)) !== null)
1402 {
1403 $jsInfo["PATH"] = $js;
1404 $jsInfo["FILE_PATH"] = $paths["FILE_PATH"];
1405 $jsInfo["FULL_PATH"] = $paths["FULL_PATH"];
1406 }
1407 else
1408 {
1409 unset($this->js[$js]);
1410 continue;
1411 }
1412
1413 if ($moduleInfo = $this->isKernelJS($jsInfo['PATH']))
1414 {
1415 if ($this->sliceKernel() && $this->optimizeJs())
1416 {
1417 $jsInfo['MODULE_ID'] = $moduleInfo['MODULE_ID'];
1418 $jsInfo['TARGET'] = 'KERNEL_'.$moduleInfo['MODULE_ID'];
1419 $jsInfo['PREFIX'] = 'kernel_'.$moduleInfo['MODULE_ID'];
1420 $jsInfo['SKIP'] = $moduleInfo['SKIP'] ?? false;
1421 $jsInfo['BODY'] = $moduleInfo['BODY'];
1422 }
1423 else
1424 {
1425 $jsInfo['MODULE_ID'] = $this->assetJSCnt;
1426 $jsInfo['TARGET'] = 'KERNEL_'.$this->assetJSCnt;
1427 $jsInfo['PREFIX'] = 'kernel_'.$this->assetJSCnt;
1428 $jsInfo['SKIP'] = true;
1429 $this->assetJSCnt++;
1430 }
1431
1432 if ($jsInfo['BODY'])
1433 {
1434 $this->targetList['BODY']['JS_LIST'][$jsInfo['TARGET']] = [
1435 'TARGET' => $jsInfo['TARGET'],
1436 'PREFIX' => $jsInfo['PREFIX'],
1437 'MODE' => $this->targetList[$assetTID]['MODE'],
1438 'UNIQUE' => true,
1439 'WHERE_USED' => []
1440 ];
1441 }
1442 else
1443 {
1444 if (isset($this->targetList['KERNEL']['JS_LIST'][$jsInfo['TARGET']]['MODE']))
1445 {
1446 $this->targetList['KERNEL']['JS_LIST'][$jsInfo['TARGET']]['MODE'] |= $this->targetList[$assetTID]['MODE'];
1447 }
1448 else
1449 {
1450 $this->targetList['KERNEL']['JS_LIST'][$jsInfo['TARGET']] = [
1451 'TARGET' => $jsInfo['TARGET'],
1452 'PREFIX' => $jsInfo['PREFIX'],
1453 'MODE' => $set['ADDITIONAL'] ? $this->targetList[$set['TARGET'][0]]['MODE'] : $this->targetList[$assetTID]['MODE'],
1454 'UNIQUE' => true,
1455 'WHERE_USED' => []
1456 ];
1457 }
1458 }
1459
1460 // Add information about sets where used
1461 foreach ($set['TARGET'] as $setID)
1462 {
1463 $this->targetList['KERNEL']['JS_LIST'][$jsInfo['TARGET']]['WHERE_USED'][$setID] = true;
1464 }
1465 }
1466 elseif (strncmp($jsInfo['PATH'], '/bitrix/js/', 11) != 0)
1467 {
1468 $jsInfo['SKIP'] = !(
1469 strncmp($jsInfo['PATH'], '/bitrix/panel/', 14) != 0
1470 && strncmp($jsInfo['PATH'], '/bitrix/themes/', 15) != 0
1471 && strncmp($jsInfo['PATH'], '/bitrix/modules/', 16) != 0
1472 );
1473 }
1474 }
1475
1476 if ($jsInfo['ADDITIONAL'])
1477 {
1478 $additional[] = $jsInfo;
1479 }
1480 else
1481 {
1482 $this->js[$jsInfo['TARGET']][] = $jsInfo;
1483 }
1484 unset($this->js[$js]);
1485 }
1486
1487 // Clean body scripts
1488 foreach ($this->targetList['BODY']['JS_LIST'] as $item)
1489 {
1490 unset($this->targetList['KERNEL']['JS_LIST'][$item['TARGET']]);
1491 }
1492
1493 foreach ($additional as $jsInfo)
1494 {
1495 $this->js[$jsInfo['TARGET']][] = $jsInfo;
1496 }
1497 }
1498
1506 public function getCss($type = AssetShowTargetType::ALL)
1507 {
1508 $res = '';
1509 $additional = [];
1510 static $setList = [];
1511 static $ajaxList = [];
1512
1513 if (empty($setList))
1514 {
1515 $this->setTemplateID();
1516 $this->addTemplateCss();
1517 $this->prepareCss();
1518 $setList = $this->getTargetList();
1519 $optimizeCss = $this->optimizeCss();
1520
1521 foreach ($setList as $setInfo)
1522 {
1523 if (!isset($this->css[$setInfo['NAME']]))
1524 {
1525 continue;
1526 }
1527
1528 $data = '';
1529 if (!empty($this->moduleInfo['CSS'][$setInfo['MODULE_NAME']]['DATA']))
1530 {
1531 $data = $this->moduleInfo['CSS'][$setInfo['MODULE_NAME']]['DATA'];
1532 }
1533
1534 $location = '';
1535 if (!empty($this->moduleInfo['CSS'][$setInfo['MODULE_NAME']]['LOCATION']))
1536 {
1537 $location = $this->moduleInfo['CSS'][$setInfo['MODULE_NAME']]['LOCATION'];
1538 }
1539
1540 $resCss = '';
1541 $listAsset = [];
1542 $showLabel = ($setInfo['NAME'] == 'TEMPLATE');
1543
1544 foreach ($this->css[$setInfo['NAME']] as $cssFile)
1545 {
1546 $css = $cssFile['FULL_PATH'];
1547 if ($this->ajax)
1548 {
1549 $this->assetList['CSS'][] = $cssFile['PATH'];
1550 $ajaxList[] = $css;
1551 }
1552 elseif ($cssFile['EXTERNAL'])
1553 {
1554 $resCss .= $this->insertCss($css, $showLabel);
1555 $this->fileList['CSS'][$setInfo['NAME']]['FILES'][] = $css;
1556 }
1557 elseif ($optimizeCss)
1558 {
1559 if ($cssFile['SKIP'])
1560 {
1561 $resCss .= $this->insertCss($css, $showLabel);
1562 $this->fileList['CSS'][$setInfo['NAME']]['FILES'][] = $css;
1563 }
1564 else
1565 {
1566 $listAsset[] = $cssFile;
1567 }
1568 }
1569 else
1570 {
1571 $resCss .= $this->insertCss($css, $showLabel);
1572 $this->fileList['CSS'][$setInfo['NAME']]['FILES'][] = $css;
1573 }
1574 }
1575
1576 $optimizedAsset = $this->optimizeAsset($listAsset, $setInfo['UNIQUE'], $setInfo['PREFIX'], $setInfo['NAME'], 'css', $data);
1577
1578 $resCss = $optimizedAsset['RESULT'].$resCss;
1579 if ($location == AssetLocation::AFTER_CSS)
1580 {
1581 $additional[] = [
1582 'FILES' => $optimizedAsset['FILES'],
1583 'SOURCE_FILES' => $optimizedAsset['SOURCE_FILES'],
1584 'RES' => $resCss
1585 ];
1586 }
1587 else
1588 {
1589 $this->assetList['CSS'][$setInfo['PARENT_NAME']][$setInfo['NAME']] = $optimizedAsset['FILES'];
1590 $this->assetList['SOURCE_CSS'][$setInfo['PARENT_NAME']][$setInfo['NAME']] = ($optimizedAsset['SOURCE_FILES'] ?? []);
1591 $this->targetList[$setInfo['PARENT_NAME']]['CSS_RES'][$setInfo['NAME']][] = $resCss;
1592 }
1593 }
1594
1595 foreach ($additional as $bundle)
1596 {
1597 $templateFiles = $this->assetList['CSS']['TEMPLATE']['TEMPLATE'] ?? [];
1598
1599 $this->assetList['CSS']['TEMPLATE']['TEMPLATE'] = array_merge($templateFiles, $bundle['FILES']);
1600 $this->assetList['SOURCE_CSS']['TEMPLATE']['TEMPLATE'] = array_merge($templateFiles, $bundle['SOURCE_FILES']);
1601 $this->targetList['TEMPLATE']['CSS_RES']['TEMPLATE'][] = $bundle['RES'];
1602 }
1603
1604 unset($additional, $templateFiles, $bundle);
1605 }
1606
1607 if ($this->ajax && !empty($ajaxList))
1608 {
1609 $res .= '<script>'."BX.loadCSS(['".implode("','", $ajaxList)."']);".'</script>';
1610 }
1611
1612 if ($type == AssetShowTargetType::KERNEL)
1613 {
1614 $res .= $this->showAsset($setList, 'css', 'KERNEL');
1615 }
1616 elseif ($type == AssetShowTargetType::TEMPLATE_PAGE)
1617 {
1618 foreach ($this->targetList as $setName => $set)
1619 {
1620 if ($setName != 'TEMPLATE' && $setName != 'KERNEL')
1621 {
1622 $res .= $this->showAsset($setList, 'css', $setName);
1623 }
1624 }
1625
1626 $res .= $this->showAsset($setList, 'css', 'TEMPLATE');
1627 }
1628 else
1629 {
1630 foreach ($this->targetList as $setName => $set)
1631 {
1632 if ($setName != 'TEMPLATE')
1633 {
1634 $res .= $this->showAsset($setList, 'css', $setName);
1635 }
1636 }
1637
1638 $res .= $this->showAsset($setList, 'css', 'TEMPLATE');
1639 }
1640
1641 return $res;
1642 }
1643
1651 function getJs($type = AssetShowTargetType::ALL)
1652 {
1653 static $setList = [];
1654
1655 $res = '';
1656 $type = (int) $type;
1657 $type = (($type == AssetShowTargetType::KERNEL && $this->headString && !$this->headScript) ? AssetShowTargetType::ALL : $type);
1658 $optimize = $this->optimizeJs();
1659 if (empty($setList))
1660 {
1661 $this->prepareJs();
1662 $setList = $this->getTargetList('JS');
1663
1664 foreach ($setList as $setInfo)
1665 {
1666 if (!isset($this->js[$setInfo['NAME']]))
1667 {
1668 continue;
1669 }
1670
1671 $data = '';
1672 if (!empty($this->moduleInfo['JS'][$setInfo['MODULE_NAME']]['DATA']))
1673 {
1674 $data = $this->moduleInfo['JS'][$setInfo['MODULE_NAME']]['DATA'];
1675 }
1676
1677 $resJs = '';
1678 $listAsset = [];
1679 foreach ($this->js[$setInfo['NAME']] as $jsFile)
1680 {
1681 $js = $jsFile['FULL_PATH'];
1682 if ($optimize)
1683 {
1684 if ($jsFile['SKIP'])
1685 {
1686 $this->fileList['JS'][$setInfo['NAME']]['FILES'][] = $js;
1687 $resJs .= "<script src=\"{$js}\"></script>\n";
1688 }
1689 else
1690 {
1691 $listAsset[] = $jsFile;
1692 }
1693 }
1694 else
1695 {
1696 $this->fileList['JS'][$setInfo['NAME']]['FILES'][] = $js;
1697 $resJs .= "<script src=\"{$js}\"></script>\n";
1698 }
1699 }
1700 $optAsset = $this->optimizeAsset($listAsset, $setInfo['UNIQUE'], $setInfo['PREFIX'], $setInfo['NAME'], 'js', $data);
1701 $this->assetList['JS'][$setInfo['PARENT_NAME']][$setInfo['NAME']] = $optAsset['FILES'];
1702 $this->assetList['SOURCE_JS'][$setInfo['PARENT_NAME']][$setInfo['NAME']] = ($optAsset['SOURCE_FILES'] ?? []);
1703 $this->targetList[$setInfo['PARENT_NAME']]['JS_RES'][$setInfo['NAME']][] = $optAsset['RESULT'].$resJs;
1704 }
1705 unset($optAsset, $resJs, $listAsset);
1706 }
1707
1708 if ($type == AssetShowTargetType::KERNEL && ($this->mode & $this->targetList['KERNEL']['MODE']))
1709 {
1710 $setName = 'KERNEL';
1711 $res .= $this->getStrings(AssetLocation::AFTER_CSS);
1712 $res .= $this->showAsset($setList,'js', $setName);
1713 $res .= $this->showFilesList();
1714 $res .= $this->getStrings(AssetLocation::AFTER_JS_KERNEL);
1715
1716 if (!$this->bodyScript)
1717 {
1718 $res .= $this->getStrings(AssetLocation::BODY_END);
1719 $res .= $this->showAsset($setList,'js', 'BODY');
1720 }
1721 }
1722 elseif ($type == AssetShowTargetType::TEMPLATE_PAGE)
1723 {
1724 foreach ($this->targetList as $setName => $set)
1725 {
1726 if ($setName != 'KERNEL' && $setName != 'BODY')
1727 {
1728 $setName = $this->fixJsSetOrder($setName);
1729 $res .= $this->showAsset($setList,'js', $setName);
1730 }
1731 }
1732 $res .= $this->getStrings(AssetLocation::AFTER_JS);
1733 }
1734 elseif ($type == AssetShowTargetType::BODY && ($this->mode & $this->targetList['BODY']['MODE']))
1735 {
1736 $setName = 'BODY';
1737 $res .= $this->getStrings(AssetLocation::BODY_END);
1738 $res .= $this->showAsset($setList,'js', $setName);
1739 }
1740 else
1741 {
1742 foreach ($this->targetList as $setName => $set)
1743 {
1744 if ($this->mode & $set['MODE'])
1745 {
1746 $setName = $this->fixJsSetOrder($setName);
1747 if ($setName == 'KERNEL')
1748 {
1749 $res .= $this->getStrings(AssetLocation::AFTER_CSS);
1750 $res .= $this->showAsset($setList, 'js', $setName);
1751 $res .= $this->showFilesList();
1752 $res .= $this->getStrings(AssetLocation::AFTER_JS_KERNEL);
1753
1754 if (!$this->bodyScript)
1755 {
1756 $res .= $this->getStrings(AssetLocation::BODY_END);
1757 $res .= $this->showAsset($setList,'js', 'BODY');
1758 }
1759 }
1760 elseif ($setName != 'BODY')
1761 {
1762 $res .= $this->showAsset($setList, 'js', $setName);
1763 }
1764 }
1765 }
1766
1767 $res .= $this->getStrings(AssetLocation::AFTER_JS);
1768 }
1769
1770 return (trim($res) == '' ? $res : $res."\n");
1771 }
1772
1778 public static function getLocationByName($location)
1779 {
1780 if ($location === false || $location === 'DEFAULT')
1781 {
1782 $location = AssetLocation::AFTER_JS_KERNEL;
1783 }
1784 elseif ($location === true)
1785 {
1786 $location = AssetLocation::AFTER_CSS;
1787 }
1788
1789 return $location;
1790 }
1791
1796 public function showFilesList()
1797 {
1798 $res = '';
1799 if (!\CJSCore::IsCoreLoaded())
1800 {
1801 return $res;
1802 }
1803
1804 if (!empty($this->assetList['JS']))
1805 {
1806 $assets = [];
1807 foreach ($this->getTargetList('JS') as $set)
1808 {
1809 if ($this->mode & $set['MODE']
1810 && isset($this->assetList['SOURCE_JS'][$set['PARENT_NAME']][$set['NAME']])
1811 && is_array($this->assetList['SOURCE_JS'][$set['PARENT_NAME']][$set['NAME']]))
1812 {
1813 $assets = array_merge($assets, $this->assetList['SOURCE_JS'][$set['PARENT_NAME']][$set['NAME']]);
1814 }
1815 }
1816
1817 if (!empty($assets))
1818 {
1819 $res .= '<script>BX.setJSList('.\CUtil::phpToJSObject($assets).');</script>';
1820 $res .= "\n";
1821 }
1822 }
1823
1824 if (!empty($this->assetList['CSS']))
1825 {
1826 $assets = [];
1827 foreach ($this->getTargetList('CSS') as $set)
1828 {
1829 if ($this->mode & $set['MODE']
1830 && isset($this->assetList['SOURCE_CSS'][$set['PARENT_NAME']][$set['NAME']])
1831 && is_array($this->assetList['SOURCE_CSS'][$set['PARENT_NAME']][$set['NAME']])
1832 )
1833 {
1834 $assets = array_merge($assets, $this->assetList['SOURCE_CSS'][$set['PARENT_NAME']][$set['NAME']]);
1835 }
1836 }
1837
1838 if (!empty($assets))
1839 {
1840 $res .= '<script>BX.setCSSList('.\CUtil::phpToJSObject($assets).');</script>';
1841 $res .= "\n";
1842 }
1843 }
1844 return $res;
1845 }
1846
1854 function addCssKernelInfo($module = '', $css = [], $settings = [])
1855 {
1856 if (empty($module) || empty($css))
1857 {
1858 return;
1859 }
1860
1861 if (!array_key_exists($module, $this->moduleInfo['CSS']))
1862 {
1863 $this->moduleInfo['CSS'][$module] = [
1864 'MODULE_ID' => $module,
1865 'BODY' => false,
1866 'FILES_INFO' => true,
1867 'IS_KERNEL' => true,
1868 'DATA' => '',
1869 'SKIP' => false
1870 ];
1871 }
1872
1873 foreach ($css as $key)
1874 {
1875 $key = self::getAssetPath($key);
1876 $this->kernelAsset['CSS'][$key] = $module;
1877 }
1878
1879 $this->moduleInfo['CSS'][$module]['FILES_INFO'] = true;
1880 if (!empty($settings['DATA']))
1881 {
1882 $this->moduleInfo['CSS'][$module]['DATA'] = $settings['DATA'];
1883 }
1884
1885 if (!empty($settings['LOCATION']))
1886 {
1887 $this->moduleInfo['CSS'][$module]['LOCATION'] = $settings['LOCATION'];
1888 }
1889 }
1890
1898 function addJsKernelInfo($module = '', $js = [], $settings = [])
1899 {
1900 if (empty($module) || empty($js))
1901 {
1902 return;
1903 }
1904
1905 if (!array_key_exists($module, $this->moduleInfo['JS']))
1906 {
1907 $this->moduleInfo['JS'][$module] = [
1908 'MODULE_ID' => $module,
1909 'BODY' => false,
1910 'FILES_INFO' => true,
1911 'IS_KERNEL' => true,
1912 'DATA' => '',
1913 'SKIP' => false
1914 ];
1915 }
1916
1917 foreach ($js as $key)
1918 {
1919 $key = self::getAssetPath($key);
1920 $this->kernelAsset['JS'][$key] = $module;
1921 }
1922
1923 $this->moduleInfo['JS'][$module]['FILES_INFO'] = true;
1924 if (!empty($settings['DATA']))
1925 {
1926 $this->moduleInfo['JS'][$module]['DATA'] = $settings['DATA'];
1927 }
1928 }
1929
1935 function isKernelCSS($css)
1936 {
1938 if (!($this->sliceKernel() && $this->optimizeCss()))
1939 {
1940 return ((strncmp($css, '/bitrix/js/', 11) == 0) || (strncmp($css, '/bitrix/css/', 12) == 0));
1941 }
1942
1944 if (array_key_exists($css, $this->kernelAsset['CSS']))
1945 {
1946 return $this->moduleInfo['CSS'][$this->kernelAsset['CSS'][$css]];
1947 }
1948 elseif ((strncmp($css, '/bitrix/js/', 11) == 0) || (strncmp($css, '/bitrix/css/', 12) == 0))
1949 {
1950 $tmp = explode('/', $css);
1951 $moduleID = $tmp['3'];
1952 unset($tmp);
1953
1954 if (empty($moduleID))
1955 {
1956 return false;
1957 }
1958
1959 return [
1960 'MODULE_ID' => $moduleID.'_'.$this->assetCSSCnt++,
1961 'BODY' => false,
1962 'FILES_INFO' => false,
1963 'IS_KERNEL' => true,
1964 'DATA' => '',
1965 'SKIP' => true
1966 ];
1967 }
1968
1969 return false;
1970 }
1971
1977 function isKernelJS($js)
1978 {
1980 if (!($this->sliceKernel() && $this->optimizeJs()))
1981 {
1982 return (strncmp($js, '/bitrix/js/', 11) == 0);
1983 }
1984
1986 if (array_key_exists($js, $this->kernelAsset['JS']))
1987 {
1988 return $this->moduleInfo['JS'][$this->kernelAsset['JS'][$js]];
1989 }
1990 elseif (strncmp($js, '/bitrix/js/', 11) == 0)
1991 {
1992 $tmp = explode('/', $js);
1993 $moduleID = $tmp['3'];
1994 unset($tmp);
1995
1996 if (empty($moduleID))
1997 {
1998 return false;
1999 }
2000
2001 return [
2002 'MODULE_ID' => $moduleID.'_'.$this->assetJSCnt++,
2003 'BODY' => false,
2004 'FILES_INFO' => false,
2005 'IS_KERNEL' => true,
2006 'DATA' => '',
2007 'SKIP' => true
2008 ];
2009 }
2010
2011 return false;
2012 }
2013
2020 public function setUnique($setID = '', $uniqueID = '')
2021 {
2022 $setID = preg_replace('#[^a-z0-9_]#i', '', $setID);
2023 $uniqueID = preg_replace('#[^a-z0-9_]#i', '', $uniqueID);
2024 if (!(empty($setID) || empty($uniqueID)) && isset($this->targetList[$setID]))
2025 {
2026 $this->targetList[$setID]['UNIQUE'] = true;
2027 $this->targetList[$setID]['PREFIX'] .= ($uniqueID == '' ? '' : '_'.$uniqueID);
2028 return true;
2029 }
2030 return false;
2031 }
2032
2040 private function showAsset($setList = [], $type = 'css', $setName = '')
2041 {
2042 $res = '';
2043 $type = ($type == 'css' ? 'CSS_RES' : 'JS_RES');
2044 $skipCheck = ($setName == '');
2045
2046 foreach ($setList as $setInfo)
2047 {
2048 if (
2049 ($skipCheck || $setName == $setInfo['PARENT_NAME'])
2050 && $this->mode & $setInfo['MODE']
2051 && isset($this->targetList[$setInfo['PARENT_NAME']][$type][$setInfo['NAME']]))
2052 {
2053 $res .= implode("\n", $this->targetList[$setInfo['PARENT_NAME']][$type][$setInfo['NAME']]);
2054 }
2055 }
2056
2057 return $res;
2058 }
2059
2065 private function fixJsSetOrder($setName = '')
2066 {
2067 if ($setName == 'PAGE')
2068 {
2069 $setName = 'TEMPLATE';
2070 }
2071 elseif ($setName == 'TEMPLATE')
2072 {
2073 $setName = 'PAGE';
2074 }
2075
2076 return $setName;
2077 }
2078
2084 public static function getAssetTime($file = '')
2085 {
2086 $qpos = mb_strpos($file, '?');
2087 if ($qpos === false)
2088 {
2089 return false;
2090 }
2091 $qpos++;
2092
2093 return mb_substr($file, $qpos);
2094 }
2095
2101 private function getAssetChecksum($assetList = [])
2102 {
2103 $result = [];
2104 foreach ($assetList as $asset)
2105 {
2106 $result[$asset['PATH']] = $asset['FULL_PATH'];
2107 }
2108 ksort($result);
2109
2110 return md5(implode('_', $result));
2111 }
2112
2121 private function isAssetChanged($assetList = [], $infoFile = '', $optimFile = '', $unique = false)
2122 {
2123 $result = [
2124 'FILE' => [],
2125 'ACTION' => 'NO',
2126 'FILE_EXIST' => false,
2127 'FILES_INFO' => []
2128 ];
2129
2130 if (file_exists($infoFile) && file_exists($optimFile))
2131 {
2133 include($infoFile);
2134
2136 $result['FILES_INFO'] = is_array($filesInfo) ? $filesInfo : [];
2137 $result['FILE_EXIST'] = true;
2138 if ($unique)
2139 {
2140 if (is_array($filesInfo))
2141 {
2142 foreach ($assetList as $asset)
2143 {
2144 if (isset($filesInfo[$asset['PATH']]))
2145 {
2146 if ($this->getAssetTime($asset['FULL_PATH']) != $filesInfo[$asset['PATH']])
2147 {
2148 $result = [
2149 'FILE' => $assetList,
2150 'ACTION' => 'NEW',
2151 'FILES_INFO' => []
2152 ];
2153 break;
2154 }
2155 }
2156 else
2157 {
2158 $result['FILE'][] = $asset;
2159 $result['ACTION'] = 'UP';
2160 }
2161 }
2162 }
2163 else
2164 {
2165 $result = [
2166 'FILE' => $assetList,
2167 'ACTION' => 'NEW',
2168 'FILES_INFO' => []
2169 ];
2170 }
2171 }
2172 }
2173 else
2174 {
2175 $result['FILE'] = $assetList;
2176 $result['ACTION'] = 'NEW';
2177 }
2178
2179 return $result;
2180 }
2181
2193 private function optimizeAsset($files = [], $unique = false, $prefix = 'default', $setName = '', $type = 'css', $data = '')
2194 {
2195 if ((!is_array($files) || empty($files)))
2196 {
2197 return ['RESULT' => '', 'FILES' => []];
2198 }
2199
2200 $this->setTemplateID();
2201 $res = $comments = $contents = '';
2202 $prefix = trim($prefix);
2203 $prefix = mb_strlen($prefix) < 1 ? 'default' : $prefix;
2204 $add2End = (strncmp($prefix, 'kernel', 6) == 0);
2205 $type = ($type == 'js' ? 'js' : 'css');
2206
2207 // when we can't write files
2208 $noCheckOnly = !defined('BX_HEADFILES_CACHE_CHECK_ONLY');
2209 $prefix = ($unique ? $prefix : $prefix.'_'.$this->getAssetChecksum($files));
2210
2211 $optimPath = BX_PERSONAL_ROOT.'/cache/'.$type.'/'.SITE_ID.'/'.$this->siteTemplateID.'/'.$prefix.'/';
2212
2213 $infoFile = $this->documentRoot.BX_PERSONAL_ROOT.'/managed_cache/'.$this->dbType.'/'.$type.'/'.SITE_ID.'/'.$this->siteTemplateID.'/'.$prefix.'/info_v'.self::version.'.php';
2214
2215 $optimFile = $optimPath.$prefix.'_v'.self::version.($type == 'css' ? '.css' : '.js');
2216 $optimFName = $this->documentRoot.$optimFile;
2217
2218 $tmpInfo = $this->isAssetChanged($files, $infoFile, $optimFName, $unique);
2219 $filesInfo = $tmpInfo['FILES_INFO'];
2220 $action = $tmpInfo['ACTION'];
2221 $files = $tmpInfo['FILE'];
2222 $optimFileExist = $tmpInfo['FILE_EXIST'] ?? false;
2223
2224 $writeResult = ($action != 'NEW');
2225 $currentFileList = &$this->fileList[strtoupper($type)][$setName];
2226
2227 if ($action != 'NO')
2228 {
2229 foreach ($tmpInfo['FILE'] as $newFile)
2230 {
2231 $currentFileList['UP_NEW_FILES'][] = $newFile['FULL_PATH'];
2232 }
2233
2234 if ($action == 'UP')
2235 {
2236 if ($noCheckOnly)
2237 {
2238 $contents .= file_get_contents($optimFName);
2239 }
2240 else
2241 {
2242 $writeResult = false;
2243 }
2244 }
2245
2246 $needWrite = false;
2247 if ($noCheckOnly)
2248 {
2249 $newContent = '';
2250 $mapNeeded = false;
2251 foreach ($files as $file)
2252 {
2253 $assetContent = file_get_contents($file['FILE_PATH']);
2254 if ($type == 'css')
2255 {
2256 $comments .= "/* ".$file['FULL_PATH']." */\n";
2257 $assetContent = $this->fixCSSIncludes($assetContent, $file['PATH']);
2258 $assetContent = "\n/* Start:".$file['FULL_PATH']."*/\n".$assetContent."\n/* End */\n";
2259 $newContent .= "\n".$assetContent;
2260 }
2261 else
2262 {
2263 $info = [
2264 "full" => $file['FULL_PATH'],
2265 "source" => $file['PATH'],
2266 "min" => "",
2267 "map" => "",
2268 ];
2269
2270 if (preg_match("/\\.min\\.js$/i", $file['FILE_PATH']))
2271 {
2272 $sourceMap = self::cutSourceMap($assetContent);
2273 if ($sourceMap <> '')
2274 {
2275 $dirPath = IO\Path::getDirectory($file['PATH']);
2276 $info["map"] = $dirPath."/".$sourceMap;
2277 $info["min"] = self::getAssetPath($file['FULL_PATH']);
2278 $mapNeeded = true;
2279 }
2280 }
2281
2282 $comments .= "; /* ".$file['FULL_PATH']."*/\n";
2283 $newContent .= "\n".self::HEADER_START_TAG.serialize($info).self::HEADER_END_TAG."\n".$assetContent."\n/* End */\n;";
2284 }
2285
2286 $filesInfo[$file['PATH']] = $this->getAssetTime($file['FULL_PATH']);
2287 $needWrite = true;
2288 }
2289
2290 if ($needWrite)
2291 {
2292 $sourceMap = self::cutSourceMap($contents);
2293 $mapNeeded = $mapNeeded || $sourceMap <> '';
2294
2295 // Write packed files and meta information
2296 $contents = ($add2End ? $comments.$contents.$newContent : $newContent.$contents.$comments);
2297 if ($mapNeeded)
2298 {
2299 $contents .= self::SOURCE_MAP_TAG.$prefix.".map.js";
2300 }
2301
2302 if ($writeResult = $this->write($optimFName, $contents))
2303 {
2304 $cacheInfo = '<?php $filesInfo = [';
2305
2306 foreach ($filesInfo as $key => $hash)
2307 {
2308 $cacheInfo .= '"'.EscapePHPString($key).'" => "'.$hash.'",';
2309 }
2310
2311 $cacheInfo .= "]; ?>";
2312 $this->write($infoFile, $cacheInfo, false);
2313
2314 if ($mapNeeded)
2315 {
2316 $this->write($this->documentRoot.$optimPath.$prefix.".map.js", self::generateSourceMap($prefix.".js", $contents), false);
2317 }
2318 }
2319 }
2320 elseif ($optimFileExist)
2321 {
2322 $writeResult = true;
2323 }
2324 unset($contents);
2325 }
2326 }
2327
2328 $label = (($type == 'css') && ($prefix == 'template' || mb_substr($prefix, 0, 9) == 'template_') ? ' data-template-style="true" ' : '');
2329
2330 $bundleFile = '';
2331 $extendData = ($data != '' ? ' '.trim($data) : '');
2332 $extendData .= ($label != '' ? ' '.trim($label) : '');
2333
2334 if ($writeResult || $unique && $action == 'UP')
2335 {
2336 $bundleFile = \CUtil::GetAdditionalFileURL($optimFile);
2337 $currentFileList['FILES'][] = $bundleFile;
2338
2339 if ($type == 'css')
2340 {
2341 $res .= $this->insertCss($bundleFile, $extendData);
2342 }
2343 else
2344 {
2345 $res .= $this->insertJs($bundleFile, $extendData);
2346 }
2347 }
2348
2349 if (!$writeResult)
2350 {
2351 foreach ($files as $file)
2352 {
2353 $currentFileList['FILES'][] = $file['FULL_PATH'];
2354 if ($type == 'css')
2355 {
2356 $res .= $this->insertCss($file['FULL_PATH'], $extendData);
2357 }
2358 else
2359 {
2360 $res .= $this->insertJs($file['FULL_PATH'], $extendData);
2361 }
2362 }
2363 }
2364
2365 $resultFiles = [];
2366 if (is_array($filesInfo))
2367 {
2368 foreach ($filesInfo as $key => $hash)
2369 {
2370 $resultFiles[] = $key.'?'.$hash;
2371
2372 }
2373 }
2374
2375 unset($files);
2376
2377 if ($bundleFile != '')
2378 {
2379 $currentFileList['FULL_FILES'][$bundleFile] = $resultFiles;
2380 }
2381 return ['RESULT' => $res, 'FILES' => $resultFiles, 'SOURCE_FILES' => array_keys($filesInfo)];
2382 }
2383
2389 private static function cutSourceMap(&$content)
2390 {
2391 $sourceMapName = "";
2392
2393 $length = strlen($content);
2394 $position = $length > 512 ? $length - 512 : 0;
2395 $lastLine = strpos($content, self::SOURCE_MAP_TAG, $position);
2396 if ($lastLine !== false)
2397 {
2398 $nameStart = $lastLine + strlen(self::SOURCE_MAP_TAG);
2399 if (($newLinePos = strpos($content, "\n", $nameStart)) !== false)
2400 {
2401 $sourceMapName = substr($content, $nameStart, $newLinePos - $nameStart);
2402 }
2403 else
2404 {
2405 $sourceMapName = substr($content, $nameStart);
2406 }
2407
2408 $sourceMapName = trim($sourceMapName);
2409 $content = substr($content, 0, $lastLine);
2410 }
2411
2412 return $sourceMapName;
2413 }
2414
2420 private static function getFilesInfo($content)
2421 {
2422 $offset = 0;
2423 $line = 0;
2424
2425 $result = [];
2426 while (($newLinePos = strpos($content, "\n", $offset)) !== false)
2427 {
2428 $line++;
2429 $offset = $newLinePos + 1;
2430 if (substr($content, $offset, strlen(self::HEADER_START_TAG)) === self::HEADER_START_TAG)
2431 {
2432 $endingPos = strpos($content, self::HEADER_END_TAG, $offset);
2433 if ($endingPos === false)
2434 {
2435 break;
2436 }
2437
2438 $startData = $offset + strlen(self::HEADER_START_TAG);
2439 $data = unserialize(substr($content, $startData, $endingPos - $startData), ['allowed_classes' => false]);
2440
2441 if (is_array($data))
2442 {
2443 $data["line"] = $line + 1;
2444 $result[] = $data;
2445 }
2446
2447 $offset = $endingPos;
2448 }
2449 }
2450
2451 return $result;
2452 }
2453
2460 private static function generateSourceMap($fileName, $content)
2461 {
2462 $files = self::getFilesInfo($content);
2463 $sections = "";
2464 foreach ($files as $file)
2465 {
2466 if (!isset($file["map"]) || mb_strlen($file["map"]) < 1)
2467 {
2468 continue;
2469 }
2470
2471 $filePath = Main\Loader::getDocumentRoot().$file["map"];
2472 if (file_exists($filePath) && ($content = file_get_contents($filePath)) !== false)
2473 {
2474 if ($sections !== "")
2475 {
2476 $sections .= ",";
2477 }
2478
2479 $dirPath = IO\Path::getDirectory($file["source"]);
2480 $sourceName = IO\Path::getName($file["source"]);
2481 $minName = IO\Path::getName($file["min"]);
2482
2483 $sourceMap = str_replace(
2484 [$sourceName, $minName],
2485 [$dirPath."/".$sourceName, $dirPath."/".$minName],
2486 $content
2487 );
2488 $sections .= '{"offset": { "line": '.$file["line"].', "column": 0 }, "map": '.$sourceMap.'}';
2489 }
2490 }
2491
2492 return '{"version":3, "file":"'.$fileName.'", "sections": ['.$sections.']}';
2493 }
2494
2502 function write($filePath, $content, $gzip = true)
2503 {
2504 $fnTmp = $filePath.'.tmp';
2505
2506 if (!CheckDirPath($filePath) || !$fh = fopen($fnTmp, "wb"))
2507 {
2508 return false;
2509 }
2510
2511 $written = fwrite($fh, $content);
2512 $len = strlen($content);
2513 fclose($fh);
2514
2515 if (file_exists($filePath))
2516 {
2517 @unlink($filePath);
2518 }
2519
2520 $result = false;
2521 if ($written === $len)
2522 {
2523 $result = true;
2524 rename($fnTmp, $filePath);
2525 @chmod($filePath, BX_FILE_PERMISSIONS);
2526 if ($gzip && self::gzipEnabled())
2527 {
2528 $fnTmpGz = $filePath.'.tmp.gz';
2529 $fnGz = $filePath.'.gz';
2530
2531 if ($gz = gzopen($fnTmpGz, 'wb9f'))
2532 {
2533 $writtenGz = @gzwrite ($gz, $content);
2534 gzclose($gz);
2535
2536 if (file_exists($fnGz))
2537 {
2538 @unlink($fnGz);
2539 }
2540
2541 if ($writtenGz === $len)
2542 {
2543 rename($fnTmpGz, $fnGz);
2544 @chmod($fnGz, BX_FILE_PERMISSIONS);
2545 }
2546
2547 if (file_exists($fnTmpGz))
2548 {
2549 @unlink($fnTmpGz);
2550 }
2551 }
2552 }
2553 }
2554
2555 if (file_exists($fnTmp))
2556 {
2557 @unlink($fnTmp);
2558 }
2559
2560 return $result;
2561 }
2562}