- Информация о материале
- Автор: JediG
- Категория: Web, Python, Ubuntu, Joomla, Virtuemart
- Просмотров: 10
Недавно я столкнулся с неприятной ситуацией: после того как я удалил несколько статей на своем сайте под управлением Joomla, меня неожиданно выкинуло из административной панели. При попытке войти снова я увидел сообщение: "Вам не разрешен доступ к панели управления". Сначала я запаниковал, но потом решил разобраться, в чем дело. Оказалось, что проблема крылась в пропавшей записи root.1
в таблице sukko_assets
. Вот как я это обнаружил и исправил.
Что произошло
Все началось после того, как я удалил несколько статей через админку на сайте https://sukkograd.ru
. Я работал с объявлениями об аренде, которые публикуются через Telegram-бота, и решил убрать несколько старых записей. После этого меня выкинуло из системы, и при попытке войти в /administrator
я получил сообщение об отсутствии доступа. Это было странно, ведь я — супер-администратор с полными правами.
Первые шаги: анализ возможных причин
Я начал думать, что могло пойти не так. Вот какие варианты я рассмотрел:
- Проблема с правами в
sukko_assets
:Я знал, что Joomla использует таблицу
sukko_assets
для управления правами доступа. Возможно, удаление статей как-то повлияло на эту таблицу. - Сбой в группе пользователей:
Может быть, мои права супер-администратора (группа Super Users, ID 8) были случайно изменены?
- Сбой сессии:
Иногда Joomla "глючит" из-за проблем с сессиями, особенно после таких действий.
- Плагины:
У меня нет специфических плагинов вроде Admin Tools, но я подумал, что что-то могло заблокировать доступ.
Как я нашел проблему
Я решил начать с базы данных, так как подозревал, что удаление статей могло повредить что-то важное. Вот что я сделал:
Шаг 1: Проверка пользователя
Сначала я зашел в phpMyAdmin и открыл базу данных сайта. В таблице sukko_users
нашел свою учетную запись по имени sukkogradoff
. Поле block
было равно 0
, значит, я не заблокирован. Затем в sukko_user_usergroup_map
проверил свою группу: мой user_id
(допустим, 7905837659) был связан с group_id = 8
. Права супер-администратора на месте, так что проблема не в этом.
Шаг 2: Проверка таблицы sukko_assets
Дальше я перешел к таблице sukko_assets
. Я знал, что она управляет правами доступа, и решил проверить корневой ассет — запись с name = 'root.1'
и id = 1
. Выполнил запрос:
SELECT * FROM sukko_assets WHERE name = 'root.1';
И тут меня ждал сюрприз — запись отсутствовала! Это был ключевой момент. Без root.1
Joomla не могла корректно определить базовые права доступа, и я потерял доступ к админке.
Шаг 3: Подтверждение гипотезы
Я проверил записи, связанные с удаленными статьями (например, com_content.article.213
), но их отсутствие не должно было повлиять на вход. Проблема явно была в пропавшем корневом ассете. Видимо, при удалении статей через админку что-то пошло не так, и запись root.1
была случайно удалена.
Как я исправил проблему
Обнаружив причину, я решил восстановить запись root.1
. В phpMyAdmin я выполнил следующий SQL-запрос:
INSERT INTO sukko_assets (id, parent_id, lft, rgt, level, name, title, rules)
VALUES (1, 0, 0, 0, 0, 'root.1', 'Root Asset', '{"core.login.site":{"6":1,"2":1},"core.login.admin":{"6":1},"core.admin":{"8":1},"core.manage":{"7":1},"core.create":{"6":1,"3":1},"core.delete":{"6":1},"core.edit":{"6":1,"4":1},"core.edit.state":{"6":1,"5":1},"core.edit.own":{"6":1,"3":1}}');
Этот запрос добавил корневой ассет с типичными правами доступа, которые используются в Joomla по умолчанию. После этого я очистил сессии, чтобы сбросить старые данные:
TRUNCATE TABLE sukko_session;
Затем я вернулся на страницу входа https://sukkograd.ru/аdministratоr
, ввел свои логин и пароль — и вуаля, доступ восстановлен!
Дополнительные проверки
Чтобы убедиться, что всё работает, я зашел в раздел Пользователи → Группы и Глобальные настройки → Разрешения. Права для группы Super Users были в порядке. Я также проверил логи ошибок в файле error_log
, но там не было ничего необычного, что подтвердило: проблема была только в sukko_assets
.
Почему это произошло?
Я думаю, что удаление статей через админку вызвало сбой в Joomla, который привел к удалению корневого ассета. Наш Telegram-бот, который публикует статьи, тут ни при чем — он аккуратно работает с sukko_content
, sukko_workflow_associations
и sukko_assets
. Скорее всего, это была случайная ошибка в интерфейсе Joomla, которая повредила структуру базы данных.
Вывод
В итоге я нашел проблему в пропавшей записи root.1
в таблице sukko_assets
. Это был интересный опыт, который научил меня внимательнее проверять базу данных после массовых изменений на сайте. Если у вас возникнет похожая ситуация, начните с проверки sukko_assets
— возможно, корень зла именно там!
Если что-то подобное повторится, я теперь знаю, куда смотреть. Надеюсь, мой опыт поможет и другим!
- Информация о материале
- Автор: JediG
- Категория: Web, Python, Ubuntu, Joomla, Virtuemart
- Просмотров: 18
Со временем в материалах на моём сайте под управлением Joomla накопился "мусор" — старые теги вида {jd_file file==4}
, оставшиеся от какого-то плагина для загрузки файлов. Они больше не работали и только портили вид контента. Вручную чистить сотни статей было не вариант, поэтому я решил написать скрипт на Python, чтобы автоматизировать процесс. Вот как это было.
Проблема
В текстах материалов (в полях introtext
и fulltext
) встречались конструкции вроде:
{jd_file file==4}
{jd_file file==123}
{jd_file file==7}
Эти теги были частью старой системы, которая уже не используется, и их нужно было просто удалить из всех материалов, чтобы привести контент в порядок.
Решение
Я написал небольшой скрипт на Python, который подключается к базе данных Joomla, ищет эти теги с помощью регулярных выражений и убирает их. Вот как это выглядело шаг за шагом.
Шаг 1: Подключение к базе данных
Для начала я настроил подключение к базе Joomla через pymysql
. Параметры подключения хранятся в файле .env
:
import pymysql import os import re from dotenv import load_dotenv import logging load_dotenv() logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s:%(message)s') jedig_db_config = { 'host': os.getenv('JEDIG_MYSQL_HOST'), 'user': os.getenv('JEDIG_MYSQL_USER'), 'password': os.getenv('JEDIG_MYSQL_PASSWORD'), 'db': os.getenv('JEDIG_MYSQL_DATABASE'), 'charset': 'utf8mb4', 'cursorclass': pymysql.cursors.DictCursor } jedig_table_prefix = 'dyutb_'
Префикс dyutb_
соответствует моей базе на сайте jedig.ru
.
Шаг 2: Определение паттерна
Чтобы найти все варианты тегов {jd_file file==<цифра>}
, я использовал регулярное выражение:
jd_file_pattern = re.compile(r'\{jd_file file==\d+\}')
Здесь \d+
означает одну или более цифр, так что паттерн ловит, например, {jd_file file==4}
или {jd_file file==123}
.
Шаг 3: Основная функция
Вот код, который выполняет очистку:
def remove_jd_file_tags(): jedig_connection = pymysql.connect(**jedig_db_config) try: with jedig_connection.cursor() as jedig_cursor: jedig_cursor.execute(""" SELECT `id`, `title`, `introtext`, `fulltext` FROM `dyutb_content` """) items = jedig_cursor.fetchall() for item in items: updated = False item_id = item['id'] title = item['title'] introtext = item['introtext'] or "" if introtext and jd_file_pattern.search(introtext): updated_introtext = jd_file_pattern.sub('', introtext) jedig_cursor.execute(""" UPDATE `dyutb_content` SET `introtext` = %s WHERE `id` = %s """, (updated_introtext, item_id)) updated = True logging.info(f"Удалены теги из introtext материала ID: {item_id}, Заголовок: {title}") fulltext = item['fulltext'] or "" if fulltext and jd_file_pattern.search(fulltext): updated_fulltext = jd_file_pattern.sub('', fulltext) jedig_cursor.execute(""" UPDATE `dyutb_content` SET `fulltext` = %s WHERE `id` = %s """, (updated_fulltext, item_id)) updated = True logging.info(f"Удалены теги из fulltext материала ID: {item_id}, Заголовок: {title}") if updated: logging.info(f"Материал ID: {item_id} обновлён.") jedig_connection.commit() logging.info("Очистка завершена.") except Exception as e: logging.error(f"Ошибка: {e}") finally: jedig_connection.close() remove_jd_file_tags()
Что делает этот код:
- Выбирает все материалы из таблицы
dyutb_content
. - Для каждого материала проверяет
introtext
иfulltext
на наличие тегов. - Если теги найдены, заменяет их на пустую строку и обновляет запись в базе.
- Логирует каждое изменение и фиксирует результат.
Шаг 4: Запуск
Я сохранил скрипт в файл clean_jd_file.py
и запустил его:
python clean_jd_file.py
Логи показали, где были изменения, например:
2025-03-11 12:34:56,123:INFO:Удалены теги из fulltext материала ID: 45, Заголовок: Land Rover Maintenance 2025-03-11 12:34:56,456:INFO:Очистка завершена.
Результат
После запуска скрипта все теги {jd_file file==<цифра>}
исчезли из материалов. Например, если раньше было:
Текст статьи {jd_file file==4} и ещё немного текста.
Теперь стало:
Текст статьи и ещё немного текста.
Контент стал чище и аккуратнее, без старого "мусора".
Вывод
Python снова выручил меня, когда нужно было быстро привести в порядок старые материалы. Регулярные выражения и прямой доступ к базе данных Joomla сделали задачу простой и эффективной. Если в будущем появится другой "мусор", я просто скорректирую паттерн в скрипте и повторю процесс.
Полный код:
import pymysql
import os
import re
from dotenv import load_dotenv
import logging
load_dotenv()
logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s:%(message)s')
jedig_db_config = {
'host': os.getenv('JEDIG_MYSQL_HOST'),
'user': os.getenv('JEDIG_MYSQL_USER'),
'password': os.getenv('JEDIG_MYSQL_PASSWORD'),
'db': os.getenv('JEDIG_MYSQL_DATABASE'),
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}
jedig_table_prefix = 'dyutb_'
# Регулярное выражение для поиска конструкций {jd_file file==<цифра>}
jd_file_pattern = re.compile(r'\{jd_file file==\d+\}')
def remove_jd_file_tags():
jedig_connection = None
try:
jedig_connection = pymysql.connect(**jedig_db_config)
with jedig_connection.cursor() as jedig_cursor:
# Выбираем все материалы с полями introtext и fulltext
jedig_cursor.execute(f"""
SELECT `id`, `title`, `introtext`, `fulltext`
FROM `{jedig_table_prefix}content`
""")
items = jedig_cursor.fetchall()
for item in items:
updated = False
item_id = item['id']
title = item['title']
# Обработка introtext
introtext = item['introtext'] or ""
if introtext and jd_file_pattern.search(introtext):
updated_introtext = jd_file_pattern.sub('', introtext)
jedig_cursor.execute(f"""
UPDATE `{jedig_table_prefix}content`
SET `introtext` = %s
WHERE `id` = %s
""", (updated_introtext, item_id))
updated = True
logging.info(f"Удалены конструкции jd_file из introtext материала ID: {item_id}, Заголовок: {title}")
# Обработка fulltext
fulltext = item['fulltext'] or ""
if fulltext and jd_file_pattern.search(fulltext):
updated_fulltext = jd_file_pattern.sub('', fulltext)
jedig_cursor.execute(f"""
UPDATE `{jedig_table_prefix}content`
SET `fulltext` = %s
WHERE `id` = %s
""", (updated_fulltext, item_id))
updated = True
logging.info(f"Удалены конструкции jd_file из fulltext материала ID: {item_id}, Заголовок: {title}")
if updated:
logging.info(f"Материал ID: {item_id} обновлён.")
jedig_connection.commit()
logging.info("Обработка завершена.")
except Exception as e:
logging.error(f"Ошибка при выполнении скрипта: {e}")
finally:
if jedig_connection:
jedig_connection.close()
if __name__ == "__main__":
remove_jd_file_tags()
- Информация о материале
- Автор: JediG
- Категория: Web, Python, Ubuntu, Joomla, Virtuemart
- Просмотров: 19
Недавно мне понадобилось добавить в некоторые материалы на сайте под управлением Joomla дополнительный текст — ссылку на Яндекс Диск с файлами. Ручное редактирование заняло бы слишком много времени, поэтому я решил автоматизировать задачу с помощью Python. В этой статье я расскажу, как это сделал.
Задача
Нужно было добавить в конец материалов из категории с ID 14 текст:
Текст для добавления:
<div class="yadisk">
Файлы, связанные с материалом, перемещены на Яндекс Диск по ссылке <a href="https://disk.yandex.com/d/Y07UWUtIk5PE2w" target="_blank">https://disk.yandex.com/d/Y07UWUtIk5PE2w</a>
</div>
Но не во все материалы, а только в те, чьи заголовки содержат слова "landrover", "defender" или "td5". Например, статья "Land Rover Defender Td5 Maintenance" должна получить этот текст, а "General Car Tips" — нет.
Решение
Я написал простой скрипт на Python, который подключается к базе данных Joomla, проверяет заголовки и обновляет поле fulltext
нужных материалов. Вот как это выглядело.
Шаг 1: Подготовка
Сначала я настроил подключение к базе данных Joomla через библиотеку pymysql
. Конфигурацию (хост, пользователь, пароль, имя базы) я вынес в файл .env
для безопасности:
import pymysql import os from dotenv import load_dotenv import logging load_dotenv() logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s:%(message)s') jedig_db_config = { 'host': os.getenv('JEDIG_MYSQL_HOST'), 'user': os.getenv('JEDIG_MYSQL_USER'), 'password': os.getenv('JEDIG_MYSQL_PASSWORD'), 'db': os.getenv('JEDIG_MYSQL_DATABASE'), 'charset': 'utf8mb4', 'cursorclass': pymysql.cursors.DictCursor }
Префикс таблицы у меня был dyutb_
, так как это Joomla на сайте jedig.ru
.
Шаг 2: Определение текста
Я создал переменную с HTML-текстом, который нужно добавить:
yadisk_text = ''' <div class="yadisk"> Файлы, связанные с материалом, перемещены на Яндекс Диск по ссылке <a href="https://disk.yandex.com/d/Y07UWUtIk5PE2w" target="_blank">https://disk.yandex.com/d/Y07UWUtIk5PE2w</a> </div> '''
Класс yadisk
я добавил, чтобы потом легко найти и заменить этот блок, если потребуется.
Шаг 3: Основная функция
Вот сама функция, которая делает всю работу:
def append_yadisk_text(): jedig_connection = pymysql.connect(**jedig_db_config) try: with jedig_connection.cursor() as jedig_cursor: jedig_cursor.execute(""" SELECT `id`, `title`, `fulltext` FROM `dyutb_content` WHERE `catid` = 14 """) items = jedig_cursor.fetchall() for item in items: title_lower = item['title'].lower() keywords = ['landrover', 'defender', 'td5'] matches_keywords = any(keyword in title_lower for keyword in keywords) if matches_keywords: current_fulltext = item['fulltext'] or "" if not current_fulltext.strip().endswith(yadisk_text.strip()): updated_fulltext = current_fulltext + yadisk_text jedig_cursor.execute(""" UPDATE `dyutb_content` SET `fulltext` = %s WHERE `id` = %s """, (updated_fulltext, item['id'])) logging.info(f"Добавлен текст в материал ID: {item['id']}, Заголовок: {item['title']}") jedig_connection.commit() logging.info("Обработка завершена.") except Exception as e: logging.error(f"Ошибка: {e}") finally: jedig_connection.close() append_yadisk_text()
Что тут происходит:
- Выбираю материалы из категории 14 с помощью SQL-запроса.
- Для каждого материала проверяю заголовок на наличие ключевых слов.
- Если заголовок подходит, добавляю текст в конец
fulltext
, но только если его там ещё нет (проверка черезendswith
). - Обновляю базу и логирую изменения.
Шаг 4: Запуск
Я сохранил скрипт в файл, например, add_yadisk.py
, и запустил его:
python add_yadisk.py
В логах увидел, какие материалы обновились, например:
2025-03-11 10:15:23,456:INFO:Добавлен текст в материал ID: 123, Заголовок: Land Rover Defender Td5 Maintenance 2025-03-11 10:15:23,789:INFO:Обработка завершена.
Результат
Теперь в конце нужных материалов появился блок со ссылкой на Яндекс Диск. Например, если открыть статью "Land Rover Defender Td5 Maintenance" в админке Joomla или на сайте, внизу будет:
При этом материалы без ключевых слов остались нетронутыми.
Вывод
Python оказался отличным инструментом для автоматизации работы с Joomla. Скрипт сэкономил мне часы ручного труда, а логи помогли убедиться, что всё прошло как надо. Если нужно будет что-то подобное сделать снова, я просто адаптирую этот код под новую задачу.
Полный код:
import pymysql
import os
from dotenv import load_dotenv
import logging
load_dotenv()
logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s:%(message)s')
jedig_db_config = {
'host': os.getenv('JEDIG_MYSQL_HOST'),
'user': os.getenv('JEDIG_MYSQL_USER'),
'password': os.getenv('JEDIG_MYSQL_PASSWORD'),
'db': os.getenv('JEDIG_MYSQL_DATABASE'),
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}
jedig_table_prefix = 'dyutb_'
# Текст для добавления в конец материалов
yadisk_text = '''
<div class="yadisk">
Файлы, связанные с материалом, перемещены на Яндекс Диск по ссылке <a href="https://disk.yandex.com/d/Y07UWUtIk5PE2w" target="_blank">https://disk.yandex.com/d/Y07UWUtIk5PE2w</a>
</div>
'''
def append_yadisk_text():
jedig_connection = None
try:
jedig_connection = pymysql.connect(**jedig_db_config)
with jedig_connection.cursor() as jedig_cursor:
# Выбираем материалы из категории 14
jedig_cursor.execute(f"""
SELECT `id`, `title`, `fulltext`
FROM `{jedig_table_prefix}content`
WHERE `catid` = 14
""")
items = jedig_cursor.fetchall()
for item in items:
title_lower = item['title'].lower()
keywords = ['landrover', 'defender', 'td5']
matches_keywords = any(keyword in title_lower for keyword in keywords)
if matches_keywords:
current_fulltext = item['fulltext'] or ""
# Проверяем, нет ли уже этого текста в конце
if not current_fulltext.strip().endswith(yadisk_text.strip()):
updated_fulltext = current_fulltext + yadisk_text
jedig_cursor.execute(f"""
UPDATE `{jedig_table_prefix}content`
SET `fulltext` = %s
WHERE `id` = %s
""", (updated_fulltext, item['id']))
logging.info(f"Добавлен текст Яндекс Диска в материал ID: {item['id']}, Заголовок: {item['title']}")
else:
logging.info(f"Текст Яндекс Диска уже присутствует в материале ID: {item['id']}, Заголовок: {item['title']}")
jedig_connection.commit()
logging.info("Обработка завершена.")
except Exception as e:
logging.error(f"Ошибка при выполнении скрипта: {e}")
finally:
if jedig_connection:
jedig_connection.close()
if __name__ == "__main__":
append_yadisk_text()
- Информация о материале
- Автор: JediG
- Категория: Web, Python, Ubuntu, Joomla, Virtuemart
- Просмотров: 17
Недавно я столкнулся с задачей переноса сайта с Joomla K2 (vizator.ru
) на новую Joomla (jedig.ru
). Главной целью было настроить переадресацию старых URL на новые, чтобы сохранить SEO и удобство для пользователей. Процесс оказался не таким простым, как я ожидал, но в итоге всё заработало. Делюсь своим опытом и рассказываю, на что стоит обратить внимание.
С чего всё началось
На старом сайте использовалась Joomla с компонентом K2, а URL имели вид https://vizator.ru/category/alias
(например, https://vizator.ru/cooling-heating/proverka-klapanov-kryshki-rasshiritelnogo-bachka
). Новый сайт на Joomla использует структуру http://jedig.ru/cars/id-alias
(например, http://jedig.ru/cars/645-proverka-klapanov-kryshki-rasshiritelnogo-bachka
). Задача — сделать 301-редирект со старых URL на новые.
Я начал с составления списка соответствий (94 записи) и попытался настроить переадресацию через файл .htaccess
. Но первые попытки провалились — вместо редиректов я получал 404 ошибки.
Проблемы и их решение
1. Ошибка в структуре URL
Изначально я предположил, что старые URL содержат ID K2, например, cooling-heating/1-proverka-klapanov-kryshki-rasshiritelnogo-bachka
. Написал правила в .htaccess
с учётом этого:
RewriteRule ^cooling-heating/1-proverka-klapanov-kryshki-rasshiritelnogo-bachka$ http://jedig.ru/cars/645-proverka-klapanov-kryshki-rasshiritelnogo-bachka [R=301,L]
Тест показал, что такие URL работают, но внутренние ссылки на сайте были без ID (cooling-heating/proverka-klapanov-kryshki-rasshiritelnogo-bachka
), и они вели на 404. Пришлось переписать все правила под реальную структуру.
2. Проверка mod_rewrite
Когда переадресация не работала даже с правильными URL, я заподозрил, что проблема в сервере. Проверил с помощью простого теста:
RewriteEngine On RewriteBase / RewriteRule ^test$ http://jedig.ru [R=301,L]
Переход на https://vizator.ru/test
перенаправил на http://jedig.ru
, значит, mod_rewrite
активен. Если бы не сработало, пришлось бы включить модуль командой sudo a2enmod rewrite
и перезапустить Apache (sudo systemctl restart apache2
).
3. Конфликт условий в .htaccess
В первом варианте файла я добавил условия для пропуска новых страниц:
RewriteCond %{REQUEST_URI} !^cooling-heating/ [NC] RewriteCond %{REQUEST_URI} !^engine/ [NC] RewriteRule ^ - [L]
Но они блокировали переадресацию, потому что применялись ко всем запросам, включая старые URL. Исправил, объединив категории в одно условие:
RewriteCond %{REQUEST_URI} !^(cooling-heating|engine|body-chassis)/ [NC] RewriteRule ^ - [L]
Итоговый .htaccess
После всех правок получился рабочий файл, который учитывает внутренние ссылки без ID K2:
RewriteEngine On RewriteBase / RewriteRule ^cooling-heating/proverka-klapanov-kryshki-rasshiritelnogo-bachka$ http://jedig.ru/cars/645-proverka-klapanov-kryshki-rasshiritelnogo-bachka [R=301,L] RewriteRule ^engine/disel-engine-td5-base-manual$ http://jedig.ru/cars/646-disel-engine-td5-base-manual [R=301,L] # ... (остальные правила) RewriteCond %{REQUEST_URI} !^(cooling-heating|engine|body-chassis|lubricants-cleaners|general-information|pages|body-electrics|axles-suspension|gearbox-clutch-transfer-gearbox|fuel-emission-systems|tuning|vnedorozhnyj-pritsep)/ [NC] RewriteRule ^ - [L]
На что обратить внимание
- Точная структура URL: Проверьте, как выглядят реальные ссылки на сайте и в поисковиках. У меня они отличались от ожидаемых (без ID), и это вызвало путаницу.
- Работа mod_rewrite: Убедитесь, что модуль включён, иначе
.htaccess
будет игнорироваться. Используйте тестовое правило для проверки. - AllowOverride: В конфигурации Apache (
/etc/apache2/sites-available/000-default.conf
) должно бытьAllowOverride All
для директории сайта. - Порядок правил: Переадресации должны идти перед условиями пропуска, иначе старые URL могут не обработаться.
- Кэш: После изменения
.htaccess
очищайте кэш браузера или проверяйте в режиме инкогнито. - Логи сервера: Если что-то не работает, смотрите
/var/log/apache2/error.log
— там могут быть подсказки.
Вывод
Настройка переадресации оказалась испытанием, но в итоге я разобрался. Главное — понять структуру URL, проверить сервер и правильно организовать правила. Теперь все старые ссылки с vizator.ru
ведут на новые страницы jedig.ru
, и трафик с SEO сохранены. Надеюсь, мой опыт поможет вам избежать моих ошибок!
Код на питоне для формирования ссылок. Но с ошибочкой :)
import pymysql
from dotenv import load_dotenv
import os
# Загружаем переменные окружения из файла .env
load_dotenv()
# Конфигурация подключения к базе K2 (vizator.ru)
k2_db_config = {
'host': os.getenv('VIZATOR_MYSQL_HOST'),
'user': os.getenv('VIZATOR_MYSQL_USER'),
'password': os.getenv('VIZATOR_MYSQL_PASSWORD'),
'db': os.getenv('VIZATOR_MYSQL_DATABASE'),
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}
# Конфигурация подключения к базе Joomla (jedig.ru)
jedig_db_config = {
'host': os.getenv('JEDIG_MYSQL_HOST'),
'user': os.getenv('JEDIG_MYSQL_USER'),
'password': os.getenv('JEDIG_MYSQL_PASSWORD'),
'db': os.getenv('JEDIG_MYSQL_DATABASE'),
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}
try:
# Устанавливаем подключения к базам данных
k2_conn = pymysql.connect(**k2_db_config)
jedig_conn = pymysql.connect(**jedig_db_config)
with k2_conn.cursor() as k2_cursor, jedig_conn.cursor() as jedig_cursor:
# Шаг 1: Извлекаем данные из K2 с категориями
k2_cursor.execute("""
SELECT i.id AS k2_id, i.alias AS k2_alias, c.alias AS category_alias
FROM viz_k2_items i
LEFT JOIN viz_k2_categories c ON i.catid = c.id
""")
k2_items = k2_cursor.fetchall()
if not k2_items:
print("Не найдено материалов в viz_k2_items. Проверьте базу K2.")
exit(1)
# Шаг 2: Извлекаем данные из Joomla
jedig_cursor.execute("""
SELECT id AS joomla_id, alias
FROM dyutb_content
""")
joomla_items = jedig_cursor.fetchall()
if not joomla_items:
print("Не найдено материалов в dyutb_content. Проверьте базу Joomla.")
exit(1)
# Шаг 3: Создаём словарь соответствий Joomla ID и alias
joomla_alias_map = {item['alias']: item['joomla_id'] for item in joomla_items}
# Шаг 4: Сопоставляем K2 и Joomla по alias
mappings = []
for k2_item in k2_items:
k2_id = k2_item['k2_id']
k2_alias = k2_item['k2_alias']
category_alias = k2_item['category_alias'] or 'uncategorised' # Если категории нет, используем заглушку
joomla_id = joomla_alias_map.get(k2_alias)
if joomla_id:
mappings.append({
'k2_id': k2_id,
'k2_alias': k2_alias,
'category_alias': category_alias,
'joomla_id': joomla_id
})
else:
print(f"Предупреждение: Не найден Joomla ID для K2 материала с alias '{k2_alias}' (K2 ID: {k2_id})")
# Проверяем, есть ли соответствия
if not mappings:
print("Не найдено соответствий между K2 и Joomla. Проверьте алиасы.")
exit(1)
# Шаг 5: Генерируем правила для .htaccess
htaccess_rules = [
"RewriteEngine On",
"RewriteBase /"
]
for mapping in mappings:
k2_id = mapping['k2_id']
joomla_id = mapping['joomla_id']
category_alias = mapping['category_alias']
k2_alias = mapping['k2_alias']
# Правило: <category_alias>/<k2_id>-<k2_alias> → cars/<joomla_id>-<k2_alias>
rule = f"RewriteRule ^{category_alias}/{k2_id}-{k2_alias}$ http://jedig.ru/cars/{joomla_id}-{k2_alias} [R=301,L]"
htaccess_rules.append(rule)
# Добавляем условие для пропуска новых страниц (не начинающихся с известных категорий)
# Пример категорий можно расширить, если их много
known_categories = {m['category_alias'] for m in mappings}
for cat in known_categories:
htaccess_rules.append(f"RewriteCond %{{REQUEST_URI}} !^{cat}/ [NC]")
htaccess_rules.append("RewriteRule ^ - [L]")
# Шаг 6: Записываем правила в файл
with open('redirects.htaccess', 'w') as f:
f.write("\n".join(htaccess_rules))
print(f"Правила переадресации успешно записаны в 'redirects.htaccess'. Всего обработано {len(mappings)} материалов.")
except Exception as e:
print(f"Ошибка: {e}")
finally:
# Закрываем подключения
if 'k2_conn' in locals():
k2_conn.close()
if 'jedig_conn' in locals():
jedig_conn.close()
- Информация о материале
- Автор: JediG
- Категория: Web, Python, Ubuntu, Joomla, Virtuemart
- Просмотров: 23
Как я перенёс материалы K2 на архивный сайт и освободил домен для нового проекта
Недавно я решил вдохнуть новую жизнь в один из своих старых доменов. Но перед этим нужно было освободить его от груза прошлого — сотен материалов, созданных в K2 на сайте vizator.ru. Эти статьи, чертежи и заметки были слишком ценны, чтобы просто удалить их, поэтому я задумал перенести всё на архивный сайт jedig.ru, работающий на Joomla. Звучит просто, правда? Но, как оказалось, это был настоящий квест с кучей головоломок, которые пришлось разгадывать на ходу.
Зачем это всё?
Домен vizator.ru уже давно просился под новый проект — что-то свежее, современное, без старого багажа. Но бросить материалы K2 было жалко: годы работы, уникальные чертежи, полезные заметки. Решение пришло само собой — перенести всё на jedig.ru, мой архивный сайт, где уже крутится Joomla. Задача: сохранить контент, изображения и структуру, чтобы потом можно было спокойно перезапустить vizator.ru с чистого листа. И вот я взялся за дело.
Инструмент для переноса: Python-скрипт
Ручной перенос сотен статей? Нет уж, я не настолько мазохист! Поэтому я написал скрипт на Python, который должен был сделать всю грязную работу: вытащить данные из K2, перенести их в Joomla и обновить ссылки на изображения. Вот как он работает:
- Извлечение данных: Скрипт подключается к базе K2 (таблица
viz_k2_items
) и вытягивает всё: заголовки, тексты (introtext
иfulltext
), алиасы, даты, просмотры. - Копирование изображений: Находит изображения в папке K2 (
/var/www/www-root/data/www/vizator.ru/media/k2/items/cache/
), копирует их в новую папку Joomla (/var/www/www-root/data/www/jedig.ru/images/articals/cars/
). - Обновление ссылок: Использует регулярное выражение
<img[^>]+src=["']?([^"\s>]+)["']?[^>]*>
для поиска путей в тегах<img>
и заменяет старые пути (images/items/
) на новые (images/articals/cars/
). - Запись в Joomla: Добавляет или обновляет записи в таблицах Joomla:
dyutb_content
(статьи),dyutb_assets
(права доступа),dyutb_workflow_associations
(рабочий процесс).
Звучит как идеальный план, да? Но, как говорится, теория и практика — это два разных мира.
Проблемы, с которыми я столкнулся
Когда я запустил скрипт, он начал бодро работать… а потом всё пошло наперекосяк. Вот что пришлось чинить:
1. Ссылки обновились не везде
Я заметил, что часть ссылок на изображения осталась старыми — images/items/
вместо images/articals/cars/
. Сначала подумал: "Ну ладно, регулярка слабая, пропустила что-то". Проверил логи — нет, для обработанных статей всё обновилось. Оказалось, скрипт просто падал на середине из-за другой ошибки, и до некоторых материалов дело не доходило. Пришлось копаться глубже.
2. Ошибка с длиной заголовка
В логах вылезло: (1406, "Data too long for column 'title' at row 1")
. Я чуть не заорал: "Да что ж такое!". Оказалось, что в таблице dyutb_assets
колонка title
ограничена 100 символами, а я пытался засунуть туда полные заголовки вроде "Чертеж конусного ограничителя задних пружин для Land Rover Defender 90, Discovery 1 и стареньких RRC (TF510)" (100 символов ровно, но были и длиннее). В dyutb_content
лимит 255, и там всё нормально, а вот assets
подставил подножку.
Решение: для dyutb_assets
стал писать просто "com_content"
, как у старых материалов. Оказалось, что это поле особо и не нужно для фронтенда — главное заголовок в content
. Проблема ушла, и я выдохнул.
3. Пересохранение в админке не работало
Когда я зашёл в админку Joomla проверить результат, попытался пересохранить статью — и получил красный баннер: "danger Не удалось сохранить элемент". Тут я уже начал подозревать, что вселенная против меня. Копнул в dyutb_assets
и увидел, что parent_id
для новых статей был равен 8. Откуда эта восьмёрка? Скрипт брал её из записи с name = 'com_content'
, думая, что это корень для всех статей.
На самом деле parent_id
должен был быть 192 — ID записи в dyutb_assets
, привязанной к категории 14 (моей целевой категории). Без этого Joomla теряла связь между статьёй и категорией, и пересохранение ломалось. Исправил на фиксированное parent_id = 192
, и админка ожила. Ура!
Код, который всё спас
Вот кусок кода, который в итоге всё вытянул. Он обрабатывает материал, копирует изображения и записывает данные в Joomla:
# Установка правильного parent_id для категории
parent_id = 192
# Извлечение материала из K2
k2_cursor.execute("SELECT `id`, `title`, `alias`, `introtext`, `fulltext`, `created`, `hits` FROM `viz_k2_items`")
k2_items = k2_cursor.fetchall()
for item in k2_items:
# Обрезка заголовка для dyutb_content
content_title = item['title'][:255] if len(item['title']) > 255 else item['title']
# Обновление ссылок в introtext
updated_introtext = item['introtext'] or ""
intro_images = re.findall(r'<img[^>]+src=["']?([^"\s>]+)["']?[^>]*>', updated_introtext)
for image in intro_images:
new_image_name = copy_image(image, new_image_folder)
if new_image_name:
updated_introtext = updated_introtext.replace(image, f"images/articals/cars/{new_image_name}")
# Запись в dyutb_content
jedig_cursor.execute("UPDATE `dyutb_content` SET `title` = %s, `introtext` = %s WHERE `alias` = %s",
(content_title, updated_introtext, item['alias']))
# Запись в dyutb_assets с правильным parent_id
asset_name = f"com_content.article.{content_id}"
jedig_cursor.execute("INSERT INTO `dyutb_assets` (`parent_id`, `name`, `title`) VALUES (%s, %s, %s)",
(parent_id, asset_name, "com_content"))
Полный код длиннее, но суть та же: аккуратно перенести данные и не сломать сайт.
Итог
После всех мучений я получил то, что хотел: все материалы из K2 теперь живут на jedig.ru, ссылки на изображения обновлены, админка работает как надо. Домен vizator.ru свободен для нового проекта, и я могу спать спокойно, зная, что старый контент не пропал. Да, пришлось попотеть с заголовками, ссылками и parent_id
, но результат того стоил. Теперь я чувствую себя немного героем, который победил технического дракона!
Если у вас похожая задача — берите мой опыт и не повторяйте моих ошибок. Удачи в ваших миграциях!
Полный код предоставляется в качестве подарка. Этот код также позволяет перенести материалы K2 в рамках одного сайта. Теоретически возможно перенести и категории, но у меня не было такой задачи.
import pymysql # Библиотека для работы с MySQL
import os # Для работы с файловой системой и переменными окружения
import re # Для работы с регулярными выражениями (поиск и замена ссылок)
import shutil # Для копирования файлов (изображений)
from dotenv import load_dotenv # Для загрузки переменных из файла .env
import logging # Для логирования процесса (отладка и контроль)
import hashlib # Для генерации уникальных имен файлов (хэш MD5)
from datetime import datetime # Для работы с датами (например, modified)
import json # Для работы с JSON-форматированными данными (urls, attribs, metadata)
# Загружаем переменные окружения из файла .env (например, данные для подключения к БД)
load_dotenv()
# Настраиваем логирование: выводим сообщения с временем, уровнем и текстом
logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s:%(message)s')
# Конфигурация подключения к базе K2 (источник данных)
k2_db_config = {
'host': os.getenv('VIZATOR_MYSQL_HOST'), # Хост базы данных K2
'user': os.getenv('VIZATOR_MYSQL_USER'), # Пользователь
'password': os.getenv('VIZATOR_MYSQL_PASSWORD'), # Пароль
'db': os.getenv('VIZATOR_MYSQL_DATABASE'), # Имя базы
'charset': 'utf8mb4', # Кодировка для поддержки Unicode
'cursorclass': pymysql.cursors.DictCursor # Курсор возвращает словари
}
# Конфигурация подключения к базе Joomla (целевая база)
jedig_db_config = {
'host': os.getenv('JEDIG_MYSQL_HOST'),
'user': os.getenv('JEDIG_MYSQL_USER'),
'password': os.getenv('JEDIG_MYSQL_PASSWORD'),
'db': os.getenv('JEDIG_MYSQL_DATABASE'),
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}
# Префиксы таблиц для K2 и Joomla
k2_table_prefix = 'viz_k2' # Префикс таблиц K2
jedig_table_prefix = 'dyutb_' # Префикс таблиц Joomla
# Пути к изображениям
k2_image_base_path = '/var/www/www-root/data/www/vizator.ru/' # Базовый путь к файлам K2
new_image_folder = '/var/www/www-root/data/www/jedig.ru/images/articals/cars/' # Новый путь для копирования изображений
os.makedirs(new_image_folder, exist_ok=True) # Создаём папку, если её нет
new_image_relative_path = 'images/articals/cars/' # Относительный путь для ссылок в Joomla
# Регулярное выражение для поиска путей в тегах <img>
image_pattern = re.compile(r'<img[^>]+src=["\']?([^"\s>]+)["\']?[^>]*>')
# Захватывает src с кавычками или без, до пробела или конца тега
# Значения по умолчанию для полей Joomla
default_urls = json.dumps({"urla": False, "urlatext": "", "targeta": "", "urlb": False, "urlbtext": "", "targetb": "", "urlc": False, "urlctext": "", "targetc": ""})
# JSON для дополнительных ссылок в статье
default_attribs = json.dumps({"article_layout": "", "show_title": "", "link_titles": "", "show_tags": "", "show_intro": "", "info_block_position": "", "info_block_show_title": "", "show_category": "", "link_category": "", "show_parent_category": "", "link_parent_category": "", "show_associations": "", "show_author": "", "link_author": "", "show_create_date": "", "show_modify_date": "", "show_publish_date": "", "show_item_navigation": "", "show_icons": "", "show_print_icon": "", "show_email_icon": "", "show_vote": "", "show_hits": "", "show_noauth": "", "urls_position": "", "alternative_readmore": "", "article_page_title": "", "show_publishing_options": "", "show_article_options": "", "show_urls_images_backend": "", "show_urls_images_frontend": ""})
# JSON для настроек отображения статьи
default_metadata = json.dumps({"robots": "", "author": "", "rights": "", "xreference": ""})
# JSON для мета-данных
default_rules = json.dumps({
"core.view": {"1": 1}, # Права просмотра для всех
"core.create": {"8": 1}, "core.delete": {"8": 1}, "core.edit": {"8": 1}, "core.edit.state": {"8": 1}
# Права для группы с ID 8 (например, администраторы)
})
# JSON для правил доступа
def generate_image_filename(item_id):
"""Генерирует уникальное имя файла изображения на основе ID материала."""
return hashlib.md5(f"Image{item_id}".encode()).hexdigest() + "_L.jpg"
# Хэш MD5 от строки "Image<Id>" плюс суффикс "_L.jpg"
def extract_images(text):
"""Извлекает все пути к изображениям из текста с помощью регулярного выражения."""
return image_pattern.findall(text) # Возвращает список путей из атрибутов src
def update_image_links(text, old_path, new_path):
"""Заменяет старый путь изображения на новый в тексте."""
if not old_path or old_path == new_path:
logging.warning(f"Старый путь '{old_path}' пуст или совпадает с новым '{new_path}'. Ссылки не обновлены.")
return text
pattern = re.escape(old_path) # Экранируем путь для безопасной замены
updated_text = re.sub(rf'(<img[^>]+src=["\']?){pattern}(["\']?[^>]*>)', rf'\1{new_path}\2', text)
# Заменяем только src, сохраняя остальной тег
if updated_text != text:
logging.info(f"Обновлена ссылка: '{old_path}' → '{new_path}'")
else:
logging.warning(f"Ссылка '{old_path}' не найдена в тексте для замены на '{new_path}'")
return updated_text
def copy_image(image_path, destination_folder):
"""Копирует изображение из K2 в новую папку Joomla."""
absolute_image_path = os.path.join(k2_image_base_path, image_path) # Полный путь к исходному файлу
if not os.path.exists(absolute_image_path):
logging.error(f"Изображение {absolute_image_path} не найдено.")
return None
filename = os.path.basename(image_path) # Имя файла из пути
destination_path = os.path.join(destination_folder, filename) # Полный путь назначения
if os.path.exists(destination_path):
logging.info(f"Изображение {filename} уже существует в {destination_folder}.")
return filename
try:
shutil.copy(absolute_image_path, destination_path) # Копируем файл
logging.info(f"Изображение {filename} скопировано в {destination_folder}.")
return filename
except IOError as e:
logging.error(f"Ошибка при копировании изображения {image_path}: {e}")
return None
# Инициализируем переменные для подключений к БД
k2_connection = None
jedig_connection = None
try:
# Устанавливаем подключения к базам данных
k2_connection = pymysql.connect(**k2_db_config)
jedig_connection = pymysql.connect(**jedig_db_config)
# Открываем курсоры для выполнения запросов
with k2_connection.cursor() as k2_cursor, jedig_connection.cursor() as jedig_cursor:
# Устанавливаем parent_id = 192 для привязки статей к категории с ID 14
parent_id = 192 # Это ID записи в dyutb_assets для категории (com_content.category.14)
# Извлекаем все материалы из K2
k2_cursor.execute(f"""
SELECT `id`, `title`, `alias`, `published`, `created`, `introtext`, `fulltext`, `hits`
FROM `{k2_table_prefix}_items`
""")
k2_items = k2_cursor.fetchall() # Получаем все записи в виде списка словарей
# Обрабатываем каждый материал
for item in k2_items:
logging.info(f"Обработка материала с ID: {item['id']}, Заголовок: {item['title']}")
# Обрезаем заголовок для dyutb_content (максимум 255 символов)
original_title = item['title'] or "" # Если title None, заменяем на пустую строку
content_title = original_title[:255] if len(original_title) > 255 else original_title
if len(original_title) > 255:
logging.warning(f"Заголовок материала ID {item['id']} обрезан для content с {len(original_title)} до 255 символов: {content_title}")
# Для dyutb_assets используем фиксированное значение "com_content" (как у старых материалов)
assets_title = "com_content"
# Обрабатываем основное изображение материала
image_filename = generate_image_filename(item['id']) # Генерируем имя файла
absolute_image_path = os.path.join(k2_image_base_path, 'media/k2/items/cache/', image_filename)
if os.path.exists(absolute_image_path):
new_image_name = copy_image(f"media/k2/items/cache/{image_filename}", new_image_folder)
if new_image_name:
item['image'] = f"{new_image_relative_path}{new_image_name}"
logging.info(f"Обновленное основное изображение: {item['image']}")
else:
logging.warning(f"Основное изображение для материала {item['id']} не найдено.")
# Обрабатываем introtext (вступительный текст)
updated_introtext = item['introtext'] or "" # Если None, заменяем на пустую строку
if updated_introtext:
logging.info("Обработка introtext:")
intro_images = extract_images(updated_introtext) # Извлекаем пути к изображениям
for image in intro_images:
logging.info(f"Найдено изображение: {image}")
new_image_name = copy_image(image, new_image_folder) # Копируем изображение
if new_image_name:
updated_introtext = update_image_links(updated_introtext, image, f"{new_image_relative_path}{new_image_name}")
logging.info("Обновленный introtext:")
logging.info(updated_introtext)
# Проверяем, остались ли старые ссылки
if 'images/items/' in updated_introtext:
logging.warning(f"В introtext материала ID {item['id']} остались старые ссылки: {updated_introtext}")
# Обрабатываем fulltext (полный текст)
updated_fulltext = item['fulltext'] or "" # Если None, заменяем на пустую строку
if updated_fulltext:
logging.info("Обработка fulltext:")
fulltext_images = extract_images(updated_fulltext)
for image in fulltext_images:
logging.info(f"Найдено изображение: {image}")
new_image_name = copy_image(image, new_image_folder)
if new_image_name:
updated_fulltext = update_image_links(updated_fulltext, image, f"{new_image_relative_path}{new_image_name}")
logging.info("Обновленный fulltext:")
logging.info(updated_fulltext)
# Проверяем, остались ли старые ссылки
if 'images/items/' in updated_fulltext:
logging.warning(f"В fulltext материала ID {item['id']} остались старые ссылки: {updated_fulltext}")
# Проверяем, существует ли запись в dyutb_content по alias
jedig_cursor.execute(f"SELECT `id`, `asset_id` FROM `{jedig_table_prefix}content` WHERE `alias` = %s", (item['alias'],))
content_row = jedig_cursor.fetchone()
# Общие параметры для вставки или обновления в dyutb_content
common_params = (
content_title, # Заголовок (обрезанный до 255)
updated_introtext, # Обновлённый вступительный текст
updated_fulltext, # Обновлённый полный текст
1, # state = 1 (опубликовано)
14, # catid = 14 (ID категории в Joomla)
item['created'], # Дата создания из K2
datetime.now(), # Дата модификации (текущая)
item.get('image', ''), # Путь к основному изображению (или пусто)
item.get('hits', 0), # Количество просмотров (или 0)
default_urls, # Дополнительные ссылки
default_attribs, # Настройки отображения
default_metadata, # Мета-данные
"", # metadesc (пусто)
"*", # language (все языки)
item['created'], # Дата публикации
1, # access = 1 (общедоступно)
489 # created_by = 489 (ID пользователя-автора)
)
# Если запись существует, обновляем её
if content_row:
content_id = content_row['id']
asset_id = content_row['asset_id'] or 0
jedig_cursor.execute(f"""
UPDATE `{jedig_table_prefix}content`
SET `title` = %s, `introtext` = %s, `fulltext` = %s, `state` = %s, `catid` = %s,
`created` = %s, `modified` = %s, `images` = %s, `hits` = %s, `urls` = %s,
`attribs` = %s, `metadata` = %s, `metadesc` = %s, `language` = %s,
`publish_up` = %s, `access` = %s, `created_by` = %s, `version` = %s
WHERE `id` = %s
""", common_params + (1, content_id)) # version = 1, затем ID записи
logging.info(f"Обновлена запись в таблице content. ID: {content_id}")
# Если записи нет, вставляем новую
else:
jedig_cursor.execute(f"""
INSERT INTO `{jedig_table_prefix}content` (
`title`, `alias`, `introtext`, `fulltext`, `state`, `catid`, `created`, `modified`,
`images`, `hits`, `urls`, `attribs`, `metadata`, `metadesc`, `language`, `asset_id`,
`publish_up`, `access`, `created_by`, `version`
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (item['alias'],) + common_params + (1,)) # alias + параметры + version = 1
content_id = jedig_cursor.lastrowid # Получаем ID новой записи
asset_id = 0
logging.info(f"Добавлена запись в таблице content. ID: {content_id}")
# Добавляем связь с рабочим процессом (workflow)
jedig_cursor.execute(f"""
INSERT IGNORE INTO `{jedig_table_prefix}workflow_associations` (`item_id`, `stage_id`, `extension`)
VALUES (%s, %s, %s)
""", (content_id, 1, 'com_content.article'))
# IGNORE предотвращает дубликаты
logging.info(f"Добавлена/обновлена запись в таблице workflow_associations для Content ID: {content_id}")
# Обрабатываем запись в dyutb_assets
asset_name = f"com_content.article.{content_id}" # Уникальное имя ассета для статьи
jedig_cursor.execute(f"SELECT `id` FROM `{jedig_table_prefix}assets` WHERE `name` = %s", (asset_name,))
asset_row = jedig_cursor.fetchone()
# Если запись в assets существует, обновляем её
if asset_row:
asset_id = asset_row['id']
jedig_cursor.execute(f"""
UPDATE `{jedig_table_prefix}assets`
SET `parent_id` = %s, `lft` = %s, `rgt` = %s, `level` = %s, `name` = %s, `title` = %s, `rules` = %s
WHERE `id` = %s
""", (parent_id, 0, 0, 1, asset_name, assets_title, default_rules, asset_id))
# parent_id = 192 (категория), lft/rgt/level для вложенности (упрощённо 0/0/1)
logging.info(f"Обновлена запись в таблице assets. ID: {asset_id}")
# Если записи нет, вставляем новую
else:
jedig_cursor.execute(f"""
INSERT INTO `{jedig_table_prefix}assets` (`parent_id`, `lft`, `rgt`, `level`, `name`, `title`, `rules`)
VALUES (%s, %s, %s, %s, %s, %s, %s)
""", (parent_id, 0, 0, 1, asset_name, assets_title, default_rules))
asset_id = jedig_cursor.lastrowid
logging.info(f"Добавлена запись в таблице assets. ID: {asset_id}")
# Обновляем asset_id в dyutb_content, если он отличается
if content_row is None or content_row['asset_id'] != asset_id:
jedig_cursor.execute(f"""
UPDATE `{jedig_table_prefix}content`
SET `asset_id` = %s
WHERE `id` = %s
""", (asset_id, content_id))
logging.info(f"Обновлён asset_id в таблице content. Content ID: {content_id}, Asset ID: {asset_id}")
# Фиксируем изменения в базе Joomla
jedig_connection.commit()
logging.info(f"Материал '{content_title}' успешно обработан в JEDIG.")
logging.info("=" * 80)
except Exception as e:
# Логируем любую ошибку, которая может возникнуть
logging.error(f"Ошибка при выполнении скрипта: {e}")
finally:
# Закрываем подключения к базам, если они были открыты
if k2_connection:
k2_connection.close()
if jedig_connection:
jedig_connection.close()