Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #15 from alfajango/migrators

Introduce migrators for other commenting systems
  • Loading branch information...
commit 957e69dedcf630bc549b260336611bb17246bd05 2 parents 08a5db8 + 6ae6da5
@FooBarWidget FooBarWidget authored
View
5 Gemfile
@@ -24,6 +24,11 @@ gem 'bcrypt-ruby'
group :development do
gem 'guard-livereload'
+
+ # Gems used for comment import from WordPress
+ # gem 'htmlentities'
+ # gem 'sequel'
+ # gem 'mysqlplus'
end
# Bundle gems for the local environment. Make sure to
View
22 app/controllers/admin/comments_controller.rb
@@ -1,9 +1,10 @@
class Admin::CommentsController < ApplicationController
layout 'admin'
- skip_authorization_check :only => :preview
+ skip_authorization_check :only => [:preview, :new_import, :import]
before_filter :set_navigation_ids
before_filter :save_return_to_url, :only => [:new, :edit, :approve, :destroy]
+ before_filter :require_admin!, :only => [:new_import, :import]
def index
authorize! :read, Comment
@@ -58,6 +59,25 @@ def destroy
redirect_back(admin_comments_path)
end
+ def new_import
+ @sites = Site.all
+ @migrators = Juvia::Migrators::PLATFORMS.map{ |p| p.titleize }
+ end
+
+ def import
+ if Juvia::Migrators.process(
+ params[:site_id],
+ params[:import_type],
+ params[:database_name],
+ params[:database_user],
+ params[:database_password],
+ params[:database_host]
+ )
+ flash[:notice] = "Imported!"
+ redirect_to(admin_comments_path)
+ end
+ end
+
private
def set_navigation_ids
@navigation_ids = [:dashboard, :comments]
View
38 app/views/admin/comments/new_import.html.erb
@@ -0,0 +1,38 @@
+<% @title = "Import Comments" %>
+<%= form_tag(:import_admin_comments) do |f| %>
+
+ <div class="para">
+ <%= label_tag :site_id %><br />
+ <%= select_tag :site_id, options_from_collection_for_select(@sites, 'id', 'name', params[:site_id].to_i) %>
+ </div>
+
+ <div class="para">
+ <%= label_tag :import_type %><br />
+ <%= select_tag :import_type, options_for_select(@migrators) %>
+ </div>
+
+ <div class="para">
+ <%= label_tag :database_host %><br />
+ <%= text_field_tag :database_host, 'localhost' %>
+ </div>
+
+ <div class="para">
+ <%= label_tag :database_name %><br />
+ <%= text_field_tag :database_name %>
+ </div>
+
+ <div class="para">
+ <%= label_tag :database_user %><br />
+ <%= text_field_tag :database_user %>
+ </div>
+
+ <div class="para">
+ <%= label_tag :database_password %><br />
+ <%= text_field_tag :database_password %>
+ </div>
+
+ <div class="para actions">
+ <%= primary_button_submit_tag('Import') %>
+ <%= button_link_to 'Cancel', admin_comments_path %>
+ </div>
+<% end %>
View
1  app/views/admin/sites/show.html.erb
@@ -69,6 +69,7 @@ function hideEmbeddingCode() {
<strong>Actions</strong><br>
<%= comment_button_link_to 'Test site: see how a comment page looks like', test_admin_site_path(@site) %>
<%= edit_button_link_to 'Edit', edit_admin_site_path(@site) %>
+ <%= button_link_to 'Import comments', import_admin_comments_path(:site_id => @site) %>
<%= danger_remove_button_link_to 'Delete', [:admin, @site],
:confirm => 'Are you sure? All topics and comments for this site will also be deleted!',
:method => :delete %>
View
2  app/views/mailer/comment_posted.text.erb
@@ -11,4 +11,4 @@ IP : <%= @comment.author_ip %>
User agent: <%= @comment.author_user_agent || 'Unknown' %>
Referer : <%= @comment.referer || 'Unknown' %>
-<%= @comment.content %>
+<%= @comment.content %>
View
1  config/application.rb
@@ -21,6 +21,7 @@
module Juvia
class Application < Rails::Application
require File.expand_path('../../lib/app_config', __FILE__)
+ require File.expand_path('../../lib/juvia/migrators', __FILE__)
config.required_app_config = [:base_url, :from, :email_method]
View
2  config/routes.rb
@@ -8,6 +8,8 @@
resources :comments do
collection do
get :preview
+ get :import, :to => 'comments#new_import'
+ post :import, :to => 'comments#import'
end
member do
put :approve
View
16 lib/juvia/migrators.rb
@@ -0,0 +1,16 @@
+module Juvia
+ module Migrators
+ PLATFORMS = %w(word_press)
+
+ PLATFORMS.each do |p|
+ require "juvia/migrators/#{p}"
+ end
+
+ def self.process(site_id, migrator, dbname, user, host='localhost', options={})
+ migrator_class = migrator.downcase.gsub(/\s/, '_')
+ raise "Not a valid import type: #{migrator_class}" unless PLATFORMS.any?{ |p| p == migrator_class }
+
+ Juvia::Migrators.const_get(migrator_class.camelize).send(:process, site_id, dbname, user, host, options)
+ end
+ end
+end
View
228 lib/juvia/migrators/word_press.rb
@@ -0,0 +1,228 @@
+begin
+ require 'sequel'
+rescue LoadError
+ STDERR.puts "Could not load sequel gem, so WordPress import will not work."
+end
+require 'fileutils'
+require 'yaml'
+
+# NOTE: This converter requires Sequel and the MySQL gems.
+# The MySQL gem can be difficult to install on OS X. Once you have MySQL
+# installed, running the following commands should work:
+# $ sudo gem install sequel
+# $ sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
+
+module Juvia
+ module Migrators
+ class WordPress
+
+ # Main migrator function. Call this to perform the migration.
+ #
+ # dbname:: The name of the database
+ # user:: The database user name
+ # pass:: The database user's password
+ # host:: The address of the MySQL database host. Default: 'localhost'
+ # options:: A hash table of configuration options.
+ #
+ # Supported options are:
+ #
+ # :table_prefix:: Prefix of database tables used by WordPress.
+ # Default: 'wp_'
+ # :clean_entities:: If true, convert non-ASCII characters to HTML
+ # entities in the posts, comments, titles, and
+ # names. Requires the 'htmlentities' gem to
+ # work. Default: true.
+ # :comments:: If true, migrate post comments too. Comments
+ # are saved in the post's YAML front matter.
+ # Default: true.
+ # :categories:: If true, save the post's categories in its
+ # YAML front matter.
+ # :tags:: If true, save the post's tags in its
+ # YAML front matter.
+ # :more_excerpt:: If true, when a post has no excerpt but
+ # does have a <!-- more --> tag, use the
+ # preceding post content as the excerpt.
+ # Default: true.
+ # :more_anchor:: If true, convert a <!-- more --> tag into
+ # two HTML anchors with ids "more" and
+ # "more-NNN" (where NNN is the post number).
+ # Default: true.
+ # :status:: Array of allowed post statuses. Only
+ # posts with matching status will be migrated.
+ # Known statuses are :publish, :draft, :private,
+ # and :revision. If this is nil or an empty
+ # array, all posts are migrated regardless of
+ # status. Default: [:publish].
+ #
+ def self.process(site_id, dbname, user, pass, host='localhost', options={})
+ options = {
+ :table_prefix => 'wp_',
+ :clean_entities => true,
+ :comments => true,
+ :categories => true,
+ :tags => true,
+ :more_excerpt => true,
+ :more_anchor => true,
+ :status => [:publish] # :draft, :private, :revision
+ }.merge(options)
+ options[:site] = Site.find(site_id)
+
+ if options[:clean_entities]
+ begin
+ require 'htmlentities'
+ rescue LoadError
+ STDERR.puts "Could not require 'htmlentities', so the " +
+ ":clean_entities option is now disabled."
+ options[:clean_entities] = false
+ end
+ end
+
+ begin
+ db = Sequel.mysql(dbname, :user => user, :password => pass,
+ :host => host, :encoding => 'utf8')
+ rescue NameError => e
+ raise "#{e}\nUncomment the 'sequel' and 'mysqlplus' gems from your Gemfile and run `bundle install`."
+ end
+
+ px = options[:table_prefix]
+
+ posts_query = "
+ SELECT
+ posts.ID AS `id`,
+ posts.guid AS `guid`,
+ posts.post_type AS `type`,
+ posts.post_status AS `status`,
+ posts.post_title AS `title`,
+ posts.post_name AS `slug`,
+ posts.post_date AS `date`,
+ posts.post_content AS `content`,
+ posts.post_excerpt AS `excerpt`,
+ posts.comment_count AS `comment_count`,
+ users.display_name AS `author`,
+ users.user_login AS `author_login`,
+ users.user_email AS `author_email`,
+ users.user_url AS `author_url`
+ FROM #{px}posts AS `posts`
+ LEFT JOIN #{px}users AS `users`
+ ON posts.post_author = users.ID"
+
+ if options[:status] and not options[:status].empty?
+ status = options[:status][0]
+ posts_query << "
+ WHERE posts.post_status = '#{status.to_s}'"
+ options[:status][1..-1].each do |status|
+ posts_query << " OR
+ posts.post_status = '#{status.to_s}'"
+ end
+ end
+
+ db[posts_query].each do |post|
+ process_post(post, db, options)
+ end
+ end
+
+
+ def self.process_post(post, db, options)
+ px = options[:table_prefix]
+
+ title = post[:title]
+ if options[:clean_entities]
+ title = clean_entities(title)
+ end
+
+ slug = post[:slug]
+ if !slug or slug.empty?
+ slug = sluggify(title)
+ end
+
+ topic_url = URI("#{options[:site].url}/#{slug}")
+
+ topic = Topic.lookup_or_create(
+ options[:site].key,
+ topic_url.path,
+ title,
+ topic_url.to_s
+ )
+
+ comments = []
+
+ if options[:comments] and post[:comment_count].to_i > 0
+ cquery =
+ "SELECT
+ comment_ID AS `id`,
+ comment_author AS `author`,
+ comment_author_email AS `author_email`,
+ comment_author_url AS `author_url`,
+ comment_author_IP AS `author_ip`,
+ comment_agent AS `user_agent`,
+ comment_date AS `date`,
+ comment_date_gmt AS `date_gmt`,
+ comment_content AS `content`
+ FROM #{px}comments
+ WHERE
+ comment_post_ID = '#{post[:id]}' AND
+ comment_approved != 'spam'"
+
+
+ db[cquery].each do |comment|
+
+ comcontent = comment[:content].to_s
+ if comcontent.respond_to?(:force_encoding)
+ comcontent.force_encoding("UTF-8")
+ end
+ if options[:clean_entities]
+ comcontent = clean_entities(comcontent)
+ end
+ comauthor = comment[:author].to_s
+ if options[:clean_entities]
+ comauthor = clean_entities(comauthor)
+ end
+
+ comments << Comment.new(
+ :topic_id => comment[:id].to_i,
+ :author_name => comauthor,
+ :author_email => comment[:author_email].to_s,
+ :author_ip => comment[:author_ip].to_s,
+ :author_user_agent => comment[:user_agent],
+ :created_at => comment[:date],
+ :content => comcontent,
+ )
+ end
+
+ comments.sort!{ |a,b| a['date'] <=> b['date'] }
+ end
+
+ topic.comments << comments
+
+ end
+
+
+ def self.clean_entities( text )
+ if text.respond_to?(:force_encoding)
+ text.force_encoding("UTF-8")
+ end
+ text = HTMLEntities.new.encode(text, :named)
+ # We don't want to convert these, it would break all
+ # HTML tags in the post and comments.
+ text.gsub!("&amp;", "&")
+ text.gsub!("&lt;", "<")
+ text.gsub!("&gt;", ">")
+ text.gsub!("&quot;", '"')
+ text.gsub!("&apos;", "'")
+ text
+ end
+
+
+ def self.sluggify( title )
+ begin
+ require 'unidecode'
+ title = title.to_ascii
+ rescue LoadError
+ STDERR.puts "Could not require 'unidecode'. If your post titles have non-ASCII characters, you could get nicer permalinks by installing unidecode."
+ end
+ title.downcase.gsub(/[^0-9A-Za-z]+/, " ").strip.gsub(" ", "-")
+ end
+
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.