FEATURE: Import script for jForum

FEATURE: Import script for jForum

diff --git a/script/import_scripts/jforum.rb b/script/import_scripts/jforum.rb
new file mode 100644
index 0000000..1f52412
--- /dev/null
+++ b/script/import_scripts/jforum.rb
@@ -0,0 +1,586 @@
+# frozen_string_literal: true
+
+require "mysql2"
+require_relative 'base'
+
+class ImportScripts::JForum < ImportScripts::Base
+  BATCH_SIZE = 1000
+  REMOTE_AVATAR_REGEX ||= /\Ahttps?:\/\//i
+
+  def initialize
+    super
+
+    @settings = YAML.load(File.read(ARGV.first), symbolize_names: true)
+
+    @database_client = Mysql2::Client.new(
+      host: @settings[:database][:host],
+      port: @settings[:database][:port],
+      username: @settings[:database][:username],
+      password: @settings[:database][:password],
+      database: @settings[:database][:schema],
+      reconnect: true
+    )
+  end
+
+  def execute
+    import_users
+
+    if @settings[:import_categories_as_tags]
+      import_tags
+    else
+      import_categories
+    end
+
+    import_posts
+    import_likes
+    import_category_subscriptions
+    import_topic_subscriptions
+    mark_topics_as_solved
+  end
+
+  def import_users
+    puts '', 'creating users'
+    total_count = count("SELECT COUNT(1) AS count FROM jforum_users")
+    last_user_id = 0
+
+    custom_fields_query = user_custom_fields_query
+
+    batches do |offset|
+      rows, last_user_id = query(<<~SQL, :user_id)
+        SELECT user_id, username, user_lastvisit, user_regdate, user_email, user_from, user_active,
+               user_avatar, COALESCE(user_realname, CONCAT(first_name, ' ', last_name)) AS name
+               #{custom_fields_query}
+        FROM jforum_users
+        WHERE user_id > #{last_user_id}
+        ORDER BY user_id
+        LIMIT #{BATCH_SIZE}
+      SQL
+      break if rows.size < 1
+
+      next if all_records_exist?(:users, rows.map { |row| row[:user_id] })
+
+      create_users(rows, total: total_count, offset: offset) do |row|
+        {
+          id: row[:user_id],
+          email: row[:user_email]&.strip,
+          name: row[:name],
+          created_at: row[:user_regdate],
+          last_seen_at: row[:user_lastvisit],
+          active: row[:user_active] == 1,
+          location: row[:user_from],
+          custom_fields: user_custom_fields(row),
+          post_create_action: proc do |user|
+            import_avatar(user, row[:user_avatar])
+          end
+        }
+      end
+    end
+  end
+
+  def user_custom_fields_query
+    return "" if @settings[:custom_fields].blank?
+
+    columns = []
+    @settings[:custom_fields].map do |field|
+      columns << (field[:alias] ? "#{field[:column]} AS #{field[:alias]}" : field[:column])
+    end
+    ", #{columns.join(', ')}"
+  end
+
+  def user_fields
+    @user_fields ||= begin
+      Hash[UserField.all.map { |field| [field.name, field] }]
+    end
+  end
+
+  def user_custom_fields(row)
+    return nil if @settings[:custom_fields].blank?
+
+    custom_fields = {}
+
+    @settings[:custom_fields].map do |field|
+      column = field[:alias] || field[:column]
+      value = row[column.to_sym]
+      user_field = user_fields[field[:name]]
+
+      case user_field.field_type
+      when "confirm"
+        value = value == 1 ? true : nil
+      when "dropdown"
+        value = user_field.user_field_options.find { |option| option.value == value } ? value : nil
+      end
+
+      custom_fields["user_field_#{user_field.id}"] = value if value.present?
+    end
+
+    custom_fields
+  end
+
+  def import_avatar(user, avatar_source)
+    return if avatar_source.blank?
+
+    path = File.join(@settings[:avatar_directory], avatar_source)
+
+    if File.file?(path)
+      @uploader.create_avatar(user, path)
+    elsif avatar_source.match?(REMOTE_AVATAR_REGEX)
+      UserAvatar.import_url_for_user(avatar_source, user) rescue nil
+    end
+  end
+
+  def import_tags
+    puts "", "creating tags"
+
+    @tags_by_import_forum_id = {}
+
+    SiteSetting.tagging_enabled = true
+    SiteSetting.max_tag_length = 100
+    SiteSetting.max_tags_per_topic = 10
+    SiteSetting.force_lowercase_tags = false
+
+    additional_tags = Array.wrap(@settings[:additional_tags])
+
+    rows = query(<<~SQL)
+      SELECT c.categories_id, c.title AS category_name, f.forum_id, f.forum_name
+      FROM jforum_forums f
+        JOIN jforum_categories c ON f.categories_id = c.categories_id
+      WHERE EXISTS (
+        SELECT 1
+        FROM jforum_posts p
+        WHERE p.forum_id = f.forum_id
+      )
+    SQL
+
+    rows.each do |row|
+      tag_names = [row[:category_name], row[:forum_name]]
+
+      additional_tags.each do |additional_tag|
+        if additional_tag[:old_category_name].match?(row[:category_name])
+          tag_names += additional_tag[:tag_names]
+        end
+      end
+
+      tag_names.map! { |t| t.parameterize(preserve_case: true) }
+
+      tag_names.each_with_index do |tag_name, index|
+        tag = create_tag(tag_name)
+        next if tag.blank?
+
+        case index
+        when 0
+          url = File.join(@settings[:permalink_prefix], "forums/list/#{row[:categories_id]}.page")
+          Permalink.create(url: url, tag_id: tag.id) unless Permalink.find_by(url: url)
+        when 1
+          url = File.join(@settings[:permalink_prefix], "forums/show/#{row[:forum_id]}.page")
+          Permalink.create(url: url, tag_id: tag.id) unless Permalink.find_by(url: url)
+        end
+      end
+
+      @tags_by_import_forum_id[row[:forum_id]] = tag_names.uniq
+    end
+
+    category_mappings = Array.wrap(@settings[:category_mappings])
+
+    if category_mappings.blank?
+      rows.each do |row|
+        category_mappings.each do |mapping|
+          if mapping[:old_category_name].match?(row[:category_name])
+            @lookup.add_category(row[:forum_id], Category.find(mapping[:category_id]))
+          end
+        end
+      end
+    end
+  end
+
+  def create_tag(tag_name)
+    tag = Tag.find_by_name(tag_name)
+
+    if tag
+      # update if the case is different
+      tag.update!(name: tag_name) if tag.name != tag_name
+      nil
+    else
+      Tag.create!(name: tag_name)
+    end
+  end
+
+  def import_categories
+    puts "", "creating categories"
+
+    rows = query(<<~SQL)
+      SELECT categories_id, title, display_order
+      FROM jforum_categories
+      ORDER BY display_order
+    SQL
+
+    create_categories(rows) do |row|
+      {
+        id: "C#{row[:categories_id]}",
+        name: row[:title],
+        position: row[:display_order],
+        post_create_action: proc do |category|
+          url = File.join(@settings[:permalink_prefix], "forums/list/#{row[:categories_id]}.page")
+          Permalink.create(url: url, category_id: category.id) unless Permalink.find_by(url: url)
+        end
+      }
+    end
+
+    rows = query(<<~SQL)
+      SELECT forum_id, categories_id, forum_name, forum_desc, forum_order
+      FROM jforum_categories
+      ORDER BY categories_id, forum_order
+    SQL
+
+    create_categories(rows) do |row|
+      {
+        id: row[:forum_id],
+        name: row[:forum_name],
+        description: row[:forum_desc],
+        position: row[:forum_order],
+        parent_category_id: @lookup.category_id_from_imported_category_id("C#{row[:categories_id]}"),
+        post_create_action: proc do |category|
+          url = File.join(@settings[:permalink_prefix], "forums/show/#{row[:forum_id]}.page")
+          Permalink.create(url: url, category_id: category.id) unless Permalink.find_by(url: url)
+        end
+      }
+    end
+  end
+
+  def import_posts
+    puts '', 'creating topics and posts'
+    total_count = count("SELECT COUNT(1) AS count FROM jforum_posts")
+    last_post_id = 0
+
+    batches do |offset|
+      rows, last_post_id = query(<<~SQL, :post_id)
+        SELECT p.post_id, p.topic_id, p.user_id, t.topic_title, pt.post_text, p.post_time, t.topic_status,
+               t.topic_type, t.topic_views, p.poster_ip, p.forum_id, t.topic_acceptedanswer_post_id,

[... diff too long, it was truncated ...]

GitHub sha: 1f053173

1 Like