valeraplusplus

import

Nov 27th, 2023
99
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Smarty 20.08 KB | None | 0 0
  1. {strip}
  2.     <div class="lpc-block lpc-code-block" data-elem-type="block" id="_lp_block_{$e.block_id}" data-block-layout="{$e.layout_id}">
  3. <style>
  4. {literal}
  5. @charset "UTF-8";
  6. .btn-advanced-data {
  7.   width: 50px;
  8.   height: 50px;
  9.   position: absolute;
  10.   top: 0;
  11.   z-index: 10;
  12.   background: repeating-linear-gradient(-45deg, rgb(255, 255, 255), rgb(219, 219, 219) 5px, rgb(219, 219, 219) 5px, rgb(235, 235, 235) 10px);
  13.   border-radius: 5px;
  14.   border: 1px solid rgb(143, 143, 143);
  15. }
  16.  
  17. .btn-advanced-data i {
  18.   position: absolute;
  19.   top: 0;
  20.   left: 0;
  21.   right: 0;
  22.   bottom: 0;
  23.   width: 50px;
  24.   height: 50px;
  25.   display: inline-block;
  26.   -webkit-mask-image: url("/my/s3/images/svg_icons/ic_ol_settings.svg");
  27.   mask-image: url("/my/s3/images/svg_icons/ic_ol_settings.svg");
  28.   -webkit-mask-size: 30px 30px;
  29.   mask-size: 30px 30px;
  30.   -webkit-mask-position: 50% 50%;
  31.   mask-position: 50% 50%;
  32.   -webkit-mask-repeat: no-repeat no-repeat;
  33.   mask-repeat: no-repeat no-repeat;
  34.   background: rgb(92, 95, 131);
  35. }
  36.  
  37. .get-data-modal-wrapper {
  38.   width: 100%;
  39.   position: fixed;
  40.   top: 0;
  41.   right: 0;
  42.   left: 0;
  43.   bottom: 0;
  44.   z-index: 1000;
  45.   background: rgba(30, 30, 30, 0.30);
  46. }
  47.  
  48. /* Стили модального окна */
  49. .get-data-modal {
  50.   position: fixed;
  51.   top: 50%;
  52.   left: 50%;
  53.   transform: translate(-50%, -50%);
  54.   padding: 20px;
  55.   background-color: #fff;
  56.   border: 1px solid #ccc;
  57.   box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
  58.   z-index: 1000;
  59.   max-width: 400px;
  60.   width: 100%;
  61.   text-align: center;
  62.   border-radius: 8px;
  63. }
  64.  
  65.  
  66. .get-data-modal h2 {
  67.   color: #333;
  68. }
  69.  
  70. .get-data-modal label {
  71.   display: block;
  72.   margin-top: 10px;
  73.   margin-bottom: 5px;
  74.   color: #555;
  75. }
  76.  
  77. .get-data-modal input {
  78.   width: 100%;
  79.   padding: 8px;
  80.   margin-bottom: 15px;
  81.   box-sizing: border-box;
  82.   border: 1px solid #ccc;
  83.   border-radius: 4px;
  84. }
  85.  
  86. .get-data-modal button {
  87.   background-color: #4caf50;
  88.   color: #fff;
  89.   padding: 10px 15px;
  90.   border: none;
  91.   border-radius: 4px;
  92.   cursor: pointer;
  93.   margin-right: 10px;
  94. }
  95.  
  96. .get-data-modal button:hover {
  97.   background-color: #45a049;
  98. }
  99.  
  100. .get-data-modal .get-data-modal-enter {
  101.   background-color: #007bff;
  102. }
  103.  
  104. .get-data-modal .get-data-modal-enter:hover {
  105.   background-color: #0056b3;
  106. }
  107. {/literal}
  108. </style>
  109. <script>
  110. {literal}
  111.  
  112. var LoadDataLPC = {
  113.     appInit: function($_self){
  114.         class appCreate {
  115.             constructor(_self) {
  116.                 this.block = _self;
  117.                 this.openModal = null;
  118.  
  119.                 this.frameTemporary = null;
  120.                 this.frameGatewayUrl = 'https://gateway.oml.ru';
  121.                 this.frameElement = null;
  122.  
  123.                 this.haveImage = 0,
  124.                 this.templateBoxHtml = '';
  125.                 this.googleMethod = {
  126.                     link: null,
  127.                     list: null,
  128.                     isGoogleValid: false
  129.                 };
  130.                 this.idFolderImg = null;
  131.                 this.csvFile = false;
  132.                 this.helper = {
  133.                     checkClass(element, classElement ){
  134.                         return element.classList.contains(classElement)
  135.                     },
  136.                     isLikeImage( obj ) {
  137.                         if( obj.substr( 0, 4) == 'http' && obj.length > 3 ){
  138.    
  139.                             let url = obj.substr(obj.length - 5);
  140.    
  141.                             // Создайте регулярное выражение для сопоставления расширений файлов
  142.                             let allowedExtensions = /\.(jpeg|jpg|png|ico|gif|tiff|webp|eps|svg|psd|indd|cdr|ai|raw)$/i;
  143.    
  144.                             // Используйте метод test() для проверки URL-адреса
  145.                             if (allowedExtensions.test(url)) {
  146.                                 this.haveImage = 1;
  147.                                 return obj.split(',')[0];
  148.  
  149.                             }
  150.                             return obj
  151.                         }
  152.                         return obj
  153.                     },
  154.                     escapeHtml(str) {
  155.                         return str.replace(/[&<>"'/]/g, function(match) {
  156.                             switch (match) {
  157.                             case '&':
  158.                                 return '&amp;';
  159.                             case '<':
  160.                                 return '&lt;';
  161.                             case '>':
  162.                                 return '&gt;';
  163.                             case '"':
  164.                                 return '&quot;';
  165.                             case "'":
  166.                                 return '&#39;'; // Можно использовать &apos; вместо &#39;, если хотите
  167.                             default:
  168.                                 return match;
  169.                                 }
  170.                         })
  171.                     },
  172.                     decodeHtmlEntities(input) {
  173.                         var doc = new DOMParser().parseFromString(input, "text/html");
  174.                         return doc.documentElement.textContent;
  175.                     },
  176.                     getByUrlData(textData){
  177.                         // Получаем URL-адрес из строки браузера
  178.                         var url = window.location.href.replace('?', '&');
  179.                
  180.                         // Разбиваем URL-адрес по символу "&" для получения всех параметров
  181.                         var params = url.split('&');
  182.                
  183.                         // Перебираем параметры и ищем параметр "access"
  184.                         var accessValue = null;
  185.                         for (var i = 0; i < params.length; i++) {
  186.                             var param = params[i];
  187.                             if (param.indexOf(`${textData}=`) === 0) {
  188.                                 // Нашли параметр "access"
  189.                                 // Извлекаем его значение и декодируем URL-кодирование
  190.                                 accessValue = decodeURIComponent(param.substring( textData.length + 1 ));
  191.                                 break; // Выходим из цикла после нахождения параметра
  192.                             }
  193.                         }
  194.                
  195.                         // Теперь значение accessValue содержит значение свойства "access"
  196.                         return accessValue;
  197.                
  198.                     }
  199.                 }
  200.  
  201.             };
  202.             modalClose(){
  203.                 var _this = this;
  204.                 if(!!_this.openModal){
  205.                     document.body.removeChild( _this.openModal );
  206.                 }
  207.             };
  208.             createButton() {
  209.                 var _this = this;
  210.  
  211.                 var btnAdvancedData = document.createElement('div');
  212.                 btnAdvancedData.classList.add('btn-advanced-data');
  213.    
  214.                 // Создаем элемент "i" и добавляем его внутрь "btnAdvancedData"
  215.                 var iElement = document.createElement('i');
  216.                 btnAdvancedData.appendChild(iElement);
  217.    
  218.                 // Вставляем созданный элемент в конец целевого элемента
  219.                 this.block.appendChild(btnAdvancedData);
  220.    
  221.                 // Добавляем обработчик события клика
  222.                 btnAdvancedData.addEventListener('click', function() {
  223.                     _this.modal();
  224.                 });
  225.             };
  226.             modal(){
  227.                 var _this = this;
  228.                 var templateModal = `
  229.                 <div class="get-data-modal">
  230.                     <h2>Импорт данных</h2>
  231.                     <label for="tableLink" class="get-data-modal-label">Ссылка на таблицу:</label>
  232.                     <input type="text" id="tableLink" class="get-data-modal-input">
  233.                
  234.                     <label for="sheetName" class="get-data-modal-label">Название листа из таблицы:</label>
  235.                     <input type="text" id="sheetName" class="get-data-modal-input">
  236.                
  237.                     <label for="csvFile" class="get-data-modal-label">Выберите файлы CSV:</label>
  238.                     <input type="file" id="csvFile" class="get-data-modal-input" accept=".csv">
  239.  
  240.                     <label for="idFolderImg" class="get-data-modal-label">ID категории для картинок</label>
  241.                     <input type="text" id="idFolderImg" class="get-data-modal-input">
  242.                     <button class="get-data-modal-enter" >Получить данные</button>
  243.                     <button class="get-data-modal-close">Закрыть</button>
  244.                 </div>
  245.                 `;
  246.                 var modal = document.createElement('div');
  247.                 modal.classList.add('get-data-modal-wrapper');
  248.                 modal.innerHTML = templateModal;
  249.  
  250.                 document.body.appendChild(modal);
  251.  
  252.                 _this.openModal = modal;
  253.                 window.sss = _this.openModal
  254.                 modal.addEventListener('click', function(e) {
  255.                     let el = e.target
  256.                     let chClass = _this.helper.checkClass;
  257.                     if(chClass(el,'get-data-modal-close') || chClass(el,'get-data-modal-wrapper')){
  258.                         _this.modalClose();
  259.                     }else if(chClass(el,'get-data-modal-enter')){
  260.  
  261.                         _this.googleMethod.link = _this.openModal.querySelector('#tableLink').value;
  262.                         _this.googleMethod.list = _this.openModal.querySelector('#sheetName').value;
  263.                         this.idFolderImg = _this.openModal.querySelector('#idFolderImg').value;
  264.                         _this.csvFile = modal.querySelector('#csvFile').files[0];
  265.  
  266.                         _this.appRun();
  267.                        
  268.                     }
  269.                 });
  270.             };
  271.             createIframe() {
  272.                 this.frameElement = document.createElement('iframe');
  273.                 this.frameGatewayUrl = 'https://gateway.oml.ru';
  274.                 const handlerPage = '/gateway-sheets?';
  275.                 let amp = encodeURI('&')
  276.                 let googleSheetsLink = `${amp}sheetsLink=${ encodeURI( this.googleMethod.link ) }`;
  277.                 let googleSheetsList = `${amp}sheetslist=${ encodeURI( this.googleMethod.list ) }`;
  278.                 const selfUrl = window.location.origin + '/';
  279.                 this.frameElement.src = this.frameGatewayUrl + handlerPage + `onnerprovoking=${ encodeURI( selfUrl ) }${googleSheetsLink}${googleSheetsList}`;
  280.  
  281.                 // Скрываем this.frameElement
  282.                 this.frameElement.style.display = 'none';
  283.  
  284.                 // Добавляем this.frameElement на страницу
  285.                 document.body.appendChild(this.frameElement);
  286.  
  287.                 return true;
  288.             };
  289.             error(numb) {
  290.                 switch (numb) {
  291.                     case 1:
  292.                         console.log('file not');
  293.                         return false;
  294.                         break;
  295.                     case 2:
  296.                         console.log('google sheets not');
  297.                         return false;
  298.                         break;
  299.                     case 3:
  300.                         alert('Перебор');
  301.                         return false;
  302.                         break;
  303.                     default:
  304.                         alert("Нет таких значений");
  305.                 }
  306.             };
  307.             parseCSVToJson(csvData) {
  308.                 const array = csvData.split('\r\n'); // Разбиваем CSV на строки
  309.                 const headers = array[0].split(','); // Получаем заголовки столбцов
  310.                 const result = [];
  311.  
  312.                 for (let i = 1; i < array.length - 1; i++) {
  313.                     let obj = {};
  314.  
  315.                     let str = array[i]; // Текущая строка CSV
  316.                     let s = '';
  317.  
  318.                     let flag = 0;
  319.                     for (let ch of str) {
  320.                         if (ch === '"' && flag === 0) {
  321.                             flag = 1; // Если встречаем открывающую кавычку, устанавливаем флаг
  322.                         } else if (ch === '"' && flag == 1) {
  323.                             flag = 0; // Если встречаем закрывающую кавычку, снимаем флаг
  324.                         }
  325.                         if (ch === ',' && flag === 0) {
  326.                             ch = '|'; // Заменяем запятые на вертикальные черты, если они не внутри кавычек
  327.                         }
  328.                         if (ch !== '"') {
  329.                             s += ch;
  330.                         }
  331.                     }
  332.  
  333.                     // Разбиваем строку на значения, используя вертикальные черты
  334.                     let properties = s.split("|");
  335.  
  336.                     // Для каждого заголовка столбца сохраняем значение в объект
  337.                     for (let j in headers) {
  338.                         obj[headers[j]] = properties[j];
  339.                     }
  340.  
  341.                     // Добавляем объект в результат
  342.                     result.push(obj);
  343.                 }
  344.  
  345.                 return result;
  346.             };
  347.             async getDataByFile() {
  348.                 if (!this.csvFile) {
  349.                     return this.error(1);
  350.                 }
  351.  
  352.                 return new Promise((resolve, reject) => {
  353.                     const reader = new FileReader(); // Создаем объект для чтения файла
  354.  
  355.                     reader.onload = (event) => {
  356.                         const csvData = event.target.result; // Получаем данные CSV из файла
  357.                         this.MAINDATA = this.parseCSVToJson(csvData); // Преобразуем CSV в JSON
  358.                         resolve(this.MAINDATA);
  359.                     };
  360.  
  361.                     reader.onerror = (event) => {
  362.                         reject(new Error("Ошибка чтения файла"));
  363.                     };
  364.  
  365.                     reader.readAsText(this.csvFile); // Читаем файл как текст
  366.                 });
  367.             };
  368.             createIframe() {
  369.                 this.frameElement = document.createElement('iframe');
  370.                 this.frameGatewayUrl = 'https://gateway.oml.ru';
  371.                 const handlerPage = '/gateway-sheets?';
  372.                 let amp = encodeURI('&')
  373.                 let googleSheetsLink = `${amp}sheetsLink=${ encodeURI( this.googleMethod.link ) }`;
  374.                 let googleSheetsList = `${amp}sheetslist=${ encodeURI( this.googleMethod.list ) }`;
  375.                 const selfUrl = window.location.origin + '/';
  376.                 this.frameElement.src = this.frameGatewayUrl + handlerPage + `onnerprovoking=${ encodeURI( selfUrl ) }${googleSheetsLink}${googleSheetsList}`;
  377.  
  378.                 // Скрываем this.frameElement
  379.                 this.frameElement.style.display = 'none';
  380.  
  381.                 // Добавляем this.frameElement на страницу
  382.                 document.body.appendChild(this.frameElement);
  383.  
  384.                 return true;
  385.             };
  386.             // Метод для обработки сообщений и получения данных
  387.             async handlerMessageGr(event) {
  388.                 if (event.origin === this.frameGatewayUrl) {
  389.                     this.MAINDATA = event.data;
  390.  
  391.                     // Разрешаем обещание с полученными данными
  392.                     if (this.dataPromiseResolve) {
  393.                         this.dataPromiseResolve(this.MAINDATA);
  394.  
  395.                         // Удаляем слушатель после получения данных
  396.                         window.removeEventListener('message', this.handlerMessageGr);
  397.                     }
  398.                 }
  399.             };
  400.             async getCompleteData() {
  401.                 try {
  402.                     var data = null;
  403.                    
  404.                     if( this.csvFile ){
  405.                         data = await this.getDataByFile();
  406.                     }else if( true ){
  407.                         data = await this.getDataByGoogle();
  408.                     }
  409.                     // Выполняйте какие-либо действия с данными
  410.                     return data;
  411.                 } catch (error) {
  412.                     // Обработка ошибок, если необходимо
  413.                     return null;
  414.                 }
  415.             };
  416.             async getDataByGoogle() {
  417.                 if (typeof this.googleMethod.link === 'undefined') return this.error(2);
  418.                 if (typeof this.googleMethod.list === 'undefined') return this.error(2);
  419.  
  420.                 if (this.createIframe()) {
  421.                     // Создаем новое обещание для получения данных
  422.                     this.dataPromise = new Promise((resolve, reject) => {
  423.                         // Сохраняем функцию resolve внутри объекта для использования позже
  424.                         this.dataPromiseResolve = resolve;
  425.  
  426.                         window.addEventListener('message', (event) => {
  427.                             this.handlerMessageGr(event);
  428.                             // Может потребоваться обработать ошибку здесь, если event.origin не соответствует ожидаемому
  429.                         });
  430.                     });
  431.  
  432.                     return this.dataPromise;
  433.                 }
  434.             };
  435.  
  436.             async saveImage(imgURL) {
  437.                 try {
  438.                     var formData = new FormData();
  439.                     let ver_id = this.helper.getByUrlData('ver_id');
  440.                     let access = this.helper.getByUrlData('access');
  441.            
  442.                     let objectImage = {
  443.                         "image_id": "0",
  444.                         "filename": "",
  445.                         "ver_id": ver_id,
  446.                         "svg_body": "",
  447.                         "image_width": "0",
  448.                         "image_height": "0",
  449.                         "ext": ""
  450.                     };
  451.            
  452.                     formData.append('ver_id', ver_id);
  453.                     formData.append('access', access);
  454.                     formData.append('query_id', '0');
  455.                     formData.append('src_type', 'uri');
  456.                     if(this.idFolderImg){
  457.                         formData.append('folder_ids', this.idFolderImg);
  458.                     }
  459.            
  460.                     var fileUrls = [imgURL];
  461.                     var fileNames = [`auto-create-` + imgURL.split('.').slice(0, -1).join('.')];
  462.            
  463.                     for (var i = 0; i < fileUrls.length; i++) {
  464.                         formData.append('files[]', fileUrls[i]);
  465.                         formData.append('names[]', fileNames[i]);
  466.                         formData.append('modes[]', '');
  467.                     }
  468.            
  469.                     let response = await fetch('/my/s3/cms/v1/image/editing.php', {
  470.                         method: 'POST',
  471.                         body: formData
  472.                     });
  473.            
  474.                     if (!response.ok) {
  475.                         throw new Error('Network response was not ok');
  476.                     }
  477.            
  478.                     let data = await response.text();
  479.            
  480.                     if (data.indexOf(`The image is not found or access denied`) == 0) {
  481.                         console.error(`The image is not found or access denied`);
  482.                         throw new Error('The image is not found or access denied');
  483.                     }
  484.            
  485.                     let temporaryElement = document.createElement('div');
  486.                     temporaryElement.innerHTML = data;
  487.                     let temporaryImg = temporaryElement.querySelector('.image');
  488.            
  489.                     objectImage["image_id"] = temporaryImg.getAttribute('data-image-id');
  490.                     objectImage["filename"] = temporaryImg.getAttribute('data-filename');
  491.                     objectImage["image_width"] = temporaryImg.getAttribute('data-image-width');
  492.                     objectImage["image_height"] = temporaryImg.getAttribute('data-image-height');
  493.                     objectImage["ext"] = temporaryImg.getAttribute('data-ext');
  494.            
  495.                     return objectImage;
  496.                 } catch (error) {
  497.                     console.error('Ошибка при сохранении изображения:', error);
  498.                     throw error;
  499.                 }
  500.             };
  501.             async setImage(imgURL) {
  502.                 /*
  503.                 let img = {
  504.                     "image_id": "212149508",
  505.                     "filename": "55234803_2.jpg",
  506.                     "ver_id": 3049689,
  507.                     "svg_body": "",
  508.                     "image_width": "1920",
  509.                     "image_height": "1512",
  510.                     "ext": "jpg"
  511.                 };
  512.                 */
  513.                 let img = {"image_id":"212149508","filename":"55234803_2.jpg","ver_id":3049689,"svg_body":"","image_width":"1920","image_height":"1512","ext":"jpg"};
  514.  
  515.                 let matchText = /value="([^"]+)"/;
  516.                 let searchResult = this.templateBoxHtml.match(matchText);
  517.                
  518.                 if (searchResult) {
  519.                     // searchResult[0] содержит всю найденную строку, а searchResult[1] содержит значение атрибута "value"
  520.                    
  521.  
  522.                     let decodedString = searchResult[1].replace(/&quot;/g, '"');
  523.                    
  524.                     // Преобразуем строку в JSON объект
  525.                     let jsonObject = JSON.parse(decodedString);
  526.  
  527.                     let imageFieldName = '';
  528.                     for( var key in jsonObject ){
  529.                         if( typeof jsonObject[key].image_id != 'undefined'){
  530.                             imageFieldName = key;
  531.                             console.log( imageFieldName )
  532.                         }
  533.                     }
  534.  
  535.                     let imgPath = await this.saveImage(imgURL);
  536.                     jsonObject[imageFieldName] = imgPath;
  537.  
  538.                     //jsonObject.menu_image = img;
  539.  
  540.                     let temporaryElement = document.createElement('div');
  541.  
  542.                     temporaryElement.innerHTML = this.templateBoxHtml;
  543.  
  544.                     var jsonString = JSON.stringify(jsonObject);
  545.  
  546.                     // Заменяем кавычки на экранированные кавычки
  547.                     //var escapedString = this.helper.escapeHtml( jsonString );
  548.                    
  549.                     temporaryElement.querySelector('input').value = jsonString;
  550.  
  551.                     this.templateBoxHtml = temporaryElement.innerHTML;
  552.  
  553.  
  554.                     return `ОК`;
  555.                 } else {
  556.                     return new Error("Значение атрибута 'value' не найдено.");
  557.                 }
  558.             };
  559.             async saveData(data) {
  560.                 if (data) {
  561.                     this.block.querySelector('[data-metrika="landingBlockEdit"]').click();
  562.                    
  563.                     let popupHtml = popupController.getLastPopup().obj,
  564.                         boxesList = popupHtml.querySelector('.boxes.data-list'),
  565.                         box = boxesList.querySelector('.box');
  566.                     var counter = 0;
  567.                         //data = data.splice(0, 2);
  568.                     for (const dataObject of data) {
  569.                         let cloneBox = box.cloneNode(true);
  570.                         this.templateBoxHtml = cloneBox.innerHTML;
  571.                        
  572.                         for (let key in dataObject) {
  573.                             let valueObject = dataObject[key];
  574.                            
  575.                             let matchText = `{{${key}}}`;
  576.                             let searchRegex = new RegExp(matchText, 'g');
  577.                            
  578.                             if (key == 'image' ) {
  579.                                 valueObject = this.helper.isLikeImage(valueObject)
  580.                                 let simp_valueObject = await this.setImage(valueObject);
  581.                             }else{
  582.  
  583.                                 valueObject = valueObject.replace(/[\"]/g, function (char) {
  584.                                     return  `'`;
  585.                                 });
  586.                                 valueObject = this.helper.escapeHtml( valueObject.replace(/\s+/g, ' ') );
  587.                                 this.templateBoxHtml = this.templateBoxHtml.replace(searchRegex, valueObject);
  588.                             }
  589.  
  590.                             this.haveImage = 0;
  591.                         }
  592.                         cloneBox.innerHTML = this.templateBoxHtml;
  593.  
  594.                         boxesList.append(cloneBox);
  595.                     }
  596.                     box.remove();
  597.  
  598.                     popupHtml.querySelector('.s3-btn-v2').click();
  599.  
  600.                     if (this.frameElement) this.frameElement.remove();
  601.  
  602.                     this.modalClose();
  603.                 }
  604.             };
  605.             async appRun(){
  606.                 const data = await this.getCompleteData();
  607.                
  608.                 if (data) {
  609.                     await this.saveData(data);
  610.                 }
  611.             };
  612.             async init() {
  613.                 this.createButton();
  614.             };
  615.         }
  616.  
  617.         var app = new appCreate($_self[0]);
  618.  
  619.         app.init().catch((error) => {
  620.             console.error("Ошибка при инициализации: ", error);
  621.         });
  622.  
  623.         /* OBJECT */
  624.     }
  625. }
  626.  
  627.  
  628. lpc_template.queue.loadData = function ($self) {
  629.     if($self.is('body')) {
  630.         $('.lpc-block').each( function(){
  631.             if( $(this).find('.lpc-row').length){
  632.                 LoadDataLPC.appInit($(this));
  633.             }
  634.         })
  635.  
  636.     } else if($self.find('.lpc-row').length) {
  637.         LoadDataLPC.appInit($self);
  638.     }
  639. }
  640. {/literal}
  641. </script>      
  642.         </br>
  643.         </br>
  644.         <h3>Загрузчик</h3>
  645.         </br>
  646.         </br>
  647.     </div>
  648. {/strip}
Add Comment
Please, Sign In to add comment