K2 - в целом неплохой компонент (был). Некоторое время он давал гораздо больше возможностей для отображения контента, чем стандартный компонент материалов Joomla. Однако, время не стоит на месте, и сейчас стандартный компонент не уступает в возможностях компоненту K2. Разработчики Joomla потрудились на славу, чего не скажешь о разработчиках компонента K2. Мало того, что долгое время не обновлялся функционал компонента, так они не подготовили обновление для перехода на 4 версию Joomla. На момент написания этой статьи прошло почти два года с выпуска Joomla 4, а обновления компонента K2 для совместимости с новой версией так и нет. Возможно, на тот момент, когда вы будете читать эти строки разработчики K2 что-то выкатят, но сейчас нет.
Для перехода на новую версию Joomla необходимо перенести все материалы из K2 в стандартный компонент. Да и вообще, зачем нагружать систему дополнительным компонентом, когда стандартный имеет тот же функционал?! Я перерыл много страниц страниц интернета в поиске оптимального решения этой задачи, и на удивление ничего не нашел! Совсем чуток информации накопал в буржунете. Но недостаточно для моего случая.
Если у вас всего несколько материалов, вы можете сделать это вручную. Однако у меня на сайте примерно 10 000 материалов, и все они проиндексированы. Мне необходимо сохранить не только структуру каталогов и материалов, но также и все алиасы.
В общем, решил делать всё вручную через базу данных. И первое, что нужно понимать - структура таблиц в базе данных у компонента K2 и стандартного компонента материалов Joomla.
Структура таблиц в базе данных
Мы рассмотрим только самые важные элементы таблиц.
Структура таблиц компонента K2 в базе данных Joomla
Таблица #__k2_items (где `#__` - префикс таблиц вашей Joomla установки):
id (PRIMARY KEY): Уникальный идентификатор элемента K2.
title: Заголовок элемента
alias: Алиас элемента, используется в URL.
catid: Идентификатор категории, к которой принадлежит элемент.
published: Флаг, указывающий, опубликован ли элемент.
introtext: Вводный текст элемента.
fulltext: Полный текст элемента.
created: Дата создания элемента.
created_by: Идентификатор пользователя, создавшего элемент.
modified: Дата последнего изменения элемента.
modified_by: Идентификатор пользователя, внесшего последние изменения.
и другие поля, специфичные для элементов K2.
Таблица #__k2_categories:
id (PRIMARY KEY): Уникальный идентификатор категории K2.
name: Название категории.
alias: Алиас категории, используется в URL.
published: Флаг, указывающий, опубликована ли категория.
description: Описание категории.
и другие поля, специфичные для категорий K2.
Таблица #__k2_users:
id (PRIMARY KEY): Уникальный идентификатор пользователя K2.
userid: Идентификатор пользователя Joomla, связанного с пользователем K2.
alias: Алиас пользователя, используется в URL.
description: Описание пользователя.
и другие поля, специфичные для пользователей K2.
Структура таблиц стандартного компонента Joomla
Таблица #__categories (где `#__` - префикс таблиц вашей Joomla установки):
id (PRIMARY KEY): Уникальный идентификатор категории.
title: Заголовок категории.
alias: Алиас (URL-адрес) категории.
parent_id: Идентификатор родительской категории.
description: Описание категории.
published: Статус публикации категории.
access: Уровень доступа категории.
params: Дополнительные параметры категории.
Таблица #__content:
id: Уникальный идентификатор материала.
title: Заголовок материала.
alias: Алиас (URL-адрес) материала.
catid: Идентификатор категории, к которой относится материал.
introtext: Краткое описание материала.
fulltext: Полное содержимое материала.
created: Дата создания материала.
created_by: Идентификатор пользователя, создавшего материал.
modified: Дата последнего изменения материала.
modified_by: Идентификатор пользователя, внесшего последние изменения в материал.
publish_up: Дата начала публикации материала.
publish_down: Дата окончания публикации материала.
access: Уровень доступа к материалу.
state: Состояние публикации материала.
featured: Отметка "Рекомендуемый материал".
language: Язык материала.
images: Дополнительные изображения, связанные с материалом.
urls: Пользовательские URL-адреса материала.
attribs: Дополнительные атрибуты материала.
Как видите, многие ячейки в таблицах K2 и стандартного имеют схожее назначение, что значительно облегчает нашу задачу. Но есть очень важный нюанс, на который я напоролся при первой попытке. А именно, таблица #_assets. Эта таблица хранит в себе все связи в вашей БД: уникальные идентификаторы, последовательность, все созданные категории, материалы, модули и т.п. и все связи между ними. Удаление или пропуск или ошибка в одной единственной строчке этой таблицы, может поломать вам все связи на сайте. Т.е., если вы хотели просто одним запросом перенести все категории, а вторым перенести все материалы, то так работать не будет!
Таблица #_assets
Исходя из этого у нас есть три варианта, как перенести контент из K2 в Joomla:
Полуручной/полуавтоматический перенос. Не знаю, как правильно назвать, но именно его я и опишу далее. Он подойдет, если у вас немного категорий в компоненте K2, поскольку в этом варианте мы будем переносить (а точнее создавать) категории вручную.
Этот вариант подойдет для любителей баз данных, SQL-запросов и логических задачек. Если вас не пугает перспектива сидеть часами в базе данных, выясняя какой ключ подобрать и куда вставить для перенесенных таблиц и как правильно составить сложный SQL-запрос, чтобы не запороть всю БД, этот вариант для вас. Его опишу, если только будет достаточный отклик на эту статью.
Я бы назвал этот вариант выбором профессионалов. Написать PHP-скрипт (можно оформить даже в виде плагина), который будет использовать встроенные джумловские методы, типа, JTable, JDatabase, JAccessRules, и т.п. которые в свою очередь позволят правильно взаимодействовать с БД Joomla и автоматически пропишут все нужные строки в таблицах, типа #_assets, а потом ещё и выложить этот скрипт куда-нибудь на github, чтобы много других пользователей решили эту проблему переноса - это профессионально. Но я этим заниматься не буду :-) Этот вариант у меня займет кучи времени, а первым способом я в своем единственном сайте на Joomla перенесу контент в течении дня.
Поехали
1 шаг. Перенос-создание категорий
Думаю, будет не лишним напомнить, что перед любыми изменениями мы делаем дамб базы данных или работаем на тестовом сайте.
Мы бы могли выполнить перенос категорий с помощью SQL-запросов, но в таком случае нам бы также пришлось создавать SQL-запросы на изменение таблицы #_assets. А сначала обнаружить все нужные узлы и связи и решить сложную логическую задачу, как правильно внести изменения, чтобы потом всё работало. В общем задача довольно сложная. А мне, с моими 10 категориями, проще их заново создать. В общем, приступим.
Заходим в phpMyAdmin. Открываем таблицу #_k2_categories. Можете ее сфотографировать или экспортировать. В дальнейшем вам понадобятся данные из этой таблицы.
Далее залетаем в: Панель управления Joomla --> Материалы --> Категории --> Создать категорию
Мы создадим копии всех категорий из компонента K2 в стандартном компоненте материалов Joomla, сохраняя уровень вложенности по отношению друг к другу. Единственное, что нужно изменить - это alias. Так как система не даст создать новый элемент с существующим alias. Чтобы потом было легче вернуть родной alias, я просто добавил к концу оригинального alias -2. Т.е. в оригинале был alias, например, world, а стал world-2. Таким образом создаем копии всех категорий из K2 в джумле.
На этом первый шаг закончен, переходим к следующему.
Шаг 2. Перенос материалов
Внимание! Если вы хотите сохранить авторство материалов, то сначала прочитай часть ЗАВЕРШЕНИЕ.
Материалы мы будем переносить массово с помощью SQL-запросов. Мы постараемся сделать этот перенос правильно, с внесением изменений в таблицу #_assets.
Переносим все материалы одной категории:
INSERT INTO #_content (title, alias, introtext, fulltext, state, created, publish_up, publish_down, access, featured, metakey, metadesc, language)
SELECT title, alias, introtext, fulltext, published, created, publish_up, publish_down, access, featured, metakey, metadesc, language
FROM #_k2_items
WHERE catid = 26;
Обратите внимание! # - меняем на префикс в вашей БД (и так будет везде далее по статье). 26 - это id категории в К2, из которой вы достаете материалы.
До этого мы создали копии категорий из К2 в нашем стандартном компоненте материалов. Теперь нужно найти соответствующие строки в БД, чтобы узнать какой теперь id у нашей категории:
Идентификаторы категорий в К2 и в компоненте Joomla
Теперь меняем id категории материала. В предыдущем запросе мы не переносили данные столбца catid, чтобы не получить конфликта с одинаковыми идентификаторами. Поэтому сейчас id=0. А запрос, значит, будет выглядеть так:
UPDATE #_content
SET catid = 58
WHERE catid = 0;
Кстати, эти два запроса можно объединить в один и сразу переносить материалы с обновлением категории. Я сделал двумя запросами, чтобы вам была понятна логика действий.
Эту процедуру необходимо повторить для каждой созданной категории.
После этого вы сможете увидеть все материалы внутри созданных категорий в панели управления. Также вы сможете их открыть. Но вы не сможете сохранить материал, не сможете создать новый материал в этой категории или какой-нибудь другой. Всё дело в том, что мы нарушили структуру общего дерева. И чтобы ее восстановить необходимо внести непростые изменения в таблицу #_assets и #_content.
Шаг 3. Вносим изменения в таблицу #_assets
Открываем таблицу #_assets и переходим в самый конец. Если кроме категорий вы ничего не создавали и не удаляли, тогда последние записи в этой таблице должны быть ваши категории:
Добавленные категории в таблице #_assets
Здесь нужно быть предельно внимательным, чтобы всё сделать правильно. Разберем столбцы, что каждый из них означает:
id - (PRIMARY KEY). Каждый созданный элемент на вашем сайте будет заноситься в эту таблицу под уникальным id. Если вы откроете таблицу #_content, то увидите, что у ваших материалов есть id, а есть asset_id. Так вот asset_id в таблице #_content = id в таблице #_assets. И никак иначе.
parent_id - не сложно догадаться по названию, указывает родителя для элемента, который вы создали. Например, все категории, у которых при создании было указано "нет родителя", имеют parent_id = 8. Потому что их родителем считается сам компонент com.content, который в свою очередь находится в таблице #_assets на 8 строчке (у вас должно быть также, но лучше проверьте). А вот категория "Акции" имеет parent_id = 6541, поскольку является дочерней категорией категории "Прочее" с id=6541.
lft и rgt - не знаю, так ли необходимо было усложнять структуру этим столбцами, но что есть, то есть. Они указывают на предыдущий и на следующий элемент соответственно в том списке, где вы создали свой новый элемент. При чем они создаются для каждого списка отдельно. Т.е. отдельно идет нумерация для материалов, категорий, модулей и т.д. В целом, до конца я не понял логику и смысл этих столбцов, если кто знает, напишите в комменты.
level - отображает уровень вложенности в общее дерево созданного вами элемента. Здесь несложно. 0 - только у одного элемента - у ядра системы. 1 - у расширений (компонентов, плагинов, модулей). 2 - у главных элементов, созданных компонентами. Например, все категории "без родителя" имеют level=2. Все материалы и подкатегории этих категорий уже имеют level=3 (на скриншоте выше, вы можете увидеть, что почти все категории у меня с level=2, так как "без родителя", а "Акции" с level=3, так как дочерняя для "Прочее"). Если же вы создаете материал в подкатегории, то уже будет level=4 и т.д.
name - имя созданного элемента. Обратите внимание на комментарий под названием столбца. Каждое имя должно быть уникально, наподобие как id (PRIMARY KEY). Это очень важно, особенно при составлении sql-запроса.
title - думаю нет особого смысла объяснять. Для материалов он соответствует title в таблице #_content.
rules - хранит права доступа, зашифрованные в JSON
Со столбцами разобрались. Теперь перейдем к sql-запросам. Можно было бы начать со столбца id. Но он формируется автоматически, поэтому я его пропустил. Устанавливать parent_id для элементов, которые я даже не вижу, я не хотел, а страшные lft и rgt я вообще решил оставить напоследок. В общем, мне показалось, самым сложным будет задать каждому новому элементу новое имя, и я решил начать со столбца name.
В первую очередь нужно найти в таблице строчку с последним созданным материалом:
Уникальное имя последнего созданного материала
Хорошо, я знаю уникальное имя последнего созданного материала. Понимаю, что у следующего должно быть com_content.article.783, а у следующего com_content.article.784 и т.д. Также у всех перенесенных материалов (пока ещё!) asset_id = 0. Ну, отсюда собственно и первый запрос:
SET @row_number = 782;
INSERT INTO #_assets (name)
SELECT CONCAT('com_content.article.', @row_number := @row_number + 1) AS name
FROM #_content AS c
WHERE c.asset_id = 0;
Я думаю, вам не нужно объяснять, что у вас будет не 782, а другое число в запросе. На всякий случай - я взял число 782 из уникального имени последнего материала com_content.article.782 (на картинке).
Теперь у нас есть определенное количество новых строчек в таблице #_assets, которое соответствует количеству материалов в компоненте K2. Также у нас заполнен столбец name и должен был автоматически заполнится столбец id. Если столбец id не заполнился автоматически, выполните простой запрос:
UPDATE #_assets
SET id = 6550 + (id - 0)
WHERE id = 0;
Обратите внимание, что 6550 - это следующий id после последнего не равного 0.
Далее можно по-быстрому заполнить все ячейки столбца rules. Я установил там значение {}. Вы можете установить другие права. Вот запрос:
UPDATE #_assets
SET rules = '{}'
WHERE rules IS NULL OR rules = '';
Обновляем столбцы parent_id и title
Я сделал обновление путем создания новой временной таблицы. Возможно есть способ проще, я не придумал. И так, создаем новую таблицу в phpMyAdmin, имя таблицы придумываете сами, я сделал wwwnew. В таблице создаем три столбца: id, cat и title. Столбец id должен хранить значения INT со свойством INDEX. Столбец cat должен хранить просто значения INT. Столбец title должен хранить значение text.
В созданную таблицу вставляем все ячейки из столбцов catid и title таблицы #_content:
INSERT INTO wwwnew(`cat`, `title`) SELECT catid, title FROM #_content
Если перед тем, как выполнить перенос материалов из таблицы #_k2_items, в таблице #_content были материалы (скорре всего хотя бы один был), то их теперь нужно удалить из нашей временной таблицы wwwnew:
DELETE FROM wwwnew
WHERE true
LIMIT 25;
У меня было 25 лишних материалов, поэтому и число 25!
Теперь нужно изменить значение ячеек cat, чтобы они соответствовали не id категории, а ее asset_id. Нужные вам числа вы можете найти, например, в таблице #_categories:
ID и asset_id как раз находятся в соседних столбцах в таблице #_categories
Теперь мы знаем нужные нам цифры и можем установить их в столбце cat:
UPDATE wwwnew
SET cat = CASE
WHEN cat = 58 THEN 6531
WHEN cat = 59 THEN 6532
WHEN cat = 60 THEN 6533
WHEN cat = 61 THEN 6534
WHEN cat = 62 THEN 6535
WHEN cat = 63 THEN 6536
WHEN cat = 64 THEN 6537
WHEN cat = 65 THEN 6538
WHEN cat = 66 THEN 6539
WHEN cat = 67 THEN 6540
WHEN cat = 68 THEN 6541
WHEN cat = 69 THEN 6542
WHEN cat = 70 THEN 6543
WHEN cat = 71 THEN 6544
WHEN cat = 72 THEN 6545
ELSE cat
END;
Теперь можно перенести данные в таблицу #_assets из нашей временной таблицы:
UPDATE #_assets AS a
JOIN (
SELECT cat, @row_num := @row_num + 1 AS row_num
FROM wwwnew, (SELECT @row_num := 0) AS r
ORDER BY cat
) AS subquery ON subquery.row_num = (a.id - 6549)
SET a.parent_id = subquery.cat
WHERE a.id >= 6550;
и
UPDATE #_assets AS a
JOIN (
SELECT title, @row_num := @row_num + 1 AS row_num
FROM wwwnew, (SELECT @row_num := 0) AS r
ORDER BY title
) AS subquery ON subquery.row_num = (a.id - 6549)
SET a.title = subquery.title
WHERE a.id >= 6550;
Не забываем, что у меня 6550 - это строчка с которой мы начали вставлять новые записи в таблицу #_assets, у вас она наверняка будет другая. Ну и 6549 - это последняя строчка в таблице #_assets перед всеми нашими действиями. Будьте внимательны, это очень важный нюанс!
Обновляем столбцы lft и rgt
Мы близки к концу. Суть столбцов я уже описывал выше. Поэтому просто находите последний созданный материал, который вы создавали через панель управления Joomla в стандартном компоненте. У меня это, как я писал выше (и скриншот выше), материал с name = com_content.article.782. И значения ячеек lft=97, rgt=98. Следующий материал должен содержать соответственно lft=99, rgt=100. Несложно заметить, что значения в ячейках растут с шагом +2. Отсюда и два наших запроса:
UPDATE #_assets
SET lft = 97 + ((id - 6550) * 2)
WHERE id > 6549;
и
UPDATE #_assets
SET rgt = 98 + ((id - 6550) * 2)
WHERE id > 6549;
100500-й раз напоминаю, что 6549 - это последняя строчка в таблице #_assets перед всеми нашими действиями. 97 - значение lft, 98 - значение rgt последнего созданного материала в панели управления joomla. Не забывайте менять на свои значения.
И собственно на этом всё! Теперь ваши материалы должны открываться и сохраняться и записываться в общее дерево. И все другие действия с материалами, категориями модулями и т.п. теперь не будут нарушать целостности общего дерева, как будто вы внесли все эти материалы через панель управления Joomla.
Завершение
Основная задача на этом выполнена. Далее вы можете подрихтовать ваши новые строки, например, добавить автора материалов. Вы можете выбрать одного автора присвоить все материалы ему таким запросом:
UPDATE #_content
SET created_by = 1
WHERE created_by = '0';
Где 1 - это id выбранного вами автора
Если у вас несколько авторов и важно авторство материалов, запрос будет несколько сложнее, так как идентификатор пользователя в таблицах К2 не соответствует идентификатору пользователя в Joomla. В этом случае вам необходимо сначала перенести все значения из created_by таблицы #_k2_items, это лучше сделать при самом первом запросе, когда вы переносите материалы, чтобы не сбился порядок. Для этого просто добавьте created_by в наш первый запрос в INSERT INTO и SELECT. А после переноса материалов замените каждого автора подобным запросом:
UPDATE `#_content`
SET created_by =10
WHERE created_by =18;
Где 10 - это id автора в #_users, а 18 - это идентификатор автора в К2. Выполните это действие для всех ваших авторов. Возможно вы захотите обновить ещё какие-то данные в таблицах, это уже на ваше усмотрение. Главное, не забывайте делать резервную копию базы данных.
Собственно на этом всё. Желаю всем удачного переноса! И не забывайте предварительно делать резервную копию!
P.S. После переноса материалов из K2 в Joomla, я обновил CMS до 4 версии и там столкнулся с кучей новых ошибок, но это уже материал для другой статьи))
Оригинал статьи в моем дзене.