Skip to content

Commit

Permalink
export/import themes as zip files [#17 state:resolved]
Browse files Browse the repository at this point in the history
  • Loading branch information
Sven Fuchs committed Jul 20, 2008
1 parent 69fb0c6 commit 3364126
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 9 deletions.
27 changes: 27 additions & 0 deletions TODO
Expand Up @@ -165,6 +165,33 @@ apache multisite config http://www.appelsiini.net/2007/6/mephisto-multiple-site-
end


PROJECT ----------------------------------------------------------------------


Website
Design

Documentation
overview-level
tech-level
howtos
Plugins
Themes

Mailinglist
Bugtracker + GitHub












RESOURCES --------------------------------------------------------------------

Themes
Expand Down
1 change: 1 addition & 0 deletions stories/helper.rb
Expand Up @@ -15,6 +15,7 @@
cache_dirs = ActionController::Base.page_cache_directory, Theme.base_dir, Asset.base_dir
cache_dirs.each{|dir| FileUtils.rm_rf dir unless dir.empty? or dir == '/' }
FileUtils.rm_rf Dir[RAILS_ROOT + "/tmp/webrat*"]
# FileUtils.rm_rf Dir[RAILS_ROOT + "/tmp/stories"]

Spec::Runner.configure do |config|
config.include Spec::Story
Expand Down
36 changes: 34 additions & 2 deletions stories/steps/theme.rb
Expand Up @@ -9,6 +9,10 @@
get admin_theme_path(@site, 'a_new_theme')
end

When "the user goes to the admin theme import page" do
get import_admin_themes_path(@site)
end

When "the user fills in the admin theme creation form with valid values" do
fills_in 'name', :with => 'a new theme'
fills_in 'author', :with => 'the author'
Expand All @@ -19,7 +23,18 @@

When "the user fills in the admin theme file creation form with valid values" do
fills_in 'Filename', :with => 'shared/_footer.html.erb'
fills_in 'file[data]', :with => 'this is the new theme template'
fills_in 'file[data]', :with => 'this is the new theme template' # + "some random text #{rand}" * 1024
end

When "the user fills in the form with valid values" do
attaches_file 'theme[file]', RAILS_ROOT + "/tmp/stories/downloads/theme.zip"
end

When "the user downloads the theme" do
clicks_link 'Download'
filename = RAILS_ROOT + "/tmp/stories/downloads/theme.zip"
FileUtils.mkdir_p File.dirname(filename)
File.open(filename, 'wb+') {|f| f.write(response.body) }
end

Then "the user sees the admin theme creation page" do
Expand Down Expand Up @@ -47,12 +62,18 @@
response.should have_form_posting_to(action)
end

Then "the page has a theme import form" do
action = import_admin_themes_path(@site)
response.should have_form_posting_to(action)
end

Then "the page lists the filename of the new theme template" do
response.should have_text(%r(templates/shared/_footer.html.erb))
end

Then "a new theme is saved" do
@theme = Theme.find('a_new_theme', "site-#{@site.id}").should_not be_nil
@theme = Theme.find('a_new_theme', "site-#{@site.id}")
@theme.should_not be_nil
end

Then "the theme was updated" do
Expand All @@ -61,6 +82,17 @@
@theme.author.should == 'the updated author'
end

Then "the theme was deleted" do
@theme = Theme.find('a_new_theme')
@theme.should be_nil
end

Then "the theme was imported" do
@theme = Theme.find('a_new_theme', "site-#{@site.id}")
@theme.should_not be_nil
@theme.name.should == 'a new theme'
end

Then "the theme is selected" do
@site.reload
@site.theme_names.should include('a_new_theme')
Expand Down
18 changes: 17 additions & 1 deletion stories/stories/admin/theme.txt
Expand Up @@ -48,4 +48,20 @@ Story: Managing themes
Then the theme is selected
When the user goes to the url /
Then the page is cached
And the page shows 'this is the new theme template'
And the page shows 'this is the new theme template'

Scenario: An admin exports, deletes and re-imports a theme
GivenScenario: An admin creates a new theme template
When the user goes to the admin theme show page
And the user downloads the theme
And the user goes to the admin theme list page
And the user clicks on 'Uninstall'
Then the theme was deleted
When the user goes to the admin theme import page
Then the page has a theme import form
When the user fills in the form with valid values

And the user clicks the 'Import' button
Then the theme was imported
And the user is redirected to the admin theme list page

36 changes: 34 additions & 2 deletions vendor/engines/adva_cms/app/controllers/admin/themes_controller.rb
@@ -1,9 +1,9 @@
class Admin::ThemesController < Admin::BaseController
layout "admin"

before_filter :set_theme, :only => [:show, :use, :preview, :edit, :update, :destroy]
before_filter :set_theme, :only => [:show, :use, :edit, :update, :destroy, :export]

guards_permissions :theme, :update => [:select, :unselect]
guards_permissions :theme, :update => [:select, :unselect], :show => :export, :create => :import

def index
@themes = @site.themes.find(:all)
Expand Down Expand Up @@ -49,6 +49,26 @@ def destroy
end
end

def import
if request.post?
file = ensure_uploaded_theme_file_saved params[:theme][:file]
p file
if @site.themes.import file
flash.now[:notice] = "The theme has been imported."
redirect_to admin_themes_path
else
flash.now[:error] = "The file could not be imported as a theme."
end
end
end

def export
zip_path = @theme.export!
send_file(zip_path.to_s, :stream => false) rescue raise "Error sending #{zip_path} file"
ensure
FileUtils.rm_r File.dirname(zip_path)
end

def select
@site.theme_names_will_change!
@site.theme_names << params[:id]
Expand Down Expand Up @@ -79,4 +99,16 @@ def set_site
def set_theme
@theme = @site.themes.find(params[:id]) or raise "can not find theme #{params[:id]}"
end

def ensure_uploaded_theme_file_saved(file)
if file.path
file
else
tmp_file = ActionController::UploadedTempfile.new("uploaded-theme")
tmp_file.write file.read
tmp_file.original_path = file.original_path
tmp_file.read # no idea why we need this here, otherwise the zip can't be opened in Theme::import
tmp_file
end
end
end
@@ -1,6 +1,7 @@
<% if @themes.size > 0 %>
<%= link_to "New theme", new_admin_theme_path %>
<%= link_to "New theme", new_admin_theme_path(@site) %> &middot;
<%= link_to "Import theme", import_admin_themes_path(@site) %>

<ul id="themelist">
<%= render :partial => 'theme', :collection => @themes %>
Expand Down
Expand Up @@ -3,4 +3,3 @@
<% form_for :theme, @theme, :url => admin_themes_path(@site) do |f| %>
<%= render :partial => "form", :locals => {:f => f} %>
<% end %>

2 changes: 2 additions & 0 deletions vendor/engines/adva_cms/app/views/admin/themes/show.html.erb
Expand Up @@ -6,4 +6,6 @@
<% content_for :sidebar do %>
<%= render :partial => 'admin/theme_files/files' %>
<h4>Export</h4>
<p><%= link_to 'Download as zip', export_admin_theme_path(@site, @theme.id) %></p>
<% end %>
4 changes: 3 additions & 1 deletion vendor/engines/adva_cms/routes.rb
Expand Up @@ -40,7 +40,9 @@

map.resources :themes, :controller => 'admin/themes',
:path_prefix => 'admin/sites/:site_id',
:name_prefix => 'admin_'
:name_prefix => 'admin_',
:collection => { :import => :any },
:member => { :export => :get }

map.admin_site_selected_themes 'admin/sites/:site_id/themes/selected',
:controller => 'admin/themes',
Expand Down
50 changes: 49 additions & 1 deletion vendor/engines/theme_support/app/models/theme.rb
Expand Up @@ -25,6 +25,17 @@ def create!(attrs)
end
end

def import(file)
import_from_zip(file)
end

def make_tmp_dir
random = Time.now.to_i.to_s.split('').sort_by{rand}
returning Pathname.new(RAILS_ROOT + "/tmp/themes/tmp_#{random}/") do |dir|
FileUtils.mkdir_p dir unless dir.exist?
end
end

def to_id(str)
str.gsub(/[^\w\-_]/, '_').downcase
end
Expand All @@ -45,6 +56,19 @@ def installed_theme_paths(subdir = nil)
pattern = ::File.join *[base_dir, subdir, '[-_a-zA-Z0-9]*'].compact
Dir.glob(pattern).collect { |file| file if ::File.directory?(file) }.compact
end

def import_from_zip(file)
name = file.original_filename.gsub(/(^.*(\\|\/))|(\.zip$)/, '').gsub(/[^\w\.\-]/, '_')
tmp_dir = Theme.make_tmp_dir
Zip::ZipFile.open(file.path) do |zip|
zip.each do |entry|
path = tmp_dir + name + entry.name
FileUtils.mkdir_p ::File.dirname(path)
entry.extract path
end
end
Theme.new :path => tmp_dir + name
end
end

def initialize(attrs = {})
Expand Down Expand Up @@ -82,6 +106,7 @@ def #{attr_name}=(value)
end

def id=(id)
id.gsub! %r([^\w-]), ''
if self.id and self.id != id
self.path = Pathname.new(self.path.to_s.gsub(%r(/#{self.id}$), "/#{id}"))
end
Expand All @@ -93,6 +118,7 @@ def name
end

def name=(name)
name.gsub! %r(/|\\), ''
about['name'] = name
self.id = self.class.to_id(name)
end
Expand All @@ -113,6 +139,10 @@ def find(id)
end
end[templates, assets, others]
end

def about_file
Theme::Other.new self, self.about_filename
end

def templates
@templates ||= Files.new(Theme::Template, self)
Expand Down Expand Up @@ -171,13 +201,21 @@ def validate
@validated = true
end

def export!(options = {})
options.reverse_merge! :as => :zip
send :"export_as_#{options[:as]}", Theme.make_tmp_dir
end

def mkdir
raise ThemeError.new("can't create directory #{@path}") unless @path.to_s =~ %r(^#{Theme.base_dir})
FileUtils.mkdir_p @path.to_s
end

def mv(path)
raise ThemeError.new("can't rename to directory #{path}") unless path.to_s =~ %r(^#{Theme.base_dir})
# TODO this is crap. do not move the theme on create by default even if there's no
# theme path set, yet. Also, took the following check out so we're able to create a theme
# on a tmp directory and move it afterwards
# raise ThemeError.new("can't rename to directory #{path}") unless path.to_s =~ %r(^#{Theme.base_dir})
if @path and path != @path
raise ThemeError.new("can't rename to existing directory #{path}") if ::File.exists?(path)
FileUtils.mv @path.to_s, path.to_s
Expand Down Expand Up @@ -210,6 +248,16 @@ def read_about_file
def write_about_file
::File.open(about_filename, 'wb'){|f| f.write about.to_yaml }
end

def export_as_zip(dir)
returning dir + "#{id}.zip" do |filename|
filename.unlink if filename.exist?
Zip::ZipFile.open filename, Zip::ZipFile::CREATE do |zip|
zip.add('about.yml', about_filename)
files.flatten.each{|file| zip.add(file.localpath.to_s, file.fullpath.to_s) }
end
end
end

class Sanitizer
include ActionView::Helpers::SanitizeHelper
Expand Down
5 changes: 5 additions & 0 deletions vendor/engines/theme_support/app/models/theme/path.rb
Expand Up @@ -99,6 +99,11 @@ def valid_path?
!!(fullpath.to_s =~ /^#{theme.path.to_s}/) && self.class.valid_path?(fullpath.to_s)
end

def mv(path)
FileUtils.mkdir_p ::File.dirname(path)
FileUtils.mv fullpath, path
end

private

def strip_subdir(path)
Expand Down
12 changes: 12 additions & 0 deletions vendor/engines/theme_support/lib/theme_support/active_record.rb
Expand Up @@ -83,6 +83,18 @@ def find(id)
def build(attributes)
Theme.new(attributes.merge :path => fetch(:owner).themes_dir)
end

def import(file)
theme = Theme.import(file)
path = Pathname.new fetch(:owner).themes_dir + Theme.to_id(theme.name)
tmp_path = theme.path
path.rmtree if path.exist? # TODO look for a unique name if this one is already taken?
theme.about_file.mv path + 'about.yml' # TODO maybe make this Theme#mv instead of copying the actual dir?
theme.files.flatten.each do |file|
file.mv path + file.localpath if file.valid?
end
FileUtils.rm_r tmp_path.dirname
end
end[:owner, self]
end

Expand Down

0 comments on commit 3364126

Please sign in to comment.