Категории форума

Записки CotoДела

Мои записи, как типовые решения разных вопросов с Cotonti


webitproff
webitproff • 16.03.2026 11:20 #1

Меняем массово статус наличия товара, в экстраполях путем SQL-запроса.

 

Модуль маркет про (Cotonti)
Для обновления записей в таблице cot_market, где категория 'energy', а поле `fieldmrkt_product_status` пустое (пустая строка ''), используем следующий SQL-запрос:
 


UPDATE cot_market 
SET fieldmrkt_product_status = 'outofstock' 
WHERE fieldmrkt_cat = 'energy' 
 AND fieldmrkt_product_status = '';

 

дополнительно можно: 
 


UPDATE cot_market 
SET fieldmrkt_product_status = 'outofstock' 
WHERE fieldmrkt_cat = 'energy' 
 AND (fieldmrkt_product_status = '' OR fieldmrkt_product_status IS NULL);

 

но NULL у нас нет - это лишнее,и  просто для примера. 

webitproff
webitproff • 18.03.2026 08:58 #2

Как перезаписать цену товара в своей валюте, по актуальному курсу, используя валюту поставщика


Шаг 1. Добавление поля в базу данных

Вам нужно выполнить SQL-запрос в phpMyAdmin или через консоль MySQL.
Запрос:

ALTER TABLE `cot_market` ADD `fieldmrkt_cost_usd` DECIMAL(10,2) NOT NULL DEFAULT '0.00' AFTER `fieldmrkt_costdflt`;

Это добавит новое поле для хранения цены в USD.


Шаг 2. Добавление настроек в конфигурацию модуля Market

Откройте файл modules/market/market.setup.php.
Найдите секцию [BEGIN_COT_EXT_CONFIG] и добавьте туда две строки:

market_rate_value_to_uah=76:string::44.00:
market_for_rate_base_currency_cost_usd=75:string::USD:

После этого зайдите в админку → Управление сайтом → Модули → Market и нажмите "Сохранить", чтобы новые настройки появились в базе.


Шаг 3. Изменение файла functions.php

Откройте modules/market/inc/market.functions.php.

3.1. В функции cot_market_import (примерно строка 80–90) найдите строку с $ritem['fieldmrkt_costdflt'] и сразу после неё добавьте:

$ritem['fieldmrkt_cost_usd'] = cot_import('ritemarketcostusd', $source, 'NUM');

3.2. В функции cot_generate_markettags найдите массив $temp_array (там много строк). После строки с 'COSTDFLT' => ... добавьте:

'COST_USD' => (float)$item_data['fieldmrkt_cost_usd'],
'COST_USD_RAW' => rtrim(rtrim(number_format((float)$item_data['fieldmrkt_cost_usd'], 2, '.', ''), '0'), '.'),
'COST_USD_FORMATTED' => $item_data['fieldmrkt_cost_usd'] > 0 
    ? number_format($item_data['fieldmrkt_cost_usd'], 2, '.', ' ') 
    : '',

Шаг 4. Добавление поля в форму редактирования товара

Откройте modules/market/market.edit.php.

Найдите массив $itemedit_array (там перечислены 'MARKETEDIT_FORM_COSTDFLT' и другие). Рядом с 'MARKETEDIT_FORM_COSTDFLT' добавьте:

'MARKETEDIT_FORM_COST_USD' => cot_inputbox('text', 'ritemarketcostusd', $item['fieldmrkt_cost_usd'], 'size="10"'),

Теперь в шаблоне редактирования (например, market.edit.tpl) вы сможете вывести это поле:

<div class="mb-3">
    <label class="form-label">Цена в UAH (обычная):</label>
    {MARKETEDIT_FORM_COSTDFLT}
</div>
<div class="mb-3">
    <label class="form-label">Цена в USD (закупочная):</label>
    {MARKETEDIT_FORM_COST_USD}
</div>

Шаг 5. Изменение шаблонов вывода цены

Вам нужно заменить блоки, где выводится цена, в трёх файлах:

  • market.tpl (детальная страница товара)
  • market.list.tpl (список товаров)
  • market.index.tpl (индексная страница)

Вставьте этот код вместо существующего блока с ценой:

<!-- IF {MARKET_COSTDFLT} > 0 -->
    <span class="price-label">{PHP.L.market_price_supplier_coverted}</span>
    <p>
        <span class="ms-2 text-success market-price"
              data-usd-price="{MARKET_COST_USD_RAW}"
              data-rate="{PHP.cfg.market.market_rate_value_to_uah}">
            {MARKET_COSTDFLT}
        </span>
    </p>
    {PHP.cfg.market.market_currency}

    <!-- IF {MARKET_COST_USD} > 0 -->
    <div class="text-muted small">
        {PHP.L.market_price_supplier}: {MARKET_COST_USD_FORMATTED} USD
    </div>
    <!-- ENDIF -->
<!-- ENDIF -->

Обратите внимание: data-currency мы убрали, так как валюта выводится отдельно.


Шаг 6. Добавление языковых констант

Откройте языковой файл модуля Market, например modules/market/lang/market.ru.php.
Добавьте в конец (перед закрывающим ?> если есть):

$L['market_price_supplier'] = 'Отпускная цена поставщика';
$L['market_price_supplier_coverted'] = 'Цена в UAH (по курсу USD):';
$L['market_price_converted_label'] = 'Цена в UAH (по курсу USD):';
$L['market_price'] = 'Цена:';

Если какие-то из этих констант уже есть, просто добавьте недостающие.


Шаг 7. Создание файла хука для JavaScript

Создайте новый файл modules/market/market.rc.php (если его нет) и вставьте туда следующий код:

<?php
/* ====================
[BEGIN_COT_EXT]
Hooks=rc
[END_COT_EXT]
==================== */

defined('COT_CODE') or die('Wrong URL');

// Подключаем языковой файл модуля Market, если ещё не загружен
if (!isset($L['market_price_converted_label'])) {
    require_once cot_langfile('market', 'module');
}

global $L, $cfg;

if (cot_module_active('market')) {
    $converted_label = $L['market_price_converted_label'] ?? 'Цена в UAH (по курсу USD):';
    $default_label   = $L['market_price'] ?? 'Цена:';
    $rate            = $cfg['market']['market_rate_value_to_uah'] ?? 44.50;

    $converted_label_js = json_encode($converted_label, JSON_HEX_APOS | JSON_HEX_QUOT);
    $default_label_js   = json_encode($default_label, JSON_HEX_APOS | JSON_HEX_QUOT);
    $rate_js            = json_encode((float)$rate);

    Resources::embedFooter(<<<JS
    document.addEventListener('DOMContentLoaded', function() {
        const convertedLabel = {$converted_label_js};
        const defaultLabel   = {$default_label_js};
        const defaultRate    = {$rate_js};

        document.querySelectorAll('.market-price').forEach(function(el) {
            const usdPrice = parseFloat(el.dataset.usdPrice);
            const rate     = parseFloat(el.dataset.rate) || defaultRate;

            if (!isNaN(usdPrice) && usdPrice > 0 && !isNaN(rate) && rate > 0) {
                const uahPrice = usdPrice * rate;
                const formatted = uahPrice.toLocaleString('ru-RU', {
                    minimumFractionDigits: (uahPrice % 1 !== 0) ? 2 : 0,
                    maximumFractionDigits: 2
                });
                el.innerHTML = formatted;

                const label = el.closest('p, div, span, .fw-bold')?.querySelector('.price-label');
                if (label) {
                    label.textContent = convertedLabel;
                }
            } else {
                const label = el.closest('p, div, span, .fw-bold')?.querySelector('.price-label');
                if (label && label.textContent !== defaultLabel) {
                    label.textContent = defaultLabel;
                }
            }
        });
    });
    JS
    );
}

Этот файл будет автоматически подключаться благодаря хуку rc, и JavaScript встроится в подвал сайта.


Шаг 8. Проверка

  1. Зайдите в админку, откройте любой товар на редактирование – должно появиться поле для ввода цены в USD.
  2. Заполните цену в USD (например, 100) и сохраните товар.
  3. На странице товара вы увидите:
    • Отпускная цена поставщика: 100.00 USD (маленьким текстом)
    • Основная цена (число) будет пересчитана по курсу, например, 4400.00, а валюта (UAH) выведена отдельно.
    • Подпись над ценой изменится на "Цена в UAH (по курсу USD):".
  4. Если поле USD пустое или равно 0, то конвертация не происходит, отображается обычная цена и стандартная подпись "Цена:".

Возможные проблемы и их решение

  • Поле в редактировании не появилось – проверьте, что вы добавили строку в $itemedit_array и что файл market.edit.php сохранён в UTF-8 без BOM.
  • Цена не пересчитывается – откройте консоль браузера (F12) и посмотрите, нет ли ошибок JavaScript. Убедитесь, что у элемента .market-price есть атрибуты data-usd-price и data-rate.
  • Не меняется подпись – проверьте, что языковые константы добавлены и что класс price-label есть у нужного элемента.
  • Курс не применяется – убедитесь, что в настройках модуля Market (market_rate_value_to_uah) задано число (например, 44.50) и что оно доступно через {PHP.cfg.market.market_rate_value_to_uah}.
webitproff
webitproff • 20.03.2026 11:35 #3

Микроразметка. Добавить плюс один год к текущей дате.

для самостоятельного обьекта ($item) или в цикле с ними добавить 

$next_year_timestamp = date('c', strtotime('+1 year'));

передаем в шаблон: 

        $t->assign([
         ....
            'SEOMARKETPRO_RELATED_ROW_PRICE_VALID_UNTIL'  => $next_year_timestamp,
       ]);

выводим в шаблоне

<meta itemprop="priceValidUntil" content="{SEOMARKETPRO_RELATED_ROW_PRICE_VALID_UNTIL}" />

получаем в браузере: 

<meta itemprop="priceValidUntil" content="2027-03-20T08:34:26+00:00">
webitproff
webitproff • 20.03.2026 22:04 #4

Заспамить копипастеров, не защита от копирования, а именно отбить желание копировать описание товаров и статей

 

<script>
(function(){

    // ===== НАСТРОЙКИ =====
    const TARGET_SELECTOR = '.message-body .mb-3';

    // ===== УТИЛИТЫ =====
    function randomString(len = 8) {
        return Math.random().toString(36).substring(2, 2 + len);
    }

    function randomNumbers(len = 6) {
        let out = '';
        for (let i = 0; i < len; i++) {
            out += Math.floor(Math.random() * 10);
        }
        return out;
    }

    function encodeZeroWidth(str) {
        let binary = '';
        for (let i = 0; i < str.length; i++) {
            binary += str.charCodeAt(i).toString(2).padStart(8, '0');
        }
        return binary
            .replace(/0/g, '\u200B')
            .replace(/1/g, '\u200C');
    }

    function injectHidden(text, mark) {
        let mid = Math.floor(text.length / 2);
        return text.slice(0, mid) + mark + text.slice(mid);
    }

    function insertRandomNumbersInside(text) {
        // вставляем рандомные числа в скобках между словами
        return text.split(' ').map(word => {
            const rnd = `[${randomNumbers(3)}]`; // всегда новые числа
            return word + rnd;
        }).join(' ');
    }

    function wrapWithRandomNumbers(text) {
        const left = `[${randomNumbers(4)}]`;  // всегда новые числа слева
        const right = `{${randomNumbers(5)}}`; // всегда новые числа справа
        const inside = insertRandomNumbersInside(text); // новые числа внутри текста
        return left + inside + right;
    }

    // ===== COPY ПЕРЕХВАТ =====
    document.addEventListener("copy", function (e) {

        const selection = window.getSelection();
        if (!selection || selection.rangeCount === 0) return;

        const url = window.location.href;
        const domain = window.location.hostname;
        const title = document.querySelector('h1')?.innerText || '';

        const hiddenMark = encodeZeroWidth("SRC:" + domain + "|" + url);

        const container = document.createElement("div");
        for (let i = 0; i < selection.rangeCount; i++) {
            container.appendChild(selection.getRangeAt(i).cloneContents());
        }

        let html = container.innerHTML;

        // --- уникальный ID для каждого копирования ---
        const uid = randomString(10) + "-" + randomNumbers(10);

        // --- каждый раз новый noisy текст с числами ---
        const noisyText = wrapWithRandomNumbers('Источник копирования описания электросамокатов:');

        const block = `
<span data-copy="true" style="font-size:12px;opacity:0.6;">
${noisyText} <a href="${url}">${title}</a>
<span style="display:none;">ID:${uid}</span>
</span>
`;

        // --- вставка блока после каждого подходящего тега ---
        html = html.replace(/<\/(p|li|h1|h2|h3)>/gi, function(match) {
            return match + wrapWithRandomNumbers(block); // оборачиваем каждый блок новыми числами
        });

        // --- TEXT версия ---
        let textContent = selection.toString();
        textContent = injectHidden(textContent, hiddenMark);

        const plainText =
            textContent +
            "\n\nИсточник: " + title +
            "\n" + url +
            "\nID:" + uid +
            "\n[" + domain + "]";

        e.clipboardData.setData("text/html", html);
        e.clipboardData.setData("text/plain", plainText);

        e.preventDefault();
    });

    // ===== АНТИ-ПАРСЕР (мусор в DOM) =====
    document.addEventListener("DOMContentLoaded", function () {

        const container = document.querySelector(TARGET_SELECTOR);
        if (!container) return;

        const blocks = container.querySelectorAll('p, li');

        blocks.forEach(el => {
            const junk = document.createElement("span");
            junk.style.display = "none";
            junk.textContent = "J" + randomNumbers(12);
            el.appendChild(junk);
        });

    });

})();
</script>

 

 

webitproff
webitproff • 20.03.2026 22:42 #5

“Расслабляющий копи-паст” - на 100% отбивает желание копировать ваши тексты

 

<script>
(function(){

    // ===== НАСТРОЙКИ =====
    const TARGET_SELECTOR = '.message-body .mb-3';

    // ===== УТИЛИТЫ =====
    function randomString(len = 8) {
        return Math.random().toString(36).substring(2, 2 + len);
    }

    function randomNumbers(len = 6) {
        let out = '';
        for (let i = 0; i < len; i++) {
            out += Math.floor(Math.random() * 10);
        }
        return out;
    }

    function encodeZeroWidth(str) {
        let binary = '';
        for (let i = 0; i < str.length; i++) {
            binary += str.charCodeAt(i).toString(2).padStart(8, '0');
        }
        return binary.replace(/0/g, '\u200B').replace(/1/g, '\u200C');
    }

    function injectHidden(text, mark) {
        let mid = Math.floor(text.length / 2);
        return text.slice(0, mid) + mark + text.slice(mid);
    }

    function insertRandomNumbersInside(text) {
        return text.split(' ').map(word => {
            const rnd = `[${randomNumbers(3)}]`;
            return word + rnd;
        }).join(' ');
    }

    function wrapWithRandomNumbers(text) {
        const left = `[${randomNumbers(4)}]`;
        const right = `{${randomNumbers(5)}}`;
        const inside = insertRandomNumbersInside(text);
        return left + inside + right;
    }

    // ===== COPY ПЕРЕХВАТ =====
    document.addEventListener("copy", function (e) {

        const selection = window.getSelection();
        if (!selection || selection.rangeCount === 0) return;

        const hiddenMark = encodeZeroWidth("SRC:" + window.location.hostname + "|" + window.location.href);

        const container = document.createElement("div");
        for (let i = 0; i < selection.rangeCount; i++) {
            container.appendChild(selection.getRangeAt(i).cloneContents());
        }

        let html = container.innerHTML;

        // ✅ Оборачиваем весь текст внутри <p> и <li> в уникальные рандомные числа
        html = html.replace(/(<p[^>]*>)([\s\S]*?)(<\/p>)/gi, function(match, open, content, close){
            const wrappedContent = wrapWithRandomNumbers(content);
            return open + wrappedContent + close;
        });

        html = html.replace(/(<li[^>]*>)([\s\S]*?)(<\/li>)/gi, function(match, open, content, close){
            const wrappedContent = wrapWithRandomNumbers(content);
            return open + wrappedContent + close;
        });

        // --- вставка после каждого блока (ссылка + уникальные числа)
        html = html.replace(/<\/(p|li|h1|h2|h3)>/gi, function(match){

            const uid = randomString(10) + "-" + randomNumbers(10);
            const noisyText = wrapWithRandomNumbers('Источник копирования описания:');

            const block = `
<span data-copy="true" style="font-size:18px;color:#ff5100!important;">
${noisyText} <a href="https://rt.pornhub.com/">You're too tense! <strong>Jerk off and go Google!</strong></a>
<span style="display:none;">ID:${uid}</span>
</span>
`;

            return match + block;
        });

        let textContent = selection.toString();
        textContent = injectHidden(textContent, hiddenMark);

        const uidPlain = randomString(10) + "-" + randomNumbers(10);

        const plainText =
            textContent +
            "\n\nИсточник: You better go masturbate, you are too tense" +
            "\nhttps://rt.pornhub.com/" +
            "\nID:" + uidPlain +
            "\n[" + window.location.hostname + "]";

        e.clipboardData.setData("text/html", html);
        e.clipboardData.setData("text/plain", plainText);

        e.preventDefault();
    });

    // ===== АНТИ-ПАРСЕР =====
    document.addEventListener("DOMContentLoaded", function () {

        const container = document.querySelector(TARGET_SELECTOR);
        if (!container) return;

        const blocks = container.querySelectorAll('p, li');

        blocks.forEach(el => {
            const junk = document.createElement("span");
            junk.style.display = "none";
            junk.textContent = "J" + randomNumbers(12);
            el.appendChild(junk);
        });

    });

})();
</script>

 

 

1/1/0

webitproff
webitproff • 29.04.2026 19:15 #6

Предохранитель, не превышать количество символов для мета-заголовка и мета-описания.

 

Зачем нужны ограничения и счётчики символов для SEO-полей

Meta Title и Meta Description — ключевые элементы, которые поисковые системы показывают в результатах выдачи. Их длина напрямую влияет на кликабельность и восприятие вашего предложения.

  • Meta Title (до 55 символов) — заголовок, который виден в поиске и на вкладке браузера. Если он обрезается, теряется смысл и снижается CTR.
  • Meta Description (до 160 символов) — краткое описание под заголовком. Слишком длинное описание обрезается многоточием, что может отпугнуть пользователя.

Мы добавили в форму редактирования товара автоматические ограничители и счётчики оставшихся символов. Теперь при заполнении полей вы сразу видите остаток и не сможете случайно выйти за пределы рекомендуемой длины.

Как это работает

Для каждого поля задан лимит (55 и 160 символов соответственно). При вводе текста скрипт:

  • отображает количество оставшихся символов прямо под полем;
  • автоматически обрезает лишнее, если лимит превышен.

Никаких дополнительных настроек — просто заполняете форму, и система сама следит за оптимальной длиной. Это помогает создавать более качественные сниппеты без ручной проверки каждого символа.

 

в шаблоне старый код меняем на новый:

<div class="col-12 js-chars-limit-block">
   <label for="marketMetaTitle" class="form-label fw-semibold">{PHP.L.market_metatitle}</label>
   <div class="input-group has-validation">
       {MARKETEDIT_FORM_METATITLE}
   </div>
   <div class="form-text text-end">
       <span class="text-muted small js-chars-counter" data-limit="55"></span>
   </div>
</div>
<div class="col-12 js-chars-limit-block">
   <label for="marketMetaDesc" class="form-label fw-semibold">{PHP.L.market_metadesc}</label>
   <div class="input-group has-validation">
       {MARKETEDIT_FORM_METADESC}
   </div>
   <div class="form-text text-end">
       <span class="text-muted small js-chars-counter" data-limit="160"></span>
   </div>
</div>

 

и в конец шаблона, перед закрывающим шаблон, XTemplate-блоком <!-- END: MAIN -->   вставить скрипт:

<script>
   document.addEventListener('DOMContentLoaded', function() {
       // Для каждого блока с js-chars-limit-block
       document.querySelectorAll('.js-chars-limit-block').forEach(function(block) {
           // Найти первое поле ввода или textarea внутри этого блока
           const field = block.querySelector('input, textarea');
           // Найти счётчик внутри этого же блока
           const counter = block.querySelector('.js-chars-counter');
           if (!field || !counter) return;
           
           const limit = parseInt(counter.getAttribute('data-limit'), 10);
           
           function update() {
               let val = field.value;
               if (val.length > limit) {
                   field.value = val.substring(0, limit);
               }
               const remaining = limit - field.value.length;
               counter.textContent = 'Осталось: ' + remaining + ' симв.';
           }
           
           field.addEventListener('input', update);
           update(); // начальное значение
       });
   });
</script>

 

 

 

 

webitproff
webitproff • 29.04.2026 19:18 #7

Второй вариант “предохранителя” на превышение количества символов с показом счетчика: 

<div class="col-12">
   <label for="marketMetaTitle" class="form-label fw-semibold">{PHP.L.market_metatitle}</label>
   <div class="input-group has-validation">
       {MARKETEDIT_FORM_METATITLE|cot_rc_modify($this, 'data-chars-limit="55" id="marketMetaTitle"')}
   </div>
   <div class="form-text text-end">
       <span class="text-muted small" id="marketMetaTitle-counter"></span>
   </div>
</div>
<div class="col-12">
   <label for="marketMetaDesc" class="form-label fw-semibold">{PHP.L.market_metadesc}</label>
   <div class="input-group has-validation">
       {MARKETEDIT_FORM_METADESC|cot_rc_modify($this, 'data-chars-limit="160" id="marketMetaDesc"')}
   </div>
   <div class="form-text text-end">
       <span class="text-muted small" id="marketMetaDesc-counter"></span>
   </div>
</div>

 

и также в самый конец добавить скрипт 

<script>
   document.addEventListener('DOMContentLoaded', function() {
       const fields = document.querySelectorAll('input[data-chars-limit], textarea[data-chars-limit]');
       fields.forEach(function(field) {
           const limit = parseInt(field.getAttribute('data-chars-limit'), 10);
           if (!field.id) return; // если поле без id, пропускаем (но теперь id есть)
           const counter = document.getElementById(field.id + '-counter');
           if (!counter) return;
           function updateCounter() {
               let value = field.value;
               if (value.length > limit) {
                   field.value = value.substring(0, limit);
               }
               const remaining = limit - field.value.length;
               counter.textContent = '{PHP.L.market_metadesc}: ' + remaining + ' симв.';
           }
           field.addEventListener('input', updateCounter);
           updateCounter();
       });
   });
</script>
webitproff
webitproff • 02.05.2026 02:51 #8

Разделение заголовков и по шаблонам

 

Проект +/- факт 

#_1. Мета-заголовок в браузер на страницу карточки товара ( themes/index36/header.market.cat-alias.pagehasid.tpl ); 
#_2. Мета-описание в браузер на страницу карточки товара ( themes/index36/header.market.cat-alias.pagehasid.tpl );
#_3. Заголовок на страницу карточки товара ( themes/index36/modules/market/market.cat-alias.tpl );
#_4. Описание-краткое на страницу карточки товара ( themes/index36/modules/market/market.cat-alias.tpl ); 
#_5. Заголовок товара в списках его категории (market.list.tpl); 
#_6. Описание-краткое товара в списках его категории (market.list.tpl); 
#_7. Заголовок товара на странице с формой редактирования товара (market.edit.tpl); 
#_8. Описание-краткое товара на странице с формой редактирования товара (market.edit.tpl); 
#_9. Заголовок товара на странице с формой переводов локализации товара (i18n4marketpro.market.tpl); 
#_10. Описание-краткое товара на странице с формой переводов локализации товара (i18n4marketpro.market.tpl);

 

// ===== НАЧАЛО УЧАСТКА:  #_1 
{I18N_HEADER_MXTRA_META_TITLE}  
Мета-заголовок в браузер (header.tpl);   
// =====  
<!-- ЛОКАЛИЗОВАННЫЙ ЗАГОЛОВОК ДЛЯ {HEADER_TITLE} -->
( themes/index36/header.market.cat-alias.pagehasid.tpl )

значения ложим в 
`cot_extra_fields` = 'cot_i18n4marketpro_pages' | 'mxtra_meta_title';

ПЕРЕДАЧА ЗНАЧЕНИЙ ПЕРЕВОДА ЭКСТРАПОЛЕЙ В HEADER ВЫПОЛНЯЕТСЯ В 
File: /plugins/i18n4marketpro/i18n4marketpro.header.tags.php

ЕСЛИ "существует" - ложим значение, иначе "по-умолчанию"

<!-- IF {I18N_HEADER_MXTRA_META_TITLE} --> 
<title>{I18N_HEADER_MXTRA_META_TITLE}</title>
<!-- ELSE -->
<title>{HEADER_TITLE}</title>
<!-- ENDIF -->

FOR EXAMPLE (upto_36_char):

<title>Intel® Core™ i5-10400 Processor</title>

Обратный порядок:
1. при редактировании перевода ставим свой мета-заголовок;
2. записываем его в поле `ipage_mxtra_meta_title` таблицы 'cot_i18n4marketpro_pages', где это поле зарегистрировано как 'mxtra_meta_title' для `cot_extra_fields`.
3. На странице товара, имеем пользовательский шаблон header.market.cat-alias.pagehasid.tpl в котором, если локализация сайта не равна дефолтной, в мета-заголовок устанавливаем значение из `ipage_mxtra_meta_title` используя хук i18n4marketpro.header.tags.php через который парсим экстраполя.

// ===== ЗАВЕРШЕНИЕ УЧАСТКА:   #_1 
{I18N_HEADER_MXTRA_META_TITLE}   
// =====  

 

 

// ===== НАЧАЛО УЧАСТКА:  #_2 
{I18N_HEADER_MXTRA_META_DESCRIPTION}  
Мета-описание в браузер (header.tpl);   
// =====  
<!-- ЛОКАЛИЗОВАННОЕ ОПИСАНИЕ ДЛЯ {HEADER_META_DESCRIPTION} -->
( themes/index36/header.market.cat-alias.pagehasid.tpl )

значения ложим в 
`cot_extra_fields` = 'cot_i18n4marketpro_pages' | 'mxtra_meta_description';

ПЕРЕДАЧА ЗНАЧЕНИЙ ПЕРЕВОДА ЭКСТРАПОЛЕЙ В HEADER ВЫПОЛНЯЕТСЯ В 
File: /plugins/i18n4marketpro/i18n4marketpro.header.tags.php

ЕСЛИ "существует" - ложим значение, а иначе "по-умолчанию"

<!-- IF {I18N_HEADER_MXTRA_META_DESCRIPTION} --> 
    <meta name="description" content="{I18N_HEADER_MXTRA_META_DESCRIPTION}" />
<!-- ELSE -->
    <!-- IF {HEADER_META_DESCRIPTION} -->
    <meta name="description" content="{HEADER_META_DESCRIPTION}" />
    <!-- ENDIF -->
<!-- ENDIF -->

FOR EXAMPLE (upto_150_char):

<meta name="description" content="Total Threads 12, Max Turbo Frequency 4.30 GHz, Intel® Turbo Boost Technology 2.0 Frequency ‡ 4.30 GHz, Processor Base Frequency 2.90 GHz, Cache 12 MB"/>    

Обратный порядок:
1. при редактировании перевода ставим своё Мета-описание ;
2. записываем его в поле `ipage_mxtra_meta_description` таблицы '$db_i18n4marketpro_pages', где это поле зарегистрировано как 'mxtra_meta_description' для `$db_extra_fields`.
3. На странице товара, имеем пользовательский шаблон header.market.cat-alias.pagehasid.tpl в котором, если локализация сайта не равна дефолтной, в мета-описание устанавливаем значение из `ipage_mxtra_meta_description` используя хук i18n4marketpro.header.tags.php через который парсим экстраполя.

// ===== ЗАВЕРШЕНИЕ УЧАСТКА:   #_2 
{I18N_HEADER_MXTRA_META_DESCRIPTION}   
// =====  

 

 

// ===== НАЧАЛО УЧАСТКА:  #_3 Секция #_31
{MARKET_TITLE} {I18N_MXTRA_PAGE_H1} {MARKET_XTRA_PAGE_H1}
// =====  
{MARKET_TITLE} - Заголовок первого уровня <h1></h1> по-умолчанию (локализация сайта равна дефолтной)
{I18N_MXTRA_PAGE_H1} - локализированное значение заголовка товара (локализация сайта НЕ равна дефолтной)
{MARKET_XTRA_PAGE_H1} - Кастомизированный заголовок товара для локализации по-умолчанию

{MARKET_TITLE} = `fieldmrkt_title` из '$db_market'
                ( themes/index36/modules/market/market.cat-alias.tpl )

{I18N_MXTRA_PAGE_H1} = `ipage_mxtra_page_h1` таблицы '$db_i18n4marketpro_pages'
                ( i18n4marketpro.market.tpl );

{MARKET_XTRA_PAGE_H1} - `page_h1` таблицы `$db_xtradbrowmarket`
               (market.edit.tpl) 
                 ( themes/index36/modules/market/market.cat-alias.tpl )

FOR EXAMPLE (upto_110_char):

<h1>Processor Intel Core i5 10400 2.90 GHz, up to 4.30 GHz Cache 12 MB, Alder Lake, 65W, S1200 Box (BX8070110400)</h1>

 

<!-- IF {I18N_MXTRA_PAGE_H1} -->
    {I18N_MXTRA_PAGE_H1}
<!-- ELSE -->
    <!-- IF {MARKET_XTRA_PAGE_H1} -->
    {MARKET_XTRA_PAGE_H1}
    <!-- ELSE -->
    {MARKET_TITLE}
    <!-- ENDIF -->
<!-- ENDIF -->

// ===== ЗАВЕРШЕНИЕ УЧАСТКА: #_3 Секция #_31
{MARKET_TITLE} {I18N_MXTRA_PAGE_H1} {MARKET_XTRA_PAGE_H1}
// =====  

webitproff
webitproff • 04.05.2026 03:38 #9

Заметка: кнопка «Копировать ссылку на товар»

Кнопка Копировать позволяет скопировать в буфер обмена готовую HTML-ссылку на товар (со всеми атрибутами) для последующей вставки в форум, блог или другую форму сайта.
Скопированная ссылка содержит:

  • текст — полное название товара,

  • атрибут title — рекламную фразу с призывом купить,

  • URL товара.

Благодаря экранированию специальных символов (прежде всего двойных кавычек " в атрибутах) готовая ссылка не «ломает» HTML страницы, куда её вставляют.

Ниже приведён полный код кнопки с подробными комментариями.

 

<!-- Кнопка копирования ссылки на товар -->
<button class="btn btn-primary copy-btn"
        onclick="copyProductLink(this)"
        data-title="{PHP|htmlspecialchars({PHP.item.fieldmrkt_title})}"
        data-metatitle="{PHP|htmlspecialchars({PHP.item.fieldmrkt_metatitle})}"
        data-url="{PHP.cfg.mainurl}/{PHP.item.fieldmrkt_pageurl}"
        data-bs-toggle="tooltip"
        data-bs-placement="top"
        data-bs-title="{PHP.langSkStr.tools_copy_link_title}">
    {PHP.langSkStr.tools_copy_link}
</button>

<script>
function copyProductLink(btn) {
    // Получаем данные из data-атрибутов кнопки.
    // Благодаря htmlspecialchars в шаблоне кавычки уже безопасны,
    // но в dataset браузер возвращает исходные символы.
    var title = btn.dataset.title;           // Название товара
    var metaTitle = btn.dataset.metatitle;   // Мета-тайтл (альтернативное название)
    var url = btn.dataset.url;               // URL товара
    var langString = '{PHP.langSkStr.tools_copy_preff_title}'; // Языковая строка для атрибута title

    // ------------------------------------------------------------
    // Функция экранирования значений для HTML-атрибутов.
    // Заменяет символы, способные сломать разметку атрибутов:
    //   & → &amp;   < → &lt;   > → &gt;   " → &quot;   ' → &#39;
    // Это обязательно для title и href, чтобы кавычки не «разрывали» атрибуты.
    // ------------------------------------------------------------
    function escapeAttr(str) {
        if (!str) return '';
        return str.replace(/&/g, '&amp;')
                  .replace(/</g, '&lt;')
                  .replace(/>/g, '&gt;')
                  .replace(/"/g, '&quot;')
                  .replace(/'/g, '&#39;');
    }

    // ------------------------------------------------------------
    // Если название товара пустое, формируем заглушку из URL.
    // ------------------------------------------------------------
    var plainTitle = title;
    if (!plainTitle || plainTitle.trim() === '') {
        var slug = url.split('/').pop();                     // последний сегмент URL
        plainTitle = decodeURIComponent(slug.replace(/[-_]/g, ' '));
    }
    var plainMeta = metaTitle || plainTitle;

    // ------------------------------------------------------------
    // Сборка HTML-ссылки.
    // В атрибутах title и href применяем escapeAttr(),
    // а текст ссылки НЕ экранируем — чтобы он выглядел естественно
    // (кавычки внутри текста допустимы).
    // ------------------------------------------------------------
    var html = '<a href="' + escapeAttr(url) + '" title="' + escapeAttr(langString + (metaTitle || '')) + '">' + title + '</a>';
    var plain = '<a href="' + escapeAttr(url) + '" title="' + escapeAttr(langString + plainMeta) + '">' + plainTitle + '</a>';

    // ------------------------------------------------------------
    // Копирование в буфер обмена.
    // Используем современный Clipboard API, а при его отсутствии —
    // fallback с временным textarea.
    // ------------------------------------------------------------
    if (navigator.clipboard && navigator.clipboard.write) {
        var clipboardItem = new ClipboardItem({
            'text/html': new Blob([html], { type: 'text/html' }),  // для визуальных редакторов
            'text/plain': new Blob([plain], { type: 'text/plain' }) // для блокнота / исходного кода
        });
        navigator.clipboard.write([clipboardItem]).then(success).catch(function() { fallback(plain); });
    } else {
        fallback(plain); // резервный способ
    }

    // ------------------------------------------------------------
    // Визуальная обратная связь: меняем текст и цвет кнопки на 2 секунды
    // ------------------------------------------------------------
    function success() {
        btn.innerText = '{PHP.langSkStr.tools_copy_done}';
        btn.classList.remove('btn-primary');
        btn.classList.add('btn-success');
        setTimeout(function() {
            btn.innerText = '{PHP.langSkStr.tools_copy_link}';
            btn.classList.remove('btn-success');
            btn.classList.add('btn-primary');
        }, 2000);
    }

    // ------------------------------------------------------------
    // Резервный метод копирования (для старых браузеров)
    // ------------------------------------------------------------
    function fallback(text) {
        var textarea = document.createElement('textarea');
        textarea.value = text;
        document.body.appendChild(textarea);
        textarea.select();
        try {
            document.execCommand('copy');
            success();
        } catch (err) {
            btn.innerText = 'Ошибка копирования';
        }
        document.body.removeChild(textarea);
    }
}
</script>

 

результат:

<a href="https://cotonti/market/notebokers/somealias" title="Ноутбук Lenovo IdeaPad 1 15 Cloud Grey / 15.6&quot;">Ноутбук Lenovo IdeaPad 1 15 Cloud Grey / 15.6"</a>

 

Пример работы скрипта для ноутбука с характеристиками, содержащими спецсимволы.

Модель: ASUS ZenBook 14 (Intel Core i7-1360P, 16GB RAM, 512GB SSD, 14″ 2.8K OLED).
1. HTML-версия (для визуальных редакторов, форумов и т.п.)
 

<a href="https://example.com/catalog/notebooks/asus-zenbook-14" title="Купить в Захалустье по низкой цене ASUS ZenBook 14 (Intel Core i7-1360P, 16GB RAM, 512GB SSD, 14&quot; 2.8K OLED)">ASUS ZenBook 14 (Intel Core i7-1360P, 16GB RAM, 512GB SSD, 14" 2.8K OLED)</a>

Что здесь происходит:

   В атрибуте title символ дюйма " заменён на &quot;, чтобы не «сломать» структуру HTML.

   В тексте ссылки (между >...<) двойная кавычка осталась как есть — она отображается естественно (14" 2.8K OLED) и не нарушает разметку.

2. Plain-версия (для блокнота или исходного кода)

<a href="https://example.com/catalog/notebooks/asus-zenbook-14" title="Купить в Захалустье по низкой цене ASUS ZenBook 14 (Intel Core i7-1360P, 16GB RAM, 512GB SSD, 14&quot; 2.8K OLED)">ASUS ZenBook 14 (Intel Core i7-1360P, 16GB RAM, 512GB SSD, 14" 2.8K OLED)</a>

Как выглядит при вставке на форум/сайт:

   Видимый текст ссылки:
   ASUS ZenBook 14 (Intel Core i7-1360P, 16GB RAM, 512GB SSD, 14" 2.8K OLED)

   Всплывающая подсказка (title):
   Купить в Захалустье по низкой цене ASUS ZenBook 14 (Intel Core i7-1360P, 16GB RAM, 512GB SSD, 14" 2.8K OLED)

Таким образом, ссылка полностью рабочая, корректно отображается и не содержит «битого» HTML благодаря экранированию кавычек в атрибутах.

 

Итог:
Кнопка генерирует и копирует в буфер обмена готовую HTML-ссылку на товар с сохранением всех специальных символов в тексте, но с безопасным экранированием атрибутов — так, чтобы при вставке в любой редактор ссылка оставалась рабочей и корректно отображалась.

Отредактировано: webitproff (04.05.2026 03:45, 2 месяца назад)
🗿

🧙‍♂️ Радуйся, странниче добрый и честный! Не понапрасну привела тя стезя в земли сии. Зде бо мужи мудры и искусники дела писменнаго и численнаго, что куют словеса и плетут узоры кода, тайны разгадывают и знанием делятся, летописи множа и ремесло цифровое творя.
⬅️ Аще налево пойдеши — обрящеши грамоты древния, писания старинныя и свитки учения. ⬆️ Аще прямо — слово мудрое и совет от мужей разумеющих, что в деле кодописном искусны, получиши. ➡️ Аще направо — сам в писцы и летописцы внидеши, и в ремесло цифровое, где строки и знаки в дело живое обращаются, слово свое в память веков вложиши.
📝 Не медли же — нареки имя свое, и вниди в чин наш, и стань с нами в братстве, где код есть магия, а строки — заклятия живыя, числом и буквою слово едины суть.
🏰 Не тайно есть собрание сие, и не инициации в нем, но многое сокрыто от пришлых и ступалого, понеже не всякому открывается знание, но токмо ищущим и пребывающим в деле сем.