FIX: Import sub-sub-categories (#8810)

This should make the importer more resilient to incomplete or damaged
backups. It will disable some validations and attempt to automatically
repair category permissions before importing.
This commit is contained in:
Dan Ungureanu
2020-01-30 18:46:33 +02:00
committed by GitHub
parent e470b27b41
commit 62b9a432bd
2 changed files with 107 additions and 14 deletions

View File

@ -80,8 +80,17 @@ module ImportExport
existing_categories = CategoryCustomField.where("name = 'import_id' AND value IN (?)", import_ids).select(:category_id, :value).to_a existing_categories = CategoryCustomField.where("name = 'import_id' AND value IN (?)", import_ids).select(:category_id, :value).to_a
existing_category_ids = existing_categories.pluck(:value) existing_category_ids = existing_categories.pluck(:value)
levels = category_levels
max_level = levels.values.max
if SiteSetting.max_category_nesting < max_level
puts "Setting max_category_nesting to #{max_level}..."
SiteSetting.max_category_nesting = max_level
end
fix_permissions
@categories.reject! { |c| existing_category_ids.include? c[:id].to_s } @categories.reject! { |c| existing_category_ids.include? c[:id].to_s }
@categories.sort_by! { |c| c[:parent_category_id].presence || 0 } @categories.sort_by! { |c| levels[c[:id]] || 0 }
@categories.each do |cat_attrs| @categories.each do |cat_attrs|
begin begin
@ -93,18 +102,19 @@ module ImportExport
category.user_id = new_user_id(cat_attrs[:user_id]) category.user_id = new_user_id(cat_attrs[:user_id])
import_id = "#{id}#{import_source}" import_id = "#{id}#{import_source}"
category.custom_fields["import_id"] = import_id category.custom_fields["import_id"] = import_id
category.permissions = permissions.present? ? permissions : { "everyone" => CategoryGroup.permission_types[:full] } category.permissions = permissions
category.save! category.save!
existing_categories << { category_id: category.id, value: import_id } existing_categories << { category_id: category.id, value: import_id }
if cat_attrs[:description].present? if cat_attrs[:description].present?
post = category.topic.ordered_posts.first post = category.topic.ordered_posts.first
post.raw = cat_attrs[:description] post.raw = cat_attrs[:description]
post.skip_validation = true
post.save! post.save!
post.rebake! post.rebake!
end end
rescue rescue => e
next puts "Failed to import category (ID = #{id}, name = #{cat_attrs[:name]}): #{e.message}"
end end
end end
@ -173,5 +183,62 @@ module ImportExport
@_import_source ||= "#{ENV['IMPORT_SOURCE'] || ''}" @_import_source ||= "#{ENV['IMPORT_SOURCE'] || ''}"
end end
def category_levels
@levels ||= begin
levels = {}
# Incomplete backups may lack definitions for some parent categories
# which would cause an infinite loop below.
parent_ids = @categories.map { |category| category[:parent_category_id] }.uniq
category_ids = @categories.map { |category| category[:id] }.uniq
(parent_ids - category_ids).each { |id| levels[id] = 0 }
loop do
changed = false
@categories.each do |category|
if !levels[category[:id]]
if !category[:parent_category_id]
levels[category[:id]] = 1
elsif levels[category[:parent_category_id]]
levels[category[:id]] = levels[category[:parent_category_id]] + 1
end
changed = true
end
end
break if !changed
end
levels
end
end
def fix_permissions
categories_by_id = @categories.to_h { |category| [category[:id], category] }
@categories.each do |category|
if category[:permissions_params].blank?
category[:permissions_params] = { "everyone" => CategoryGroup.permission_types[:full] }
end
end
max_level = category_levels.values.max
max_level.times do
@categories.each do |category|
parent_category = categories_by_id[category[:parent_category_id]]
next if !parent_category || !parent_category[:permissions_params] || parent_category[:permissions_params][:everyone]
parent_groups = parent_category[:permissions_params].map(&:first)
child_groups = category[:permissions_params].map(&:first)
only_subcategory_groups = child_groups - parent_groups
if only_subcategory_groups.present?
parent_category[:permissions_params].merge!(category[:permissions_params].slice(*only_subcategory_groups))
end
end
end
end
end end
end end

View File

@ -33,7 +33,8 @@ describe ImportExport::Importer do
.and change { User.count }.by(2) .and change { User.count }.by(2)
end end
it 'categories and groups' do context 'categories and groups' do
it 'works' do
data = import_data.dup data = import_data.dup
data[:topics] = nil data[:topics] = nil
data[:users] = nil data[:users] = nil
@ -46,6 +47,31 @@ describe ImportExport::Importer do
.and change { User.count }.by(0) .and change { User.count }.by(0)
end end
it 'works with sub-sub-categories' do
data = import_data.dup
# 11 -> 10 -> 15
data[:categories].find { |c| c[:id] == 10 }[:parent_category_id] = 11
data[:categories].find { |c| c[:id] == 15 }[:parent_category_id] = 10
expect { import(data) }
.to change { Category.count }.by(6)
.and change { SiteSetting.max_category_nesting }.from(2).to(3)
end
it 'fixes permissions' do
data = import_data.dup
data[:categories].find { |c| c[:id] == 10 }[:permissions_params] = { custom_group: 1 }
data[:categories].find { |c| c[:id] == 15 }[:permissions_params] = { staff: 1 }
permissions = data[:categories].find { |c| c[:id] == 10 }[:permissions_params]
expect { import(data) }
.to change { Category.count }.by(6)
.and change { permissions[:staff] }.from(nil).to(1)
end
end
it 'categories, groups and users' do it 'categories, groups and users' do
data = import_data.dup data = import_data.dup
data[:topics] = nil data[:topics] = nil