From 1cad3914e88fe2fdc2f7655b8e8e411add00971b Mon Sep 17 00:00:00 2001 From: Doug Youch Date: Wed, 22 Sep 2010 11:10:57 -0400 Subject: [PATCH 1/5] Added a wordpress import model. Added push_folder and add to DomainFile makes it easier to import files into a folder. Fix truncate warning. --- app/models/domain_file.rb | 29 ++++- .../app/models/blog/wordpress_importer.rb | 110 ++++++++++++++++++ .../feedback/feedback/_comments_table.rhtml | 2 +- 3 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 vendor/modules/blog/app/models/blog/wordpress_importer.rb diff --git a/app/models/domain_file.rb b/app/models/domain_file.rb index cc994c91..4f354836 100644 --- a/app/models/domain_file.rb +++ b/app/models/domain_file.rb @@ -596,9 +596,14 @@ def self.temporary_folder # Returns the themes folder of the file system def self.themes_folder - DomainFile.find(:first,:conditions => "name = 'Themes' and parent_id = #{self.root_folder.id}") || DomainFile.create(:name => 'Themes', :parent_id => self.root_folder.id, :file_type => 'fld') + self.push_folder 'Themes' end - + + def self.push_folder(name, opts={}) + parent_id = opts[:parent_id] || self.root_folder.id + DomainFile.find(:first,:conditions => ["name = ? and parent_id = ?", name, parent_id]) || DomainFile.create(:name => name, :parent_id => parent_id, :file_type => 'fld') + end + # Is this an image def image?; self.file_type == 'img'; end @@ -1329,7 +1334,25 @@ def generate_csv nil end end - + + def add(filename, opts={}) + return nil unless self.folder? + + filename = filename.filename if filename.is_a?(DomainFile) + + # if it is a url, create a URI + if filename =~ /^https?:\/\// + begin + filename = URI.parse(filename) + rescue URI::Error => e + return nil + end + end + + process_immediately = opts.has_key?(:process_immediately) ? opts[:process_immediately] : true + DomainFile.create :parent_id => self.id, :filename => filename, :process_immediately => process_immediately + end + def self.download(uri, limit=10) raise ArgumentError, 'HTTP redirect too deep' if limit == 0 diff --git a/vendor/modules/blog/app/models/blog/wordpress_importer.rb b/vendor/modules/blog/app/models/blog/wordpress_importer.rb new file mode 100644 index 00000000..63db0749 --- /dev/null +++ b/vendor/modules/blog/app/models/blog/wordpress_importer.rb @@ -0,0 +1,110 @@ + +class Blog::WordpressImporter + attr_accessor :xml, :blog, :images, :folder + + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper + + def initialize + self.images = {} + end + + def folder + @folder ||= DomainFile.push_folder self.blog.name + end + + def parse + begin + Hash.from_xml self.xml.gsub('excerpt:encoded>', 'excerpt>').gsub(//, '') + rescue + end + end + + def import + xml_data = self.parse + return unless xml_data + + categories = {} + blog_categories = xml_data['rss']['channel']['category'] + blog_categories = [blog_categories] unless blog_categories.is_a?(Array) + blog_categories.each do |category| + cat = self.push_category(category) + next unless cat + categories[category['cat_name']] = cat + end + + items = xml_data['rss']['channel']['item'] + items = [items] unless items.is_a?(Array) + items.each do |item| + if item['post_type'] == 'post' + self.create_post categories, item + end + end + end + + def push_category(opts={}) + name = opts['cat_name'] + return nil if name == 'Uncategorized' + return nil if name.blank? + self.blog.blog_categories.find_by_name(name) || self.blog.blog_categories.create(:name => name) + end + + def parse_body(body) + body.gsub!(/src=("|')([^\1]+?)\1/) do |match| + quote = $1 + src = $2 + file = nil + file = self.folder.add(src) if src =~ /^http/ + if file + self.images[src] = file + "src=#{quote}#{file.editor_url}#{quote}" + else + match + end + end + + self.images.each do |src, file| + body.gsub! src, file.editor_url + end + + simple_format body + end + + def create_post(categories, item={}) + body = item['encoded'] + return if body.blank? + status = item['status'] == 'publish' ? 'published' : 'draft' + disallow_comments = item['comment_status'] == 'open' ? false : true + post = self.blog.blog_posts.create :body => self.parse_body(body), :author => item['creator'], :title => item['title'], :status => 'published', :published_at => Time.parse(item['pubDate']), :status => status, :disallow_comments => disallow_comments, :permalink => item['post_name'], :created_at => Time.parse(item['post_date_gmt']), :preview => self.parse_body(item['excerpt']) + + post_categories = item['category'] + post_categories = [post_categories] unless post_categories.is_a?(Array) + post_categories.uniq.each do |cat| + next unless categories[cat] + Blog::BlogPostsCategory.create :blog_post_id => post.id, :blog_category_id => categories[cat].id + end + + comments = item['comment'] + if comments + comments = [comments] unless comments.is_a?(Array) + comments.each do |comment| + self.create_comment post, comment + end + end + + if item['tag'] + tags = item['tag'] + tags = [tags] unless tags.is_a?(Array) + post.add_tags tags.join(',') + end + + post + end + + def create_comment(post, comment) + return if comment['comment_content'].blank? + user = comment['comment_author_email'].blank? ? nil : EndUser.push_target(comment['comment_author_email'], :name => comment['comment_author']) + rating = comment['comment_approved'] == "1" ? 1 : 0 + Comment.create :target => post, :end_user_id => user ? user.id : nil, :posted_at => Time.parse(comment['comment_date_gmt']), :posted_ip => comment['comment_author_IP'], :name => comment['comment_author'], :email => comment['comment_author_email'], :comment => comment['comment_content'], :rating => rating + end +end diff --git a/vendor/modules/feedback/app/views/feedback/feedback/_comments_table.rhtml b/vendor/modules/feedback/app/views/feedback/feedback/_comments_table.rhtml index b059dd68..fb39aa74 100644 --- a/vendor/modules/feedback/app/views/feedback/feedback/_comments_table.rhtml +++ b/vendor/modules/feedback/app/views/feedback/feedback/_comments_table.rhtml @@ -21,7 +21,7 @@ end target_title = target_cache[t.target_type][t.target_id] && target_cache[t.target_type][t.target_id].content_node ? target_cache[t.target_type][t.target_id].content_node.title : '' -%> - <%= truncate(target_title,35) if target_cache[t.target_type][t.target_id] %> + <%= truncate(target_title,:length => 35) if target_cache[t.target_type][t.target_id] %> <%= v(t.end_user.name) if t.end_user -%> From 938a51cce1ee202d25e4affb31b4b4c4950c6af9 Mon Sep 17 00:00:00 2001 From: Doug Youch Date: Wed, 22 Sep 2010 13:22:12 -0400 Subject: [PATCH 2/5] Added Word Press Web Service to download word press blogs. Added dynamic routes to active web service. --- lib/active_web_service.rb | 12 ++- .../app/models/blog/wordpress_web_service.rb | 96 +++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 vendor/modules/blog/app/models/blog/wordpress_web_service.rb diff --git a/lib/active_web_service.rb b/lib/active_web_service.rb index 3b0d1730..9d1c2791 100644 --- a/lib/active_web_service.rb +++ b/lib/active_web_service.rb @@ -212,7 +212,17 @@ def #{name}(#{function_args.join(',')}) end method_src << "end\n" - self.class_eval method_src, __FILE__, __LINE__ + if options[:instance] + options[:instance].instance_eval method_src, __FILE__, __LINE__ + elsif options[:class] + options[:class].class_eval method_src, __FILE__, __LINE__ + else + self.class_eval method_src, __FILE__, __LINE__ + end + end + + def route(name, path, options={}) + self.class.route name, path, options.merge(:instance => self) end # Creates routes for a RESTful API diff --git a/vendor/modules/blog/app/models/blog/wordpress_web_service.rb b/vendor/modules/blog/app/models/blog/wordpress_web_service.rb new file mode 100644 index 00000000..29598a88 --- /dev/null +++ b/vendor/modules/blog/app/models/blog/wordpress_web_service.rb @@ -0,0 +1,96 @@ +require 'nokogiri' + +class Blog::WordpressWebService < ActiveWebService + attr_accessor :error + + headers 'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/4.0 (.NET CLR 3.5.30729)' + + def initialize(url, username, password) + @uri = URI.parse(url.gsub(/\/$/, '')) + @username = username + @password = password + self.base_uri = "#{@uri.scheme}://#{@uri.host}:#{@uri.port}" + + self.route :wp_login_get, "#{@uri.path}/wp-login.php", :method => :get, :return => :parse_login_page + self.route :wp_login_post, "#{@uri.path}/wp-login.php", :method => :post, :return => :parse_login_page + + self.route :wp_export_get, "#{@uri.path}/wp-admin/export.php", :method => :get, :return => :parse_export_form + self.route :wp_export, "#{@uri.path}/wp-admin/export.php", :method => :get + end + + def parse_login_page(response) + return @error = 'Login page not found' unless response.code == 200 + + parent = Nokogiri::HTML.parse(response.body).css('body').first + login_form = parent.css('#loginform') + return @error = 'Login form not found' unless login_form.size > 0 + + login_error = parent.css('#login_error') + return @error = 'Login failed' if login_error.size > 0 + + login_form = login_form.first + @inputs = {} + login_form.css('input').each do |input| + next unless input.attributes['name'] + @inputs[input.attributes['name'].to_s] = (input.attributes['value'] || '').to_s + end + + @cookies ||= {} + @response.headers['set-cookie'].collect { |cookie| cookie.split("\; ")[0].split('=') }.each do |c| + @cookies[c[0]] = c[1] + end + end + + def login + self.wp_login_get + return false if @inputs.nil? + @inputs['log'] = @username + @inputs['pwd'] = @password + begin + self.wp_login_post @inputs, :no_follow => true + rescue HTTParty::RedirectionTooDeep => e + e.response.header['set-cookie'].split(',').collect { |cookie| cookie.split("\; ")[0].split('=') }.each do |c| + @cookies[c[0]] = c[1] + end + end + @error ? false : true + end + + def parse_export_form(response) + parent = Nokogiri::HTML.parse(response.body).css('body').first + export_form = parent.css('form') + return @error = 'Export form not found' unless export_form.size > 0 + export_form = export_form.first + @inputs = {} + export_form.css('input').each do |input| + next unless input.attributes['name'] + @inputs[input.attributes['name'].to_s] = (input.attributes['value'] || '').to_s + end + + export_form.css('select').each do |input| + next unless input.attributes['name'] + value = '' + input.css('option').each do |option| + value = option.attributes['value'] if option.attributes['selected'] + end + + @inputs[input.attributes['name'].to_s] = (value || '').to_s + end + + @inputs + end + + def export + self.wp_export_get + return false if @error + self.wp_export :query => @inputs + return self.response.body if self.response.headers['content-type'].to_s =~ /xml/ + false + end + + def build_options!(options) + super + options[:headers] ||= {} + options[:headers]['cookie'] = @cookies.to_a.collect{|c| "#{c[0]}=#{c[1]}"}.join('; ') + ';' if @cookies + end +end From 36da1e5d3966af6b3a7efa2797876e1fdf09be5b Mon Sep 17 00:00:00 2001 From: Doug Youch Date: Wed, 22 Sep 2010 17:37:07 -0400 Subject: [PATCH 3/5] Can import word press files and sites from the blog manage import page. --- .../app/controllers/blog/manage_controller.rb | 67 +++++++++++++-- .../app/models/blog/wordpress_importer.rb | 86 ++++++++++++++++--- .../blog/app/views/blog/manage/import.rhtml | 23 +++-- 3 files changed, 150 insertions(+), 26 deletions(-) diff --git a/vendor/modules/blog/app/controllers/blog/manage_controller.rb b/vendor/modules/blog/app/controllers/blog/manage_controller.rb index b7bcc57a..a3121085 100644 --- a/vendor/modules/blog/app/controllers/blog/manage_controller.rb +++ b/vendor/modules/blog/app/controllers/blog/manage_controller.rb @@ -235,18 +235,73 @@ def import @blog = Blog::BlogBlog.find(params[:path][0]) blog_path(@blog,"Import Blog") - if request.post? && params[:import] && @file = DomainFile.find_by_id(params[:import][:import_file_id]) + @import = ImportOptions.new params[:import] + @import.wordpress_import_settings = ['comments', 'pages'] unless params[:import] + + if request.post? && @import.valid? if params[:commit] - @blog.import_file(@file,myself) + if @import.import @blog, myself + redirect_to :action => 'index', :path => [ @blog.id ] + end + else + redirect_to :action => 'index', :path => [ @blog.id ] end - redirect_to :action => 'index', :path => [ @blog.id ] end - - end - + protected + class ImportOptions < HashModel + attributes :import_file_id => nil, :wordpress_export_file_id => nil, :wordpress_url => nil, :wordpress_username => nil, :wordpress_password => nil, :wordpress_import_settings => [] + + domain_file_options :import_file_id, :wordpress_export_file_id + + def validate + if self.import_file_id.blank? && self.wordpress_export_file_id.blank? && self.wordpress_url.blank? + self.errors.add_to_base 'Import settings not specified' + elsif self.import_file_id.blank? && self.wordpress_export_file_id.blank? && ! self.wordpress_url.blank? + self.errors.add(:wordpress_url, 'is invalid') unless URI::regexp(%w(http https)).match(self.wordpress_url) + self.errors.add(:wordpress_username, 'is missing') if self.wordpress_username.blank? + self.errors.add(:wordpress_password, 'is missing') if self.wordpress_password.blank? + end + end + + def wordpress_importer + return @wordpress_importer if @wordpress_importer + @wordpress_importer = Blog::WordpressImporter.new + @wordpress_importer.import_comments = self.wordpress_import_settings.include?('comment') + @wordpress_importer.import_pages = self.wordpress_import_settings.include?('pages') + @wordpress_importer + end + + def import(blog, user) + if self.import_file + blog.import_file(self.import_file, user) + elsif self.wordpress_export_file + self.wordpress_importer.blog = blog + self.wordpress_importer.import_file(self.wordpress_export_file) + self.errors.add(:wordpress_export_file_id, self.wordpress_importer.error) unless self.wordpress_importer.import + elsif self.wordpress_url + self.wordpress_importer.blog = blog + unless self.wordpress_importer.import_site(self.wordpress_url, self.wordpress_username, self.wordpress_password) + if self.wordpress_importer.error == 'Login failed' + self.errors.add(:wordpress_username, 'is invalid') + self.errors.add(:wordpress_password, 'is invalid') + else + self.errors.add(:wordpress_url, 'is invalid') + self.errors.add_to_base(self.wordpress_importer.error) + end + end + + unless self.wordpress_importer.error + self.errors.add_to_base(self.wordpress_importer.error) unless self.wordpress_importer.import + end + end + + self.errors.length > 0 ? false : true + end + end + def blog_path(blog,path=nil) base = ['Content'] base << 'Site Blogs' if blog.is_user_blog? diff --git a/vendor/modules/blog/app/models/blog/wordpress_importer.rb b/vendor/modules/blog/app/models/blog/wordpress_importer.rb index 63db0749..524ae527 100644 --- a/vendor/modules/blog/app/models/blog/wordpress_importer.rb +++ b/vendor/modules/blog/app/models/blog/wordpress_importer.rb @@ -1,18 +1,42 @@ class Blog::WordpressImporter - attr_accessor :xml, :blog, :images, :folder + attr_accessor :xml, :blog, :images, :folder, :error, :import_comments, :import_pages include ActionView::Helpers::TagHelper include ActionView::Helpers::TextHelper def initialize self.images = {} + self.import_pages = true + self.import_comments = true end def folder @folder ||= DomainFile.push_folder self.blog.name end + def import_file(file) + file = file.filename if file.is_a?(DomainFile) + File.open(file, 'r') { |f| self.xml = f.read } + true + end + + def import_site(url, username, password) + service = Blog::WordpressWebService.new url, username, password + unless service.login + self.error = service.error + return false + end + + self.xml = service.export + unless self.xml + self.error = service.error + return false + end + + true + end + def parse begin Hash.from_xml self.xml.gsub('excerpt:encoded>', 'excerpt>').gsub(//, '') @@ -22,24 +46,43 @@ def parse def import xml_data = self.parse - return unless xml_data + unless xml_data + self.error = 'WordPress file is invalid' + return false + end + + unless xml_data['rss'] && xml_data['rss']['channel'] + self.error = 'WordPress file is invalid' + return false + end categories = {} blog_categories = xml_data['rss']['channel']['category'] - blog_categories = [blog_categories] unless blog_categories.is_a?(Array) - blog_categories.each do |category| - cat = self.push_category(category) - next unless cat - categories[category['cat_name']] = cat + if blog_categories + blog_categories = [blog_categories] unless blog_categories.is_a?(Array) + blog_categories.each do |category| + cat = self.push_category(category) + next unless cat + categories[category['cat_name']] = cat + end end items = xml_data['rss']['channel']['item'] - items = [items] unless items.is_a?(Array) - items.each do |item| - if item['post_type'] == 'post' - self.create_post categories, item + if items + items = [items] unless items.is_a?(Array) + items.each do |item| + if item['post_type'] == 'post' + self.create_post categories, item + elsif item['post_type'] == 'page' + self.create_page item + end end + else + self.error = 'WordPress file has no posts to import' + return false end + + true end def push_category(opts={}) @@ -73,10 +116,14 @@ def parse_body(body) def create_post(categories, item={}) body = item['encoded'] return if body.blank? + return if self.blog.blog_posts.find_by_permalink item['post_name'] + status = item['status'] == 'publish' ? 'published' : 'draft' disallow_comments = item['comment_status'] == 'open' ? false : true post = self.blog.blog_posts.create :body => self.parse_body(body), :author => item['creator'], :title => item['title'], :status => 'published', :published_at => Time.parse(item['pubDate']), :status => status, :disallow_comments => disallow_comments, :permalink => item['post_name'], :created_at => Time.parse(item['post_date_gmt']), :preview => self.parse_body(item['excerpt']) + return unless post.id + post_categories = item['category'] post_categories = [post_categories] unless post_categories.is_a?(Array) post_categories.uniq.each do |cat| @@ -102,9 +149,26 @@ def create_post(categories, item={}) end def create_comment(post, comment) + return unless self.import_comments return if comment['comment_content'].blank? user = comment['comment_author_email'].blank? ? nil : EndUser.push_target(comment['comment_author_email'], :name => comment['comment_author']) rating = comment['comment_approved'] == "1" ? 1 : 0 Comment.create :target => post, :end_user_id => user ? user.id : nil, :posted_at => Time.parse(comment['comment_date_gmt']), :posted_ip => comment['comment_author_IP'], :name => comment['comment_author'], :email => comment['comment_author_email'], :comment => comment['comment_content'], :rating => rating end + + def create_page(item) + return unless self.import_pages + body = item['encoded'] + return if body.blank? + + node_path = SiteNode.generate_node_path(item['post_name']) + node_path = '' if node_path == 'home' + SiteVersion.current.root_node.push_subpage(node_path) do |nd, rv| + rv.title = item['title'] + # Basic Paragraph + rv.push_paragraph(nil, 'html') do |para| + para.display_body = self.parse_body(body) + end + end + end end diff --git a/vendor/modules/blog/app/views/blog/manage/import.rhtml b/vendor/modules/blog/app/views/blog/manage/import.rhtml index 1eae2557..1a1f0ffd 100644 --- a/vendor/modules/blog/app/views/blog/manage/import.rhtml +++ b/vendor/modules/blog/app/views/blog/manage/import.rhtml @@ -1,16 +1,21 @@
+<% admin_form_for :import, @import do |f| %> + <%= f.show_error_message :base, '' %> + <%= f.header 'Select a CSV file to import' %> + <%= f.filemanager_file :import_file_id, :description => "File must be a CSV with the following columns:\n Title, permalink, author,published date, preview, body, embed\nOnly title and body are required to have non-empty values but all columns must be present" %> - <% admin_form_for :import do |f| %> - <%= f.header 'Select a CSV file to import' %> - <%= f.filemanager_file :import_file_id, :description => "File must be a CSV with the following columns:\n Title, permalink, author,published date, preview, body, embed\nOnly title and body are required to have non-empty values but all columns must be present" %> - - - <%= f.spacer %> - <%= f.cancel_submit_buttons "Cancel","Submit" %> - <% end -%> - + <%= f.header 'Import WordPress Blog' %> + <%= f.filemanager_file :wordpress_export_file_id, :label => 'Export file', :description => "Log into your blog. Goto Tools > Export and click Download" %> + <%= f.custom_field '', :value => 'Or' %> + <%= f.text_field :wordpress_url, :label => 'URL', :size => 40, :description => "The home page of your word press blog.\n Example: http://.wordpress.com/" %> + <%= f.text_field :wordpress_username, :label => 'Username', :size => 15 %> + <%= f.password_field :wordpress_password, :label => 'Password', :size => 15 %> + <%= f.check_boxes :wordpress_import_settings, [['Import comments', 'comments'], ['Import pages', 'pages']], :label => 'Import settings' %> + <%= f.spacer %> + <%= f.cancel_submit_buttons "Cancel","Submit" %> +<% end -%>
From 0b191a3f87b50ac36aea7cd9d3ce92e4ec121c7d Mon Sep 17 00:00:00 2001 From: Doug Youch Date: Wed, 22 Sep 2010 18:33:55 -0400 Subject: [PATCH 4/5] Recreate the directory struct of wordpress pages. --- .../app/models/blog/wordpress_importer.rb | 20 ++++++++++++++++--- .../blog/app/views/blog/manage/import.rhtml | 2 +- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/vendor/modules/blog/app/models/blog/wordpress_importer.rb b/vendor/modules/blog/app/models/blog/wordpress_importer.rb index 524ae527..bb8ecd69 100644 --- a/vendor/modules/blog/app/models/blog/wordpress_importer.rb +++ b/vendor/modules/blog/app/models/blog/wordpress_importer.rb @@ -161,9 +161,23 @@ def create_page(item) body = item['encoded'] return if body.blank? - node_path = SiteNode.generate_node_path(item['post_name']) - node_path = '' if node_path == 'home' - SiteVersion.current.root_node.push_subpage(node_path) do |nd, rv| + path = nil + begin + uri = URI.parse(item['link']) + path = uri.path.sub(/^\//, '').sub(/\/$/, '') + rescue + end + + return unless path + + node_path = nil + parent = SiteVersion.current.root_node + path.split('/').each do |node_path| + node_path = '' if node_path == 'home' + parent = parent.push_subpage(node_path) + end + + parent.push_subpage(node_path) do |nd, rv| rv.title = item['title'] # Basic Paragraph rv.push_paragraph(nil, 'html') do |para| diff --git a/vendor/modules/blog/app/views/blog/manage/import.rhtml b/vendor/modules/blog/app/views/blog/manage/import.rhtml index 1a1f0ffd..5c12640c 100644 --- a/vendor/modules/blog/app/views/blog/manage/import.rhtml +++ b/vendor/modules/blog/app/views/blog/manage/import.rhtml @@ -8,7 +8,7 @@ <%= f.header 'Import WordPress Blog' %> <%= f.filemanager_file :wordpress_export_file_id, :label => 'Export file', :description => "Log into your blog. Goto Tools > Export and click Download" %> <%= f.custom_field '', :value => 'Or' %> - <%= f.text_field :wordpress_url, :label => 'URL', :size => 40, :description => "The home page of your word press blog.\n Example: http://.wordpress.com/" %> + <%= f.text_field :wordpress_url, :label => 'URL', :size => 40, :description => "The home page of your word press blog.\n Example: http://.wordpress.com/" %> <%= f.text_field :wordpress_username, :label => 'Username', :size => 15 %> <%= f.password_field :wordpress_password, :label => 'Password', :size => 15 %> <%= f.check_boxes :wordpress_import_settings, [['Import comments', 'comments'], ['Import pages', 'pages']], :label => 'Import settings' %> From 30b28bdbf2c7c7948008b3d72c6f95b2dda93abb Mon Sep 17 00:00:00 2001 From: Doug Youch Date: Wed, 22 Sep 2010 18:42:06 -0400 Subject: [PATCH 5/5] Require the httparty gem. --- config/environment.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/environment.rb b/config/environment.rb index 66d7fac6..04e002e0 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -98,6 +98,7 @@ def webiva_remove_load_paths(file) config.gem 'libxml-ruby', :lib => 'xml' config.gem 'soap4r', :lib => 'soap/soap' config.gem "json" + config.gem "httparty" if RAILS_ENV == 'test' config.gem 'factory_girl',:source => 'http://gemcutter.org'