0 Пользователей и 1 Гость просматривают эту тему.
  • 3 Ответов
  • 933 Просмотров
*

rsn

  • Давно я тут
  • 520
  • 34 / 3
Замутил небольшой скрипт из 2 файлов для быстрого изменения Названия и Алиаса товарам VirtueMart.
Особенно полезен для seo целей, когда надо найти товары с длинными заголовками и урлами и переделать их.
Стандартными ручными средствами (заходить в каждый товар) это очень долго.
(полностью автоматизировать - тоже не вариант, очень много нюансов для разных товаров).
А этим скриптом сократил время на выполнение задачи своему помощнику с 30 до 4 дней  ;)
Работает по AJAX.
Предусмотрены всевозможные защиты от "не очень опытного пользователя" )
Автоматический транслит псведонимов, автоматический пересчет символов "на лету", несколько уровней контроля, чтобы исполнитель сильно не накосячил )
Лимиты (сколько символов считать длинным) легко подстраиваемые под нужные цели, разумеется - отдельный лимит для названия, отдельно - для алиаса.
Преимущество скрипта в том, что он сразу же создает опубликованное правило редиректа (в стандартном менеджере перенаправлений Joomla). То есть поисковики не потеряют ваши страницы по старым урлам
(возможность использования данного пункта зависит от настроек сайта; разумеется, я сделал конкретно под свои настройки).
Мега SEO и тайм-менеджмент оптимизированное решение )

Кому интересно, поделюсь.

Пока что мне интересно, будет ли тут это кому-то интересно :)
Выглядит примерно так:

« Последнее редактирование: 20.04.2019, 22:20:02 от rsn »
Возможно, будет интересно: Интеграция с Ozon
*

Technic

  • Захожу иногда
  • 194
  • 7 / 0
Хотелось бы потестить
*

rsn

  • Давно я тут
  • 520
  • 34 / 3
1. Создаём в корне сайта папку, например, my_scripts (в моём случае я сделал в ней ещё одну подпапку for_us - это не обязательно);

2. На всякий случай создаем в этой папке файл .htaccess, которым блокируем доступ для всех, кроме себя. Что-то типо:
Код
# Закрываем доступ к PHP файлам извне

<FilesMatch ".php$">
Deny from all
# Мой домашний провайдер
Allow from 888.88.888.8 ----- разумеется, это меняете на свой IP или подсеть
# Рабочий IP
# Allow from 88.888.88.88
</FilesMatch>

3. Создаём в этой папке файл rename.php
Код
<?php
ini_set("display_errors", "1"); // выводим ошибки php
error_reporting(E_ALL);

// SETTING
$url_start        = 'catalog/';
$url_end          = '-detail';
$max_h1_length    = 60; // макс. длина заголовка
$max_alias_length = 47; // макс. длина псевдонима
$root_folder      = 'public_html'; // название корневой папки сайта на хостинге (обычно public_html или www)

$debug_mode = 0; // 0 или 1
?>
<!DOCTYPE html>
<html lang="ru-ru" dir="ltr">
<head>
    <title>Исправление длинных названий и псевдонимов товаров</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
    <style>
        body {font-family: monospace;}
        .wrap {max-width: 1366px; margin: 0 auto 100px; /* border-right: 1px solid #ccc; */}
        ul {padding: 0;}
        .item {border-top: 1px solid #ccc; padding: 10px 0 10px 10px; list-style-type: none;}
        .item:last-child {border-bottom: 1px solid #ccc;}
        .item:nth-child(even) {background: #f5f5f5;}
        .item:hover {background: #ecfcff;}
        .item.done, .item.done * {color: #ccc !important; border-color: #ddd;}
        .item-head pre, .item-dop pre {display: inline-block; margin-right: 30px;}
        .item-head pre:last-child, .item-dop pre:last-child {margin-right: 0;}
        .item-name {font-weight: bold; margin-left: 2px;}
        .item-sku {color: rgba(0,0,0,0.5);}
        .item-id {color: rgba(0,0,0,0.5);}
        .item-alias {color: rgba(0,0,0,0.5); max-width: 510px;}
        .item-fields {padding-left: 0;}
        .item-header {display: inline-block; margin-right: 30px;}
        .item-slug {display: inline-block;}
        .item-actions {position: relative;}
        .item-actions, .item-actions > * {display: inline-block;}
        .item-actions .result {position: absolute; top: 0; width: 150px; right: -150px;}
        .item-dop {color: rgba(0,0,0,0.5);}
        button {margin: 0 15px 0 25px; padding: 4px 10px; border: 1px solid #aaa; border-radius: 3px; cursor: pointer;}
        input {border: 1px solid #ccc; border-radius: 3px; font-family: monospace; padding: 4px 1px;}
        pre {margin: 10px 0;}
        pre.label {margin-right: 10px;}
        .length {margin: -5px 0 0 3px;}
        .item .error {color: red !important;}
        .item .success {color: green !important;}
        a {color: rgba(0,0,0,0.5); text-decoration: none;}
        a:hover {color: #333; text-decoration: underline;}
    </style>
</head>
<body>
<div class="wrap">
    <h1>Исправление длинных названий и псевдонимов товаров</h1>
    Одновременно с обновлением псевдонимов создаются редиректы со старых ulr на новые.<br>


<?php
// определяем корневую папку сайта
$script_folder = dirname(__FILE__);
$root_arr = explode('/', $script_folder);
while (end($root_arr) != $root_folder) array_pop($root_arr);
$root = '';
foreach ($root_arr as $root_item) {
    if ($root_item != '') $root .= '/'.$root_item;
}
if ($debug_mode == 1) echo "root = $root<br>\n";

// определим путь к скрипту от Главной
$root_arr = explode('/', $script_folder);
while (reset($root_arr) != $root_folder) array_shift($root_arr);
array_shift($root_arr);
$ajax_file_path = '';
foreach ($root_arr as $root_item) $ajax_file_path .= '/'.$root_item;
if ($debug_mode == 1) echo "ajax_file_path = $ajax_file_path<br>\n";

// Работа с БД
$require = require_once "$root/configuration.php"; // настройки сайта и БД
if ($require != true) die('Не удалось подключить файл конфигурации Joomla.'); else if ($debug_mode == 1) echo "Подключили файл конфигурации Joomla.<br>\n";
$config = new JConfig;
$prefix = $config->dbprefix;
$mysqli = new mysqli($config->host, $config->user, $config->password, $config->db);
if ($mysqli->connect_error) {
    die('Ошибка соединения с БД: ('.$mysqli->connect_errno.') '.$mysqli->connect_error);
} else if ($debug_mode == 1) echo "Подключились к БД.<br>\n";

$results = $mysqli->query("
SELECT
    COUNT(*) AS count
FROM
    ".$prefix."virtuemart_products p
LEFT JOIN
    ".$prefix."virtuemart_products_ru_ru pru USING(virtuemart_product_id)
WHERE
(LENGTH(pru.slug) > $max_alias_length OR CHAR_LENGTH(pru.product_name) > $max_h1_length)
AND p.published = 1
");
if ($results) {
    if ($debug_mode == 1) echo "Получили результаты запроса SELECT с общим кол-вом.<br>\n";
} else {
    print 'Ошибка выполнения запроса в БД: ('.$mysqli->errno.') '.$mysqli->error;
}
$row = $results->fetch_assoc();
$number = $row['count'];

$results = $mysqli->query("
SELECT
    p.virtuemart_product_id, p.product_sku, pru.product_name, pru.slug, p.product_mpn, mru.mf_name
FROM
    ".$prefix."virtuemart_products p
LEFT JOIN
".$prefix."virtuemart_products_ru_ru pru USING(virtuemart_product_id)
LEFT JOIN
".$prefix."virtuemart_product_manufacturers pm USING(virtuemart_product_id)
LEFT JOIN
".$prefix."virtuemart_manufacturers_ru_ru mru ON pm.virtuemart_manufacturer_id = mru.virtuemart_manufacturer_id
WHERE
(LENGTH(pru.slug) > $max_alias_length OR CHAR_LENGTH(pru.product_name) > $max_h1_length)
AND p.published = 1
GROUP BY
    p.virtuemart_product_id
ORDER BY
    LENGTH(pru.slug) DESC, pru.product_name ASC
LIMIT 20
");
// GROUP BY - чтобы не выводить один и тот же товар несколько раз, если ему назначено сразу несколько производителей

if ($results) {
    echo "По результатам запросов к БД выведено записей: ".$mysqli->affected_rows." из $number.<br>\n";
} else {
    print 'Ошибка выполнения запроса в БД: ('.$mysqli->errno.') '.$mysqli->error;
}
// закрываем подключение
$mysqli->close();
unset($mysqli);
if ($debug_mode == 1) echo "Закрыли подключение к БД.<br>\n<br>\n";

echo "<ul>\n";
while ($row = $results->fetch_assoc()) {
    if ($debug_mode == 1) {
        echo "<pre>";
        print_r($row);
        echo "</pre>";
    }
   
    $search_text = '';
    if (isset($row['mf_name'])) $search_text .= $row['mf_name'].' ';
    if ($row['product_mpn']) $search_text .= $row['product_mpn'].' ';
    $search_text .= $row['product_name'];
    ?>
   
    <li class='item' id='<?=$row['virtuemart_product_id'] ?>'>
            <div class='item-head'>
                <pre class='item-name '><?=$row['product_name'] ?></pre>
                <pre class='item-sku  '><?=$row['product_sku'] ?></pre>
                <pre class='item-id   '><?=$row['virtuemart_product_id'] ?></pre>
                <pre class='item-alias'><?=$row['slug'] ?></pre>
            </div>
            <div class='item-fields'>
                <div class='item-header'>
                    <input type='text' class='product-name' id='prod-name-<?=$row['virtuemart_product_id'] ?>' name='product-name-<?=$row['virtuemart_product_id'] ?>' value='<?=$row['product_name'] ?>' size='70' />
                </div>
                <div class='item-slug'>
                    <input type='text' class='product-slug' name='product-slug-<?=$row['virtuemart_product_id'] ?>' value='<?=$row['slug'] ?>' size='70' />
                </div>
                <div class='item-actions'>
                    <button class='' type='submit'>ОК</button>
                    <div class='result'></div>
                </div>
            </div>
            <div class='item-dop'>
            <?php if (!empty($row['mf_name'])) { ?>
                <pre class='label'>Производитель:</pre><pre class='item-manuf'><?=$row['mf_name'] ?></pre>
            <?php }
            if (!empty($row['product_mpn'])) { ?>
                <pre class='label'>Артикул произ.:</pre><pre class='item-mpn'><?=$row['product_mpn'] ?></pre>
            <?php } ?>
                <pre class='item-admin-link'><a href='/administrator/index.php?option=com_virtuemart&view=product&task=edit&virtuemart_product_id=<?=$row['virtuemart_product_id'] ?>' target='_blank'>Товар в админке</a></pre>
                <pre class='item-front-link'><a href='/<?=$url_start.$row['slug'].$url_end ?>' target='_blank'>Товар на сайте</a></pre>
                <pre class='item-yandex-link'><a href='//yandex.ru/search/?text=<?=$search_text ?>' target='_blank'>Найти в Яндекс</a></pre>
                <pre class='item-google-link'><a href='//www.google.ru/search?q=<?=$search_text ?>' target='_blank'>Найти в Google</a></pre>
            </div>
        </li>
<?php }
echo "</ul>";
?>


</div>

<script>
    // функция пригодится для изменения счетчиков
    function countChange(inputObj, max) {
        var simb_new = $(inputObj).val().length;
        var id = $(inputObj).parent().parent().parent().attr('id');
        var whatClass = $(inputObj).attr('class');
        if (whatClass == 'product-name') { var what = 'name'; }
        else if (whatClass == 'product-slug') { var what = 'slug'; }
       
        $('span.'+ what + '.length_' + id).text(simb_new);
        if (simb_new > max) {
            $('span.' + what + '.length_' + id).css({'color':'red'});
        } else {
            $('span.' + what + '.length_' + id).css({'color':'green'});
        }
    }
   
    // Счётчики симоволов в названиях
    var max_h1_length = <?=$max_h1_length ?>;
    var selector_name = 'input.product-name';
    $(selector_name).each(function() {
        var inputObj = $(this);
       
        var id = $(this).parent().parent().parent().attr('id');
        $(this).after('<span class="length name length_' + id + '" style="position: absolute;"></span>');
       
        countChange($(this), max_h1_length);
       
        $(this).on('input', function() {
            countChange(inputObj, max_h1_length);
        });
    });
   
    // Счётчики симоволов в псевдонимах
    var max_alias_length = <?=$max_alias_length ?>;
    var selector_slug = 'input.product-slug';
    $(selector_slug).each(function() {
        var inputObj = $(this);
       
        var id = $(this).parent().parent().parent().attr('id');
        $(this).after('<span class="length slug length_' + id + '" style="position: absolute;"></span>');
       
        countChange($(this), max_alias_length);
       
        $(this).on('input', function() {
            countChange(inputObj, max_alias_length);
        });
    });
   
    // функция транслитерации
    function urlLit(w,v) {
        //var tr='a b v g d e ["zh","j"] z i y k l m n o p r s t u f h c ch sh ["shh","shch"] ~ y ~ e yu ya ~ ["jo","e"]'.split(' ');
          var tr='a b v g d e ["j","zh"] z i y k l m n o p r s t u f h c ch sh ["shh","shch"] ~ y ~ e yu ya ~ ["jo","e"]'.split(' ');
        var ww=''; w=w.toLowerCase();
        for (i=0; i<w.length; ++i) {
            cc=w.charCodeAt(i); ch=(cc>=1072?tr[cc-1072]:w[i]);
            if(ch.length<3) ww+=ch; else ww+=eval(ch)[v];
        }
        return(ww.replace(/[^a-zA-Z0-9\-]/g,'-').replace(/[-]{2,}/gim, '-').replace( /^\-+/g, '').replace( /\-+$/g, ''));
    }
   
    $('input.product-slug').change(function() {
        // при снятии фокуса с алиаса делаем транслит
        $(this).val(urlLit($(this).val(),1)); // 1 - это версия (для ж, щ, ё)
        // и после транслитра пересчет символов
        countChange($(this), max_alias_length);
    });
   
    $('button').click(function() {
        // очистим предыдущий результат, если был
        $(this).siblings('.result').html('');
       
        var buttonObj = $(this);

        // сначала сделаем транслит алиаса
        $(this).parent().parent().children('.item-slug').children('.product-slug').val(
            urlLit($(this).parent().parent().children('.item-slug').children('.product-slug').val(),1)
        );
        // затем пересчитаем символы
        countChange($(this).parent().parent().children('.item-slug').children('.product-slug'), max_alias_length);
       
        var name_symb  = $(this).parent().parent().children('.item-header').children('.length').text();
        var alias_symb = $(this).parent().parent().children('.item-slug').children('.length').text();
        if (name_symb <= max_h1_length && alias_symb <= max_alias_length) {
            // alert('Название и алиас норм: ' + name_symb + ' и ' + alias_symb + ' символов.');
            var old_name = $(this).parent().parent().parent().children('.item-head').children('.item-name').text();
            var new_name = $(this).parent().parent().children('.item-header').children('input').val();
            var old_alias = $(this).parent().parent().parent().children('.item-head').children('.item-alias').text();
            var new_alias = $(this).parent().parent().children('.item-slug').children('input').val();
           
            var update_data = {}; // создали объект
            update_data.prod_id = $(this).parent().parent().parent().children('.item-head').children('.item-id').text();
            update_data.url_start = '<?=$url_start ?>';
            update_data.url_end = '<?=$url_end ?>';
            if (new_name != old_name) {
                update_data.new_name = new_name;
            } else {
                new_name = null;
            }
            if (new_alias != old_alias) {
                update_data.old_alias = old_alias;
                update_data.new_alias = new_alias;
               
            } else {
                new_alias = null;
            }
            if (new_name != null || new_alias != null) {
               
                var ajax = $.ajax({
                    type: 'POST',
                    url: '<?=$ajax_file_path ?>/rename_ajax.php',
                    data: update_data
                });
               
                ajax.done(function(data) {
                    $(buttonObj).siblings('.result').html(data);
                    // если в ответе есть Done
                    if (data.search(/done/i) > -1) {
                        $(buttonObj).parent().parent().parent().addClass('done');
                    }
                });
               
                ajax.fail(function() {
                    $(buttonObj).siblings('.result').html('<span class="error">Error</span>');
                });
            }
           
           
        } else {
            alert('Не косячить!\n\nCимволов в названии: ' + name_symb + '\n' + 'Символов в алиасе: ' + alias_symb);
        }
    });
   
</script>

</body>
</html>

4. В созданном файле есть блок настроек. Правим значения, разумеется, под свой сайт:
Код
// SETTING
$url_start        = 'catalog/';
$url_end          = '-detail';
$max_h1_length    = 60; // макс. длина заголовка
$max_alias_length = 47; // макс. длина псевдонима
$root_folder      = 'public_html'; // название корневой папки сайта на хостинге (обычно public_html или www)

$debug_mode = 0; // 0 или 1

5. Создаём там же второй файл rename_ajax.php
Код
<?php

ini_set("display_errors", "1"); // выводим ошибки php
error_reporting(E_ALL);

// print_r($_POST);

if (isset($_POST['prod_id']) && (isset($_POST['new_name']) || isset($_POST['new_alias']))) {
   
    $url_start = $_POST['url_start'];
    $url_end   = $_POST['url_end'];
   
    $script_folder = dirname(__FILE__);
    // определяем корневую папку сайта
    $root_arr = explode('/', $script_folder);
    while (end($root_arr) != 'public_html') array_pop($root_arr);
    $root = '';
    foreach ($root_arr as $root_item) $root .= '/'.$root_item;
   
    // Работа с БД
    $require = require_once "$root/configuration.php"; // настройки сайта и БД
    if ($require != true) die('Не удалось подключить файл конфигурации Joomla.');
    $config = new JConfig;
    $prefix = $config->dbprefix;
    $mysqli = new mysqli($config->host, $config->user, $config->password, $config->db);
    if ($mysqli->connect_error) die('Ошибка соединения с БД: ('.$mysqli->connect_errno.') '.$mysqli->connect_error);
   
    $sql = "UPDATE ".$prefix."virtuemart_products_ru_ru AS pru, ".$prefix."virtuemart_products AS p SET ";
    $nowGmDate = gmdate('Y-m-d H:i:s');
    $sql .= "p.modified_by = 0, p.modified_on = '$nowGmDate' ";
   
    if (isset($_POST['new_name'])) {
        $new_name = $mysqli->real_escape_string($_POST['new_name']);
        $sql .= ", pru.product_name = '$new_name' ";
    }
   
    $was_alias = false;
    if (isset($_POST['new_alias'])) {
        $new_alias = $_POST['new_alias'];
        $new_alias = htmlspecialchars($new_alias, ENT_QUOTES);
       
        // сначала проверим, нет ли уже такого алиаса у других товаров
        $subSql = "SELECT COUNT(*) AS count FROM ".$prefix."virtuemart_products_ru_ru WHERE slug = '$new_alias'";
        $results0 = $mysqli->query($subSql);
        $row = $results0->fetch_assoc();
        if ($row['count'] > 0) $was_alias = true;
        unset($subSql, $results0, $row);
       
        // продолжаем формировать запрос
        if ($was_alias != true) $sql .= ", pru.slug = '$new_alias' ";
    }
   
    $sql .= "WHERE pru.virtuemart_product_id = ".$_POST['prod_id']." AND p.virtuemart_product_id = pru.virtuemart_product_id";
    // echo $sql;
   
    $results = $mysqli->query($sql);
    if ($results) {
        echo "<span class='success'>Done</span>";
        if ($was_alias == true) echo "<br><span class='error'>Error: alias exists</span>";
    } else {
        echo "<span class='error'>Error: ($mysqli->errno) $mysqli->error</span>";
    }
   
    // делаем редирект (через стандартный Менеджер перенаправлений)
    if (isset($new_alias) && isset($_POST['old_alias'])) {
        if ($was_alias == true) {
            echo "<br><span class='error'>301 not created</span>";
        }
        else {
       
        // echo "Надо редирект";
        $old_alias = $_POST['old_alias'];
        // $new_alias определили выше
        $old_url = $url_start.$old_alias.$url_end;
        $new_url = $url_start.$new_alias.$url_end;
        // проверим, не было ли ранее такого редиректа
        $subSql = "SELECT COUNT(*) AS count FROM ".$prefix."redirect_links WHERE old_url LIKE '%$old_url%'";
        // echo $subSql;
        $results1 = $mysqli->query($subSql);
        $row = $results1->fetch_assoc();
        unset($subSql, $results1);
       
        if ($row['count'] > 0) {
           
            $sql = "UPDATE ".$prefix."redirect_links
                    SET new_url = '$new_url', comment = 'Product renamed', published = 1, modified_date = '$nowGmDate', header = 301
                    WHERE old_url LIKE '%$old_url%'
                    ";
            // echo $sql;
            $results2 = $mysqli->query($sql);
            if ($results2) {
                echo "<br><span class='success'>301 rewritten</span>";
            } else {
                echo "<br><span class='error'>Error: ($mysqli->errno) $mysqli->error</span>";
            }
        }
       
        else {
            $sql = "INSERT INTO ".$prefix."redirect_links (
                        old_url, new_url, comment, published, created_date, modified_date, header
                    )
                    VALUES (
                        '$old_url', '$new_url', 'Product renamed', 1, '$nowGmDate', '$nowGmDate', 301
                    )";
            // echo $sql;
            $results2 = $mysqli->query($sql);
            if ($results2) {
                echo "<br><span class='success'>301 done</span>";
            } else {
                echo "<br><span class='error'>Error: ($mysqli->errno) $mysqli->error</span>";
            }
        }
       
        }
    }
   
    // закрываем подключение
    $mysqli->close();
    unset($mysqli);
}

?>

6. Пробуем открыть скрипт в браузере примерно по такой ссылке: site.ru/my_scripts/for_us/rename.php

UPD: Чтобы стандартные редиректы Joomla работали, в VirtueMart надо отключить обработку ошибки 404.

Примечания:
Вероятно, у многих не такие настройки роутера VirtueMart (и пунктов меню), как у меня на этом сайте.
Поэтому, скорее всего, копирование 1 в 1 не даст нужного эффекта для редиректов.
Однако, дело это поправимое, если что ;)
Мысли есть, как это проще реализовать с другими настройками Вирта.
« Последнее редактирование: 02.09.2019, 11:51:54 от rsn »
Возможно, будет интересно: Интеграция с Ozon
*

rsn

  • Давно я тут
  • 520
  • 34 / 3
Немного улучшил скрипт. Добавил автоопределение префикса таблиц и ещё некоторые моменты. Пост с кодом обновил.
Возможно, будет интересно: Интеграция с Ozon
Чтобы оставить сообщение,
Вам необходимо Войти или Зарегистрироваться
 

VirtueMart 3 Отображать только товары в наличии!

Автор GaziroFFka

Ответов: 4
Просмотров: 2110
Последний ответ 06.06.2023, 13:55:13
от Akeksandr
SP VirtueMart Category Search

Автор ve1006

Ответов: 10
Просмотров: 6043
Последний ответ 18.01.2023, 10:39:10
от AzMandius
Как вывести модуль в страницу VirtueMart?

Автор harddrop

Ответов: 11
Просмотров: 2889
Последний ответ 13.11.2022, 13:11:37
от Evgen Kulibin
Как правильно настроить покупку в VirtueMart?

Автор varella

Ответов: 1
Просмотров: 1259
Последний ответ 12.10.2022, 16:14:04
от varella
Не могу сохранить порядок категорий в VirtueMart 3.9.8 и 4

Автор Egoritch

Ответов: 0
Просмотров: 1292
Последний ответ 26.05.2022, 12:30:59
от Egoritch