remove the import/export functionalities (replaced by the API and the new locomotive editor) + upgrade gems (codemirror, ...etc)

This commit is contained in:
did 2012-01-28 12:41:00 +01:00
parent 9f7ac8630e
commit b93c7f8f51
45 changed files with 38 additions and 1875 deletions

View File

@ -18,13 +18,13 @@ gem 'custom_fields', :path => '../gems/custom_fields' # DEV
gem 'kaminari'
gem 'haml', '~> 3.1.3'
gem 'sass-rails', '~> 3.1.4'
gem 'sass-rails', '~> 3.1.5'
gem 'coffee-script', '~> 2.2.0'
gem 'uglifier', '~> 1.0.4'
gem 'compass', '~> 0.12.alpha.4'
gem 'jquery-rails', '~> 1.0.16'
gem 'rails-backbone', '0.5.4'
gem 'codemirror-rails'
gem 'codemirror-rails', '~> 2.21'
gem 'locomotive-tinymce-rails', '~> 3.4.7'
gem 'locomotive-aloha-rails', '~> 0.20.1'
gem 'flash_cookie_session', '~> 1.1.1'
@ -43,10 +43,9 @@ gem 'fog', '~> 1.0.0'
gem 'dragonfly', '~> 0.9.8'
gem 'rack-cache', '~> 1.1', :require => 'rack/cache'
gem 'mimetype-fu', '~> 0.1.2'
gem 'rubyzip'
gem 'actionmailer-with-request', '~> 0.3.0', :require => 'actionmailer_with_request'
gem 'httparty', '~> 0.8.1'
gem 'delayed_job_mongoid', '~> 1.0.8'
# gem 'delayed_job_mongoid', '~> 1.0.8'
gem 'SystemTimer', :platforms => :ruby_18
# The rest of the dependencies are for use when in the locomotive dev environment

View File

@ -75,7 +75,7 @@ GEM
childprocess (0.3.0)
ffi (~> 1.0.6)
chunky_png (1.2.5)
codemirror-rails (2.2)
codemirror-rails (2.21)
railties (~> 3.0)
coffee-script (2.2.0)
coffee-script-source
@ -96,11 +96,6 @@ GEM
cucumber (>= 1.1.3)
nokogiri (>= 1.5.0)
database_cleaner (0.7.1)
delayed_job (3.0.1)
activesupport (~> 3.0)
delayed_job_mongoid (1.0.8)
delayed_job (~> 3.0.0)
mongoid (>= 2.0)
devise (1.5.3)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.0.3)
@ -292,13 +287,12 @@ DEPENDENCIES
capybara
carrierwave-mongoid (~> 0.1.3)
cells (~> 3.8.0)
codemirror-rails
codemirror-rails (~> 2.21)
coffee-script (~> 2.2.0)
compass (~> 0.12.alpha.4)
cucumber-rails
custom_fields!
database_cleaner
delayed_job_mongoid (~> 1.0.8)
devise (~> 1.5.3)
dragonfly (~> 0.9.8)
factory_girl_rails (~> 1.3.0)
@ -329,9 +323,8 @@ DEPENDENCIES
rmagick (= 2.12.2)
rspec-cells
rspec-rails (= 2.6.1)
rubyzip
sanitize (~> 2.0.3)
sass-rails (~> 3.1.4)
sass-rails (~> 3.1.5)
shoulda-matchers
uglifier (~> 1.0.4)
unicorn

View File

@ -5,13 +5,15 @@ h1. Locomotive CMS
Locomotive is a simple but powerful CMS based on liquid templates and mongodb database. At my company ("NoCoffee":http://www.nocoffee.fr), we use it for our clients when they request a simple website.
If we have to give only 5 main features to describe our application, there will be:
If we have to give a couple of features to describe our application, there will be:
* managing as many websites as you want with one application instance
* nice looking UI (see http://www.locomotivecms.com for some screenshots)
* flexible content types
* content localization out of the box
* playing smoothly with Heroku and MongoHQ
* inline editing (beta)
* API
h2. Strategy / Development status
@ -20,16 +22,17 @@ Now, our goal is to port our prototype to Rails 3 and migrate from mongomapper t
h2. Gems
Here is a short list of main gems used in the application.
Here is a short list of main gems / technologies used in the application.
* Rails 3.0.10
* Mongoid 2.0.2 (with MongoDB 1.8)
* Rails 3.1 (3.2 soon)
* Mongoid 2.4.2 (with MongoDB 2.0)
* Liquid
* Devise
* Carrierwave
* Haml
* Delayed job
* Jammit-s3
* Formtastic
* Cells
* Coffeescript / Backbone / SASS
h2. Installation
@ -61,7 +64,7 @@ For new features (especially large ones) it is best to create a topic on the "di
h2. Team
* Developers: "Didier Lafforgue":http://www.nocoffee.fr, "Jacques Crocker":http://www.railsjedi.com, "Mario Visic":http://www.mariovisic.com
* Developers: "Didier Lafforgue":http://www.nocoffee.fr, "Mario Visic":http://www.mariovisic.com, "Jacques Crocker":http://www.railsjedi.com
* Contributors: "Dirk Kelly":http://www.dirkkelly.com, "Raphael Costa":http://raphaelcosta.net (Brazilian Portuguese translation), "Bernd Hauser":http://www.designhunger.de (German translation), "Andrea Frigido":http://www.frisoft.it (Italian translation), "Enrique García":https://github.com/kikito (Spanish translation), "Lars Smit":https://github.com/larssmit (Dutch translation), "PitOn":https://github.com/GarPit (Russian translation), "paulsponagl":https://github.com/paulsponagl
* UI Designer: "Sacha Greif":http://www.sachagreif.com
* IE maintainer: "Alex Sanford":https://github.com/alexsanford
@ -80,5 +83,5 @@ h2. Contact
Feel free to contact me at didier at nocoffee dot fr.
Copyright (c) 2011 NoCoffee, released under the MIT license
Copyright (c) 2012 NoCoffee, released under the MIT license
...

View File

@ -48,8 +48,6 @@ task :spec_nix do
lib/locomotive/liquid/filters/text_spec.rb
lib/locomotive/liquid/filters/misc_spec.rb
lib/locomotive/heroku_spec.rb
lib/locomotive/import_spec.rb
lib/locomotive/export_spec.rb
models/content_entry_spec.rb
models/editable_element_spec.rb
models/account_spec.rb

View File

@ -1,13 +0,0 @@
Locomotive.Views.Import ||= {}
class Locomotive.Views.Import.NewView extends Backbone.View
el: '#content'
render: ->
super()
@enable_checkboxes()
enable_checkboxes: ->
@$('.formtastic input[type=checkbox]').checkToggle()

View File

@ -1,46 +0,0 @@
Locomotive.Views.Import ||= {}
class Locomotive.Views.Import.ShowView extends Backbone.View
el: '#content'
render: ->
super
@enable_updating()
enable_updating: ->
@updater = @$('#import-steps').smartupdater
url : @$('#import-steps').attr('data-url')
dataType: 'json'
minTimeout: 300,
@refresh_steps
refresh_steps: (data) =>
window.foo = data
window.bar = @
if data.step == 'done'
$('#import-steps li').addClass('done')
$.growl 'notice', @$('#import-steps').attr('data-success-message')
else
steps = ['site', 'content_types', 'assets', 'snippets', 'pages']
current_index = steps.indexOf(data.step) || 0
_.each steps, (step, index) =>
state = null
if index == current_index + 1 && data.failed then state = 'failed'
if (index <= current_index) then state = 'done'
@$("#import-steps li:eq(#{index})").addClass(state) if state?
if data.failed
$.growl 'alert', @$('#import-steps').attr('data-failure-message')
if data.step == 'done' || data.failed
@updater.smartupdater('stop')
remove: ->
super
@updater.smartupdater('stop') if @updater?

View File

@ -7,7 +7,7 @@
*= require locomotive/jquery
*= require formtastic
*= require codemirror
*= require codemirror/themes/default
*= require codemirror/themes/neat
*= require locomotive/toggle.css
*= require locomotive/liquid_mode.css
*= require_tree ./locomotive/shared

View File

@ -292,39 +292,6 @@ ul.list {
}
}
/* ___ import steps ___ */
#import-steps {
margin: 0px 200px;
li {
strong {
margin-left: 18px;
color: #b7baca;
}
.more .states {
position: relative;
top: 7px;
height: 16px;
width: 16px;
background: transparent image-url("locomotive/list/icons/states.png") no-repeat 0 0;
}
&.done {
strong { color: #1F82BC; }
.more .states {
background-position: 0 -16px;
}
}
&.failed .more .states {
background-position: 0 -32px;
}
}
}
/* ___ paragraph (for help for example) ___ */
p span.code {

View File

@ -1,20 +0,0 @@
module Locomotive
class ExportController < BaseController
skip_load_and_authorize_resource
before_filter :authorize_export
def new
zipfile = ::Locomotive::Export.run!(current_site, current_site.name.parameterize)
send_file zipfile, :type => 'application/zip', :disposition => 'attachment'
end
protected
def authorize_export
authorize! :export, Site
end
end
end

View File

@ -1,34 +0,0 @@
module Locomotive
class ImportController < BaseController
sections 'settings', 'site'
skip_load_and_authorize_resource
before_filter :authorize_import
respond_to :json, :only => :show
def show
@import = Locomotive::Import::Model.current(current_site)
respond_with @import
end
def new
@import = Locomotive::Import::Model.new
respond_with @import
end
def create
@import = Locomotive::Import::Model.create(params[:import].merge(:site => current_site))
respond_with @import, :location => Locomotive.config.delayed_job ? import_url : new_import_url
end
protected
def authorize_import
authorize! :import, Site
end
end
end

View File

@ -56,10 +56,6 @@ module Locomotive
site == @site
end
can :import, Site
can :export, Site
can :point, Site
cannot :create, Site

View File

@ -4,9 +4,6 @@
= render_cell 'locomotive/settings_menu', :show
- content_for :buttons do
- if can?(:manage, @site)
= local_action_button :export, new_export_url, :class => 'new'
= local_action_button :import, new_import_url, :class => 'new'
- if can?(:create, Locomotive::Account)
= local_action_button t('.new_membership'), new_membership_url, :class => 'new'

View File

@ -1,15 +0,0 @@
- title t('.title')
- content_for :submenu do
= render_cell 'locomotive/settings_menu', :show
%p!= t('.help')
= semantic_form_for @import, :as => 'import', :url => import_url, :html => { :multipart => true } do |f|
= f.inputs :name => :upload do
= f.input :source, :as => 'file'
= f.input :samples, :as => :'Locomotive::Toggle'
= f.input :reset, :as => :'Locomotive::Toggle'
= render 'locomotive/shared/form_actions', :back_url => edit_current_site_url, :button_label => :create

View File

@ -1,14 +0,0 @@
- title t('.title')
- content_for :submenu do
= render_cell 'locomotive/settings_menu', :show
%p!= t('.help')
%ul{ :id => 'import-steps', :class => 'list', :'data-url' => import_url(:json), :'data-success-message' => t('.messages.success'), :'data-failure-message' => t('.messages.failure') }
- %w(site content_types assets snippets pages).each do |step|
%li.item{ :id => "#{step}-step" }
%strong= t(".steps.#{step}")
.more
.states
&nbsp;

View File

@ -159,8 +159,6 @@ en:
current_site:
edit:
export: export
import: import
new_membership: add account
help: "The site name can be updated by clicking it. To apply your changes, click on the \"Save\" button."
ask_for_name: "Please type the new site name"
@ -271,23 +269,6 @@ en:
title: Cross-domain authentication
notice: You will be redirected to the website in a couple of seconds.
import:
new:
title: Import site template
help: "Be careful when you upload a new template for your existing website, your current data could be modified or even removed."
show:
title: Import in progress
help: "Your site is being updated from the theme zip file you have just uploaded. It lasts a couple of seconds."
steps:
site: Site information
content_types: Custom content types
assets: Theme files
snippets: Snippets
pages: Pages
messages:
success: "Your site was successfully updated."
failure: "The import did not work."
installation:
common:
title: First Locomotive Installation

View File

@ -150,8 +150,6 @@ fr:
current_site:
edit:
export: exporter
import: importer
new_membership: ajouter compte
help: "Le nom du site est modifiable en cliquant dessus. Pour appliquer votre modification, cliquez après sur le bouton \"Modifier\""
ask_for_name: "Veuillez entrer le nouveau nom"
@ -261,23 +259,6 @@ fr:
title: Transfert vers un autre site
notice: Vous allez être redirigé(e) vers le site dans quelques secondes.
import:
new:
title: Importer template de site
help: "Faites attention quand vous envoyez un nouveau template sur votre site, les données de celui-ci pourront être modifiées voire même supprimées."
show:
title: Import en cours
help: "Votre site est en train d'être mis à jour à partir du fichier zip précédemment envoyé sur le serveur. Cette opération peut durer quelques secondes."
steps:
site: Informations sur le site
content_types: Modèles de données personnalisés
assets: Fichiers du thème
snippets: Snippets
pages: Pages
messages:
success: "Votre site a été mis à jour avec succès."
failure: "L'import n'a pas fonctionné."
installation:
common:
title: Première installation de Locomotive

View File

@ -46,10 +46,6 @@ Locomotive::Engine.routes.draw do
resources :cross_domain_sessions, :only => [:new, :create]
resource :import, :only => [:new, :show, :create], :controller => 'import'
resource :export, :only => [:new], :controller => 'export'
# installation guide
match '/installation' => 'installation#show', :defaults => { :step => 1 }, :as => :installation
match '/installation/:step' => 'installation#show', :as => :installation_step

View File

@ -82,6 +82,7 @@ x edit my site
x notification (growl)
x change page
x i18n
- style the "ADMIN" button
- aloha:
x remove sidebar
- i18n
@ -105,22 +106,21 @@ x script to migrate existing site
x i18n
- heroku module for locomotive
- refactoring
- remove the import / export scripts
- remove the cross domain authentication (use auth_token instead)
- remove the import / export scripts
- where to put Locomotive::InlineEditorMiddleware ?
- upgrade to rails 3.2 (https://github.com/locomotivecms/engine/pull/281/files)
- bugs:
- bugs / ui tweaks
x unable to toggle the "required" check_boxes for content types
x unable to sign out
x unable to remove a field
x "back to admin" link does not work if inline editor disabled
- display by categories does not work when localized
- disallow to click twice on the submit form button (spinner ?)
- message to notify people if their browser is too old
- install a site by default at the first installation (without asking)
- where to put Locomotive::InlineEditorMiddleware ?
? install a site by default at the first installation (without asking)
BACKLOG:

View File

@ -15,8 +15,7 @@ Background:
Scenario: Accessing site settings as an Admin
Given I am an authenticated "admin"
When I go to site settings
Then I should see "import"
And I should see "add account"
Then I should see "add account"
And I should see "SEO settings"
And I should see "Access points"
And I should not see the role dropdown on myself
@ -29,8 +28,7 @@ Background:
Scenario: Accessing site settings as a Designer
Given I am an authenticated "designer"
When I go to site settings
Then I should see "import"
And I should not see "add account"
Then I should not see "add account"
And I should see "SEO settings"
And I should see "Access points"
And I should not see the role dropdown on myself
@ -43,8 +41,7 @@ Background:
Scenario: Accessing site settings as an Author
Given I am an authenticated "author"
When I go to site settings
Then I should not see "import"
And I should not see "add account"
Then I should not see "add account"
And I should see "SEO settings"
And I should not see "Access points"
And I should not see "Accounts"

View File

@ -1,29 +0,0 @@
Feature: Exporting a Site
In order to use the site outside the current locomotive instance
As an admin, designer, or author
I will be restricted based on my role
Background:
Given I have the site: "test site" set up
And I have a designer and an author
Scenario: As an unauthenticated user
Given I am not authenticated
When I go to export page
Then I should see "Log in"
Scenario: Accessing exporting functionality as an Admin
Given I am an authenticated "admin"
When I go to the export page
Then I should get a download with the filename "locomotive-test-website.zip"
Scenario: Accessing exporting functionality as a Designer
Given I am an authenticated "designer"
When I go to the export page
Then I should get a download with the filename "locomotive-test-website.zip"
Scenario: Accessing exporting functionality as an Author
Given I am an authenticated "author"
When I go to the export page
Then I should be on the pages list
And I should see the access denied message

View File

@ -1,31 +0,0 @@
Feature: Importing a Site
In order to populate the site with data and assets
As an admin, designer, or author
I will be restricted based on my role
Background:
Given I have the site: "test site" set up
And I have a designer and an author
Scenario: As an unauthenticated user
Given I am not authenticated
When I go to import page
Then I should see "Log in"
Scenario: Accessing importing functionality as an Admin
Given I am an authenticated "admin"
When I go to the import page
Then I should see "Import"
And I should see "Upload"
Scenario: Accessing importing functionality as a Designer
Given I am an authenticated "designer"
When I go to the import page
Then I should see "Import"
And I should see "Upload"
Scenario: Accessing importing functionality as an Author
Given I am an authenticated "author"
When I go to the import page
Then I should be on the pages list
And I should see the access denied message

View File

@ -29,10 +29,6 @@ module NavigationHelpers
edit_locomotive_current_site_path
when /account settings/
edit_locomotive_my_account_path
when /import page/
new_locomotive_import_path
when /export page/
new_locomotive_export_path
when /the "(.*)" model list page/
content_type = Locomotive::Site.first.content_types.where(:name => $1).first
locomotive_contents_path(content_type.slug)

View File

@ -36,13 +36,6 @@ Locomotive.configure do |config|
# :password => '<your_heroku_password>'
# }
# Locomotive uses the DelayedJob gem for the site import module.
# In case you want to deploy to Heroku, you will have to pay for an extra dyno.
# If you do not mind about importing theme without DelayedJob, disable it.
#
# Warning: this option is not used if you deploy on bushi.do and we set automatically the value to true.
config.delayed_job = false
# configure how many items we display in sub menu in the "Contents" section.
# config.ui = {
# :latest_entries_nb => 5,

View File

@ -16,9 +16,6 @@ require 'locomotive/action_controller'
require 'locomotive/routing'
require 'locomotive/regexps'
require 'locomotive/render'
require 'locomotive/import'
require 'locomotive/export'
require 'locomotive/delayed_job'
require 'locomotive/middlewares'
require 'locomotive/session_store'

View File

@ -16,8 +16,6 @@ require 'mimetype_fu'
require 'actionmailer_with_request'
require 'httparty'
require 'redcloth'
require 'delayed_job_mongoid'
require 'zip/zipfilesystem'
require 'dragonfly'
require 'cancan'
require 'RMagick'

View File

@ -1,347 +0,0 @@
require 'fileutils'
require 'zip/zip'
# Just a simple helper class to export quickly an existing and live locomotive website.
# FIXME: will be replaced by the API in the next months
module Locomotive
class Export
@@instance = nil
def initialize(site, filename = nil)
@site = site
@filename = filename || Time.now.to_i.to_s
@target_folder = File.join(Rails.root, 'tmp', 'export', @filename)
@site_hash = {} # used to generate the site.yml and compiled_site.yml files
self.create_target_folder
end
def run!
self.initialize_site_hash
self.log('copying content assets')
self.copy_content_assets
self.log('copying theme assets')
self.copy_theme_assets
self.log('copying pages')
self.copy_pages
self.log('copying snippets')
self.copy_snippets
self.log('copying content types')
self.copy_content_types
self.log('copying config files')
self.copy_config_files
self.log('generating the zip file')
self.zip_it!
end
# returns the path to the zipfile
def self.run!(site, filename = nil)
@@instance = self.new(site, filename)
@@instance.run!
end
protected
def zip_it!
"#{@target_folder}.zip".tap do |dst|
FileUtils.rm(dst, :force => true)
::Zip::ZipFile.open(dst, ::Zip::ZipFile::CREATE) do |zipfile|
Dir[File.join(@target_folder, '**/*')].each do |file|
entry = file.gsub(@target_folder + '/', '')
zipfile.add(entry, file)
end
end
end
end
def create_target_folder
FileUtils.rm_rf(@target_folder)
FileUtils.mkdir_p(@target_folder)
%w(app/content_types app/views/snippets app/views/pages config data public).each do |f|
FileUtils.mkdir_p(File.join(@target_folder, f))
end
end
def initialize_site_hash
attributes = self.extract_attributes(@site, %w(name locale seo_title meta_description meta_keywords))
attributes['pages'] = []
attributes['content_types'] = {}
@site_hash = { 'site' => attributes }
end
def copy_config_files
File.open(File.join(self.config_folder, 'compiled_site.yml'), 'w') do |f|
f.write(yaml(@site_hash))
end
@site_hash['site'].delete('content_types')
File.open(File.join(self.config_folder, 'site.yml'), 'w') do |f|
f.write(yaml(@site_hash))
end
end
def copy_pages
Page.quick_tree(@site, false).each { |p| self._copy_pages(p) }
end
def copy_snippets
@site.snippets.each do |snippet|
File.open(File.join(self.snippets_folder, "#{snippet.slug}.liquid"), 'w') do |f|
f.write(snippet.template)
end
end
end
def copy_content_types
@site.content_types.each do |content_type|
attributes = self.extract_attributes(content_type, %w(name description slug order_by order_direction group_by_field_name api_enabled))
attributes['highlighted_field_name'] = content_type.highlighted_field.name
# TODO: refactoring
# custom_fields
fields = []
content_type.entries_custom_fields.each do |field|
field_attributes = self.extract_attributes(field, %w(label type hint required))
if field.target.present?
target_klass = field['target'].constantize
field_attributes['target'] = target_klass._parent.slug
if field['reverse_lookup'].present?
field_attributes['reverse'] = target_klass.custom_field_name_to_alias(field['reverse_lookup'])
end
end
fields << { field.name => field_attributes }
end
attributes['fields'] = fields
@site_hash['site']['content_types'][content_type.name] = attributes.clone
# [structure] copy it into its own file
File.open(File.join(self.content_types_folder, "#{content_type.slug}.yml"), 'w') do |f|
f.write(self.yaml(attributes))
end
# data
data = self.extract_contents(content_type)
# [data] copy them into their own file
File.open(File.join(self.content_data_folder, "#{content_type.slug}.yml"), 'w') do |f|
f.write(self.yaml(data))
end
@site_hash['site']['content_types'][content_type.name]['contents'] = data
end
end
def copy_theme_assets
@site.theme_assets.each do |theme_asset|
target_path = File.join(self.public_folder, theme_asset.local_path)
self.copy_file_from_an_uploader(theme_asset.source, target_path) do |bytes|
if theme_asset.stylesheet_or_javascript?
base_url = theme_asset.source.url.gsub(theme_asset.local_path, '')
bytes.gsub(base_url, '/')
else
bytes
end
end
end
end
def copy_content_assets
@site.content_assets.each do |content_asset|
target_path = File.join(self.samples_folder, content_asset.source_filename)
self.copy_file_from_an_uploader(content_asset.source, target_path)
end
end
protected
def _copy_pages(page)
attributes = self.extract_attributes(page, %w{title seo_title meta_description meta_keywords redirect_url content_type published})
attributes['listed'] = page.listed? # in some cases, page.listed can be nil
unless page.raw_template.blank?
attributes.delete('redirect_url')
if page.templatized?
attributes['content_type'] = page.content_type.slug
end
# add editable elements
page.editable_elements.each do |element|
next if element.disabled? || (element.from_parent && element.default_content == element.content)
el_attributes = self.extract_attributes(element, %w{slug block hint})
case element
when EditableShortText, EditableLongText
el_attributes['content'] = self.replace_asset_urls_in(element.content || '')
when EditableFile
unless element.source_filename.blank?
filepath = File.join('/', 'samples', element.source_filename)
self.copy_file_from_an_uploader(element.source, File.join(self.public_folder, filepath))
el_attributes['content'] = filepath
else
el_attributes['content'] = '' # not sure if we run into this
end
end
(attributes['editable_elements'] ||= []) << el_attributes
end
page_templatepath = File.join(self.pages_folder, "#{page.fullpath}.liquid")
FileUtils.mkdir_p(File.dirname(page_templatepath))
File.open(page_templatepath, 'w') do |f|
f.write(self.replace_asset_urls_in(page.raw_template))
end
end
@site_hash['site']['pages'] << { page.fullpath => attributes }
page.children.each { |p| self._copy_pages(p) }
end
def extract_attributes(object, fields)
attributes = object.attributes.select { |k, v| fields.include?(k) && !v.blank? }
if RUBY_VERSION =~ /1\.9/
attributes
else
attributes.inject({}) { |memo, pair| memo.merge(pair.first => pair.last) }
end
end
def pages_folder
File.join(@target_folder, 'app', 'views', 'pages')
end
def snippets_folder
File.join(@target_folder, 'app', 'views', 'snippets')
end
def content_types_folder
File.join(@target_folder, 'app', 'content_types')
end
def config_folder
File.join(@target_folder, 'config')
end
def content_data_folder
File.join(@target_folder, 'data')
end
def public_folder
File.join(@target_folder, 'public')
end
def samples_folder
File.join(@target_folder, 'public', 'samples')
end
def copy_file_from_an_uploader(uploader, target_path, &block)
FileUtils.mkdir_p(File.dirname(target_path))
File.open(target_path, 'w') do |f|
bytes = uploader.read
bytes ||= ''
bytes = block.call(bytes) if block_given?
bytes = bytes.force_encoding('UTF-8') if RUBY_VERSION =~ /1\.9/
f.write(bytes)
end
end
def extract_contents(content_type)
data = []
# TODO (refactoring....)
highlighted_field_name = content_type.highlighted_field_name
content_type.entries.each do |content|
hash = {}
content.custom_fields.each do |field|
next if field._name == highlighted_field_name
value = (case field.type
when 'file'
uploader = content.send(field._name)
unless uploader.blank?
filepath = File.join('/samples', content_type.slug, content.send("#{field._name}_filename"))
self.copy_file_from_an_uploader(uploader, File.join(self.public_folder, filepath))
else
filepath = nil
end
filepath
when 'text'
self.replace_asset_urls_in(content.send(field._name.to_sym) || '')
when 'has_one'
content.send(field.name.to_sym).highlighted_field_value rescue nil # no bound object
when 'has_many'
unless field.reverse_has_many?
content.send(field.name.to_sym).collect(&:highlighted_field_value)
end
else
content.send(field.name.to_sym)
end)
hash[field.name] = value if value.present? || value == false # False values should be preserved in the export
end
data << { content.highlighted_field_value => hash }
end
data
end
def replace_asset_urls_in(text)
base_url = ContentAssetUploader.new(ContentAsset.new(:site => @site)).store_dir
(base_url = base_url.split('/')).pop
base_url = base_url.join('/')
text.gsub(%r(#{base_url}/[a-z0-9]{10,}/), "#{base_url.starts_with?('http') ? '/' : ''}samples/")
end
def yaml(hash_or_array)
method = hash_or_array.respond_to?(:ya2yaml) ? :ya2yaml : :to_yaml
string = (if hash_or_array.respond_to?(:keys)
hash_or_array.dup.stringify_keys!
else
hash_or_array
end).send(method)
string.gsub('!ruby/symbol ', ':').sub('---', '').split("\n").map(&:rstrip).join("\n").strip
end
def log(message, domain = '')
head = "[export_template][#{@site.name}]"
head += "[#{domain}]" unless domain.blank?
::Locomotive.log "\t#{head} #{message}"
end
end
end

View File

@ -1,15 +0,0 @@
require 'locomotive/import/logger'
require 'locomotive/import/base'
require 'locomotive/import/model'
require 'locomotive/import/job'
require 'locomotive/import/site'
require 'locomotive/import/assets'
require 'locomotive/import/content_types'
require 'locomotive/import/snippets'
require 'locomotive/import/pages'
module Locomotive
module Import
DEFAULT_SITE_TEMPLATE = 'https://github.com/locomotivecms/default-site-template/zipball/master'
end
end

View File

@ -1,59 +0,0 @@
module Locomotive
module Import
class Assets < Base
def process
self.add_theme_assets
self.add_content_assets
end
protected
def add_theme_assets
%w(images media fonts javascripts stylesheets).each do |type|
Dir[File.join(theme_path, 'public', type, '**/*')].each do |asset_path|
next if File.directory?(asset_path)
folder = asset_path.gsub(File.join(theme_path, 'public'), '').gsub(File.basename(asset_path), '').gsub(/^\//, '').gsub(/\/$/, '')
asset = site.theme_assets.where(:local_path => File.join(folder, File.basename(asset_path))).first
asset ||= site.theme_assets.build(:folder => folder)
asset.attributes = { :source => File.open(asset_path), :performing_plain_text => false }
begin
asset.save!
rescue Exception => e
self.log "!ERROR! = #{e.message}, #{asset_path}"
end
end
end
end
def add_content_assets
Dir[File.join(theme_path, 'public', 'samples', '*')].each do |asset_path|
next if File.directory?(asset_path)
self.log "other asset = #{asset_path}"
asset = site.content_assets.where(:source_filename => File.basename(asset_path)).first
asset ||= self.site.content_assets.build
asset.attributes = { :source => File.open(asset_path) }
begin
asset.save!
rescue Exception => e
self.log "!ERROR! = #{e.message}, #{asset_path}"
end
end
end
end
end
end

View File

@ -1,46 +0,0 @@
module Locomotive
module Import
class Base
include Logger
attr_reader :context, :options
def initialize(context, options)
@context = context
@options = options
self.log "*** starting to process ***"
end
def self.process(context, options)
self.new(context, options).process
end
def process
raise 'this method has to be overidden'
end
def log(message)
super(message, self.class.name.demodulize.underscore)
end
protected
def site
@context[:site]
end
def database
@context[:database]
end
def theme_path
@context[:theme_path]
end
def open_sample_asset(url)
File.open(File.join(self.theme_path, 'public', url))
end
end
end
end

View File

@ -1,245 +0,0 @@
require 'ostruct'
module Locomotive
module Import
class ContentTypes < Base
def process
return if content_types.nil?
contents_with_associations, content_types_with_associations = [], []
content_types.each do |name, attributes|
self.log "[content_types] slug = #{attributes['slug']}"
content_type = site.content_types.where(:slug => attributes['slug']).first
content_type_name = attributes['name'] || name
if content_type.nil?
content_type = self.build_content_type(attributes.merge(:name => content_type_name))
else
self.update_attributes(content_type, attributes.merge(:name => content_type_name))
end
self.add_or_update_fields(content_type, attributes['fields'])
if content_type.entries_custom_fields.any? { |f| ['has_many', 'has_one'].include?(f.type) }
content_types_with_associations << content_type
end
self.set_highlighted_field_name(content_type)
self.set_order_by_value(content_type)
self.set_group_by_value(content_type)
if options[:samples] && attributes['contents']
contents_with_associations += self.insert_samples(content_type, attributes['contents'])
end
content_type.save!
end
# look up for associations and replace their target field by the real class name
self.replace_target(content_types_with_associations)
# update all the contents with associations now that every content is stored in mongodb
self.insert_samples_with_associations(contents_with_associations)
# invalidate the cache of the dynamic classes (custom fields)
site.content_types.all.collect { |c| c.invalidate_content_klass; c.fetch_content_klass }
end
protected
def content_types
database['site']['content_types']
end
def cleanse_attributes(data)
attributes = { :group_by_field_name => data.delete('group_by') }.merge(data)
attributes.delete_if { |name, value| %w{fields contents}.include?(name) }
attributes
end
def build_content_type(data)
attributes = cleanse_attributes(data)
site.content_types.build(attributes)
end
def update_attributes(content_type, data)
attributes = cleanse_attributes(data)
content_type.update_attributes!(attributes)
end
def add_or_update_fields(content_type, fields)
fields.each_with_index do |data, position|
name, data = data.keys.first, data.values.first
reverse_lookup = data.delete('reverse')
attributes = { :name => name, :label => name.humanize, :type => 'string', :position => position }.merge(data).symbolize_keys
field = content_type.entries_custom_fields.detect { |f| f.name == attributes[:name] }
field ||= content_type.entries_custom_fields.build(attributes)
field.send(:set_unique_name!) if field.new_record?
field.attributes = attributes
field[:type] = field[:type].downcase # old versions of the kind field are capitalized
field[:tmp_reverse_lookup] = reverse_lookup # use the ability in mongoid to set free attributes on the fly
end
end
def replace_target(content_types)
content_types.each do |content_type|
content_type.entries_custom_fields.each do |field|
next unless ['has_many', 'has_one'].include?(field.type)
target_content_type = site.content_types.where(:slug => field.target).first
if target_content_type
field.target = target_content_type.content_klass.to_s
if field[:tmp_reverse_lookup]
field.reverse_lookup = field[:tmp_reverse_lookup]
field.reverse_lookup = field.safe_reverse_lookup # make sure we store the true value
end
end
end
content_type.save
end
end
def insert_samples(content_type, contents)
contents_with_associations = []
contents.each_with_index do |data, position|
value, attributes = data.is_a?(Array) ? [data.first, data.last] : [data.keys.first, data.values.first]
associations = []
# TODO (needs refactoring)
# build with default attributes
content = content_type.contents.where(content_type.highlighted_field_name.to_sym => value).first
if content.nil?
content = content_type.contents.build(content_type.highlighted_field_name.to_sym => value, :_position => position)
end
%w(_permalink seo_title meta_description meta_keywords).each do |attribute|
new_value = attributes.delete(attribute)
next if new_value.blank?
content.send("#{attribute}=".to_sym, new_value)
end
attributes.each do |name, value|
field = content_type.entries_custom_fields.detect { |f| f.name == name }
next if field.nil? # the attribute name is not related to a field (name misspelled ?)
type = field.type
if ['has_many', 'has_one'].include?(type)
associations << OpenStruct.new(:name => name, :type => type, :value => value, :target => field.target)
next
end
value = (case type
when 'file' then self.open_sample_asset(value)
when 'boolean' then Boolean.set(value)
when 'date' then value.is_a?(Date) ? value : Date.parse(value)
when 'select'
if field.select_options.detect { |item| item.name == value }.nil?
field.select_options.build :name => value
end
value
else # string, text
value
end)
content.send("#{name}=", value)
end
content.send(:set_slug)
content.save(:validate => false)
contents_with_associations << [content, associations] unless associations.empty?
self.log "insert content '#{content.send(content_type.highlighted_field_name.to_sym)}'"
end
contents_with_associations
end
def insert_samples_with_associations(contents)
contents.each do |content_information|
next if content_information.empty?
content, associations = content_information
content = content._parent.reload.contents.find(content._id) # target should be updated
associations.each do |association|
target_content_type = site.content_types.where(:slug => association.target).first
next if target_content_type.nil?
value = (case association.type
when 'has_one' then
target_content_type.contents.detect { |c| c.highlighted_field_value == association.value }
when 'has_many' then
association.value.collect do |v|
target_content_type.contents.detect { |c| c.highlighted_field_value == v }._id
end
end)
content.send("#{association.name}=", value)
end
content.save
end
end
def set_highlighted_field_name(content_type)
field = content_type.entries_custom_fields.detect { |f| f.name == content_type.highlighted_field_name.to_s }
content_type.highlighted_field_name = field._name if field
end
def set_order_by_value(content_type)
self.log "order by #{content_type.order_by}"
order_by = (case content_type.order_by
when 'manually', '_position_in_list', '_position' then '_position'
when 'default', 'created_at' then 'created_at'
else
content_type.entries_custom_fields.detect { |f| f.name == content_type.order_by }._name rescue nil
end)
self.log "order by (after) #{order_by}"
content_type.order_by = order_by || '_position'
end
def set_group_by_value(content_type)
return if content_type.group_by_field_name.blank?
field = content_type.entries_custom_fields.detect { |f| f.name == content_type.group_by_field_name }
content_type.group_by_field_name = field._name if field
end
end
end
end

View File

@ -1,184 +0,0 @@
require 'zip/zipfilesystem'
module Locomotive
module Import
class Job
include Logger
def initialize(zipfile, site, options = {})
@site_id = site._id.to_s
@options = {
:reset => false,
:samples => false,
:enabled => {}
}.merge(options)
@identifier = self.store_zipfile(zipfile)
raise "Theme identifier not found" if @identifier.blank?
# empty instance variables before serialization (issue with ruby 1.9.2)
@uploader = @site = nil
end
def before(worker)
@worker = worker
end
def site
@site ||= Locomotive::Site.find(@site_id)
end
def perform
self.log "theme identifier #{@identifier} / site_id #{@site_id}"
self.unzip!
raise "No config/compiled_site.yml found in the theme zipfile" if @database.nil?
context = {
:database => @database,
:site => self.site,
:theme_path => @theme_path,
:error => nil,
:worker => @worker
}
self.reset! if @options[:reset]
# %w(site content_types assets snippets pages).each do |step|
%w(site assets snippets pages).each do |step| # TODO (Did): unable content_types for now, waiting for its refactoring
if @options[:enabled][step] != false
"Locomotive::Import::#{step.camelize}".constantize.process(context, @options)
@worker.update_attributes :step => step if @worker
else
self.log "skipping #{step}"
end
end
end
def success(worker)
self.log 'deleting original zip file'
uploader = self.get_uploader(self.site)
uploader.retrieve_from_store!(@identifier)
uploader.remove!
self.log 'deleting working folder'
FileUtils.rm_rf(themes_folder) rescue nil
end
def self.run!(zipfile, site, options = {})
job = self.new(zipfile, site, options)
if Locomotive.config.delayed_job
Delayed::Job.enqueue job, { :site_id => site._id, :job_type => 'import' }
else
job.perform
end
end
protected
def themes_folder
File.join(Rails.root, 'tmp', 'themes', self.site._id.to_s)
end
def prepare_folder
FileUtils.rm_rf self.themes_folder if File.exists?(self.themes_folder)
FileUtils.mkdir_p(self.themes_folder)
end
def store_zipfile(zipfile)
return nil if zipfile.blank?
uploader = self.get_uploader(self.site)
begin
if zipfile.is_a?(String) && zipfile =~ /^https?:\/\//
uploader.download!(zipfile)
uploader.store!
else
file = ::CarrierWave::SanitizedFile.new(zipfile)
uploader.store!(file)
end
uploader.identifier
rescue ::CarrierWave::IntegrityError
nil
end
end
def retrieve_zipfile
uploader = self.get_uploader(self.site)
uploader.retrieve_from_store!(@identifier)
if uploader.file.respond_to?(:url)
self.log 'file from remote storage'
@theme_file = File.join(self.themes_folder, @identifier)
if RUBY_VERSION =~ /1\.9/
bytes = uploader.file.read.force_encoding('UTF-8')
else
bytes = uploader.file.read
end
File.open(@theme_file, 'w') { |f| f.write(bytes) }
else # local filesystem
self.log 'file from local storage'
@theme_file = uploader.path
end
end
def unzip!
self.prepare_folder
self.retrieve_zipfile
self.log "unzip #{@theme_file}"
Zip::ZipFile.open(@theme_file) do |zipfile|
destination_path = self.themes_folder
zipfile.each do |entry|
next if entry.name =~ /__MACOSX/
if entry.name =~ /config\/compiled_site.yml$/
@database = YAML.load(zipfile.read(entry.name))
@theme_path = File.join(destination_path, entry.name.gsub('config/compiled_site.yml', ''))
next
end
FileUtils.mkdir_p(File.dirname(File.join(destination_path, entry.name)))
zipfile.extract(entry, File.join(destination_path, entry.name))
end
end
end
def reset!
self.site.pages.destroy_all
self.site.content_assets.destroy_all
self.site.theme_assets.destroy_all
self.site.content_types.destroy_all
end
def get_uploader(site)
unless Locomotive.config.delayed_job
ThemeUploader.storage = :file
end
@uploader ||= ThemeUploader.new(site)
end
end
end
end

View File

@ -1,13 +0,0 @@
module Locomotive
module Import
module Logger
def log(message, domain = '')
head = "[import_template]"
head += "[#{domain}]" unless domain.blank?
::Locomotive.log "\t#{head} #{message}"
end
end
end
end

View File

@ -1,78 +0,0 @@
module Locomotive
module Import
class Model
include Logger
include ActiveModel::Validations
## fields ##
attr_accessor :site, :source, :reset, :samples, :enabled
## validation ##
validates :site, :source, :presence => true
def initialize(attributes = {})
attributes = HashWithIndifferentAccess.new(attributes)
self.site = attributes[:site]
self.source = attributes[:source]
self.reset = attributes[:reset] || false
self.samples = attributes[:samples] || false
self.enabled = attributes[:enabled] || {}
end
def reset=(value)
@reset = Boolean.set(value)
end
def samples=(value)
@samples = Boolean.set(value)
end
## methods ##
def to_job
new Job()
end
def to_key
['import']
end
## class methods ##
def self.create(attributes = {})
new(attributes).tap do |job|
if job.valid?
begin
self.launch!(job)
rescue Exception => e
job.log "#{e.message}\n#{e.backtrace}"
job.errors.add(:source, e.message)
end
end
end
end
def self.launch!(job)
Locomotive::Import::Job.run! job.source, job.site, {
:reset => job.reset,
:enabled => job.enabled
}
end
def self.current(site)
job = Delayed::Job.where({ :job_type => 'import', :site_id => site.id }).last
{
:step => job.nil? ? 'done' : job.step,
:failed => job && job.last_error.present?
}
end
def self.name
'Import'
end
end
end
end

View File

@ -1,203 +0,0 @@
module Locomotive
module Import
class Pages < Base
def process
context[:done] = {} # initialize the hash storing pages already processed
self.add_page('404')
self.add_page('index')
Dir[File.join(theme_path, 'app', 'views', 'pages', '**/*')].each do |template_path|
fullpath = template_path.gsub(File.join(theme_path, 'app', 'views', 'pages'), '').gsub('.liquid', '').gsub(/^\//, '')
next if %w(index 404).include?(fullpath)
self.add_page(fullpath)
end
# make sure all the pages were processed (redirection pages without template for instance)
self.pages.each { |fullpath, attributes| self.add_page_without_template(fullpath.to_s) }
end
protected
def add_page_without_template(fullpath)
page = context[:done][fullpath]
return page if page # already added, so skip it
self._save_page!(fullpath, nil)
end
def add_page(fullpath)
page = context[:done][fullpath]
return page if page # already added, so skip it
template = File.read(File.join(theme_path, 'app', 'views', 'pages', "#{fullpath}.liquid")) rescue "Unable to find #{fullpath}.liquid"
self.replace_images!(template)
self.build_parent_template(template)
self._save_page!(fullpath, template)
end
def _save_page!(fullpath, template)
parent = self.find_parent(fullpath)
attributes = {
:title => fullpath.split('/').last.humanize,
:slug => fullpath.split('/').last,
:parent => parent,
:raw_template => template,
:published => true
}.merge(self.pages[fullpath] || {}).symbolize_keys
if %w(index 404).include?(fullpath)
attributes[:position] = fullpath == 'index' ? 0 : 1
end
attributes[:position] = attributes[:position].to_i
# templatized ?
# TODO: DEBUG PURPOSE
# if content_type_slug = attributes.delete(:content_type)
# attributes.merge!({
# :templatized => true,
# :content_type => site.content_types.where(:slug => content_type_slug).first
# })
# end
# redirection page ?
attributes[:redirect] = true if attributes[:redirect_url].present?
# Don't want the editable elements to be imported: they will be regenerated
editable_elements_attributes = attributes.delete(:editable_elements)
page = site.pages.where(:fullpath => self.sanitize_fullpath(fullpath)).first || site.pages.build
page.attributes = attributes
page.save!
unless editable_elements_attributes.nil?
self.assign_editable_elements(page, editable_elements_attributes)
end
self.log "adding #{page.fullpath} (#{template.blank? ? 'without' : 'with'} template) / #{page.position}"
site.reload
context[:done][fullpath] = page
page
end
def assign_editable_elements(page, elements)
page.reload # editable elements are not synchronized otherwise
elements.each do |attributes|
element = page.find_editable_element(attributes['block'], attributes['slug'])
next if element.nil?
if element.respond_to?(:source)
unless attributes['content'].blank?
asset_path = File.join(theme_path, 'public', attributes['content'])
if File.exists?(asset_path)
element.source = File.open(asset_path)
end
end
else
element.content = attributes['content']
end
end
page.save!
end
def build_parent_template(template)
# just check if the template contains the extends keyword
fullpath = template.scan(/\{% extends \'?([\w|\/]+)\'? %\}/).flatten.first
if fullpath # inheritance detected
return if fullpath == 'parent'
self.add_page(fullpath)
end
end
def find_parent(fullpath)
return nil if %w(index 404).include?(fullpath) # avoid cyclic issue with the index page
segments = fullpath.split('/')
return site.pages.root.first if segments.size == 1
segments.pop
parent_fullpath = segments.join('/').gsub(/^\//, '')
# look for a local index page in db
parent = site.pages.where(:fullpath => parent_fullpath).first
parent || self.add_page(parent_fullpath)
end
def replace_images!(template)
return if template.blank?
template.gsub!(/\/samples\/(.*\.[a-zA-Z0-9]{3})/) do |match|
name = File.basename($1)
if asset = site.content_assets.where(:source_filename => name).first
asset.source.url
else
match
end
end
end
def pages
@pages ||= self.retrieve_pages
end
def retrieve_pages
pages = context[:database]['site']['pages']
if pages.is_a?(Array) # ordered list of pages
tmp, positions = {}, Hash.new(0)
pages.each do |data|
position = nil
fullpath = data.keys.first.to_s
unless %w(index 404).include?(fullpath)
(segments = fullpath.split('/')).pop
position_key = segments.empty? ? 'index' : segments.join('/')
position = positions[position_key]
positions[position_key] += 1
end
attributes = (data.values.first || {}).merge(:position => position)
tmp[fullpath] = attributes
end
pages = tmp
end
pages
end
def sanitize_fullpath(fullpath)
fullpath.gsub(/\/template$/, '/content_type_template')
end
end
end
end

View File

@ -1,15 +0,0 @@
module Locomotive
module Import
class Site < Base
def process
attributes = database['site'].clone.delete_if { |name, value| %w{name pages assets content_types}.include?(name) }
site.attributes = attributes
site.save!
end
end
end
end

View File

@ -1,21 +0,0 @@
module Locomotive
module Import
class Snippets < Base
def process
Dir[File.join(theme_path, 'app', 'views', 'snippets', '*')].each do |snippet_path|
self.log "path = #{snippet_path}"
name = File.basename(snippet_path, File.extname(snippet_path)).parameterize('_')
snippet = site.snippets.where(:slug => name).first || site.snippets.build(:name => name)
snippet.template = File.read(snippet_path)
snippet.save!
end
end
end
end
end

View File

@ -29,23 +29,6 @@ namespace :locomotive do
end
end
desc 'Import a remote template described by its URL -- 2 options: SITE=name or id, RESET=by default false'
task :import => :environment do
url, site_name_or_id, samples, reset = ENV['URL'], ENV['SITE'], (Boolean.set(ENV['SAMPLES']) || false), (Boolean.set(ENV['RESET']) || false)
if url.blank? || (url =~ /https?:\/\//).nil?
raise "URL is missing or it is not a valid http url."
end
site = Locomotive::Site.find(site_name_or_id) || Locomotive::Site.where(:name => site_name_or_id).first || Locomotive::Site.first
if site.nil?
raise "No site found. Please give a correct value (name or id) for the SITE env variable."
end
::Locomotive::Import::Job.run!(url, site, { :samples => samples, :reset => reset })
end
desc 'Add a new admin user (NOTE: currently only supports adding user to first site)'
task :add_admin => :environment do
name = ask('Display name: ') { |q| q.echo = true }

View File

@ -10,7 +10,7 @@ Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.authors = ['Didier Lafforgue']
s.email = ['didier@nocoffee.fr']
s.homepage = 'http://locomotivecms.com'
s.homepage = 'http://www.locomotivecms.com'
s.summary = 'A Next Generation Sexy CMS for Rails 3'
s.description = 'Locomotive is a next generation CMS system with sexy admin tools, liquid templating, and inline editing powered by mongodb and rails 3.1'
@ -32,13 +32,13 @@ Gem::Specification.new do |s|
s.add_dependency 'kaminari'
s.add_dependency 'haml', '~> 3.1.3'
s.add_dependency 'sass-rails', '~> 3.1.4'
s.add_dependency 'sass-rails', '~> 3.1.5'
s.add_dependency 'coffee-script', '~> 2.2.0'
s.add_dependency 'uglifier', '~> 1.2.2'
s.add_dependency 'compass', '~> 0.12.alpha.4'
s.add_dependency 'jquery-rails', '~> 1.0.16'
s.add_dependency 'rails-backbone', '0.5.4'
s.add_dependency 'codemirror-rails'
s.add_dependency 'codemirror-rails', '~> 2.21'
s.add_dependency 'locomotive-tinymce-rails', '~> 3.4.7'
s.add_dependency 'locomotive-aloha-rails', '~> 0.20.1'
s.add_dependency 'flash_cookie_session', '~> 1.1.1'
@ -57,11 +57,10 @@ Gem::Specification.new do |s|
s.add_dependency 'dragonfly', '~> 0.9.8'
s.add_dependency 'rack-cache', '~> 1.1'
s.add_dependency 'mimetype-fu', '~> 0.1.2'
s.add_dependency 'rubyzip'
s.add_dependency 'actionmailer-with-request', '~> 0.3.0'
s.add_dependency 'httparty', '~> 0.8.1'
s.add_dependency 'delayed_job_mongoid', '~> 1.0.8'
# s.add_dependency 'delayed_job_mongoid', '~> 1.0.8'
s.files = Dir[ 'Gemfile',
'{app}/**/*',

View File

@ -37,13 +37,6 @@ Locomotive.configure do |config|
# :password => '<your_heroku_password>'
# }
# Locomotive uses the DelayedJob gem for the site import module.
# In case you want to deploy to Heroku, you will have to pay for an extra dyno.
# If you do not mind about importing theme without DelayedJob, disable it.
#
# Warning: this option is not used if you deploy on bushi.do and we set automatically the value to true.
config.delayed_job = true # false
# configure how many items we display in sub menu in the "Contents" section.
# config.ui = {
# :lastest_entries_nb => 5,

Binary file not shown.

View File

@ -1,129 +0,0 @@
require 'spec_helper'
describe Locomotive::Export do
context '#content_type' do
before(:each) do
site = FactoryGirl.build('another site')
Locomotive::Site.stubs(:find).returns(site)
project_type = build_project_type(site)
project_type.entries.build(:title => 'Project #1', :description => 'Lorem ipsum', :active => true)
project_type.entries.build(:title => 'Project #2', :description => 'More Lorem ipsum', :active => false)
team_type = build_team_type(site, project_type)
team_type.entries.build(:name => 'Ben', :projects => project_type.entries, :current_project => project_type.entries.first)
team_type.entries.build(:name => 'Zach', :current_project => project_type.entries.last)
@project_data = ::Locomotive::Export.new(site).send(:extract_contents, project_type)
@team_data = ::Locomotive::Export.new(site).send(:extract_contents, team_type)
end
it 'includes the exact number of entries' do
@project_data.size.should == 2
@project_data.collect { |n| n.keys.first }.should == ['Project #1', 'Project #2']
end
it 'deals with real booleans' do
@project_data.first.values.first['active'].should === true
@project_data.last.values.first['active'].should === false
end
it 'stores the list of highlighted values in a has_many relationship' do
@team_data.first.values.first['projects'].size.should == 2
@team_data.first.values.first['projects'].should == ['Project #1', 'Project #2']
@team_data.last.values.first['projects'].should be_nil
end
it 'stores a highlighted value in a has_one relationship' do
@team_data.collect { |n| n.values.first['current_project'] }.should == ['Project #1', 'Project #2']
end
def build_project_type(site)
FactoryGirl.build(:content_type, :site => site, :highlighted_field_name => 'custom_field_1').tap do |content_type|
content_type.entries_custom_fields.build :label => 'Title', :name => 'title', :type => 'string'
content_type.entries_custom_fields.build :label => 'My Description', :name => 'description', :type => 'text'
content_type.entries_custom_fields.build :label => 'Active', :type => 'boolean'
end
end
def build_team_type(site, project_type)
Object.send(:remove_const, 'TestProject') rescue nil
klass = Object.const_set('TestProject', Class.new { def self.embedded?; false; end })
content_type = FactoryGirl.build(:content_type, :site => site, :name => 'team', :highlighted_field_name => 'custom_field_1')
content_type.entries_custom_fields.build :label => 'Name', :name => 'name', :type => 'string'
content_type.entries_custom_fields.build :label => 'Projects', :type => 'has_many', :name => 'projects', :target => 'TestProject'
content_type.entries_custom_fields.build :label => 'Bio', :name => 'bio', :type => 'text'
content_type.entries_custom_fields.build :label => 'Current Project', :type => 'has_one', :name => 'current_project', :target => 'TestProject'
content_type
end
end
context '#zipfile' do
before(:all) do
@site = FactoryGirl.create('another site')
# first import a brand new site
self.import_it
# then export it of course
@zip_file = self.export_it
end
it 'generates a zipfile' do
@zip_file.should_not be_nil
File.exists?(@zip_file).should be_true
end
it 'has the exact number of pages from the original site' do
self.unzip
Dir[File.join(self.zip_folder, 'app', 'views', 'pages', '**/*.liquid')].size.should == 11
end
it 'includes snippets' do
self.unzip
Dir[File.join(self.zip_folder, 'app', 'views', 'snippets', '*.liquid')].size.should == 1
end
it 'has yaml files describing the content types as well as their data' do
self.unzip
Dir[File.join(self.zip_folder, 'app', 'content_types', '*.yml')].size.should == 4
Dir[File.join(self.zip_folder, 'data', '*.yml')].size.should == 4
end
def import_it
job = Locomotive::Import::Job.new(FixturedTheme.duplicate_and_open('default.zip'), @site, { :samples => true, :reset => true })
job.perform
job.success nil
end
def export_it
::Locomotive::Export.run!(@site, @site.name.parameterize)
end
def zip_folder
File.join(File.dirname(@zip_file), 'locomotive-test-website-2')
end
def unzip
return if @zip_file || File.exists?(self.zip_folder)
Zip::ZipFile.open(@zip_file) do |zipfile|
destination_path = File.dirname(@zip_file)
zipfile.each do |entry|
FileUtils.mkdir_p(File.dirname(File.join(destination_path, entry.name)))
zipfile.extract(entry, File.join(destination_path, entry.name))
end
end
end
after(:all) do
FileUtils.rm_rf(self.zip_folder) if File.exists?(self.zip_folder)
Locomotive::Site.destroy_all
end
end
end

View File

@ -1,140 +0,0 @@
require 'spec_helper'
describe Locomotive::Import::Job do
context 'when successful' do
before(:all) do
@site = FactoryGirl.create(:site)
job = Locomotive::Import::Job.new(FixturedTheme.duplicate_and_open('default.zip'), @site, { :samples => true, :reset => true })
job.perform
job.success nil
end
it 'updates the site information' do
@site.name.should_not == "HTML5 portfolio"
@site.meta_keywords.should == "html5 portfolio theme locomotive cms"
@site.meta_description.should == "portfolio powered by html5"
end
it 'adds content types' do
@site.content_types.count.should == 4
content_type = @site.content_types.where(:slug => 'projects').first
content_type.entries_custom_fields.size.should == 9
end
it 'replaces the target and the reverse_lookup values by the correct ones in a has_many relationship' do
content_type = @site.content_types.where(:slug => 'clients').first
field = content_type.entries_custom_fields.last
field.target.should match /^ContentContentType/
field.reverse_lookup.should == 'custom_field_8'
end
it 'correctly imports content type names' do
content_type = @site.content_types.where(:slug => 'projects').first
content_type.name.should == 'My projects'
end
it 'converts correctly the order_by option for content types' do
content_type = @site.content_types.where(:slug => 'messages').first
content_type.order_by.should == 'created_at'
end
it 'adds samples coming with content types' do
content_type = @site.content_types.where(:slug => 'projects').first
content_type.entries.size.should == 5
entry = content_type.entries.first
entry._permalink.should == 'locomotivecms'
entry.seo_title.should == 'My open source CMS'
entry.meta_description.should == 'bla bla bla'
entry.meta_keywords.should == 'cms ruby engine mongodb'
entry.name.should == 'Locomotive App'
entry.thumbnail.url.should_not be_nil
entry.featured.should == true
entry.client.name.should == 'My client #1'
entry.team.first.name.should == 'Michael Scott'
end
it 'inserts theme assets' do
@site.theme_assets.count.should == 10
end
it 'inserts all the pages' do
@site.pages.count.should == 11
end
it 'inserts the index and 404 pages' do
@site.pages.root.first.should_not be_nil
@site.pages.not_found.first.should_not be_nil
end
it 'sets the editable text for a page from the site config file' do
page = @site.pages.where(:title => 'Contact').first
page.find_editable_element('content', 'address').content.should == '<p>Our office address: 215 Vine Street, Scranton, PA 18503</p>'
end
it 'sets the editable file for a page from the site config file' do
page = @site.pages.where(:title => 'Contact').first
page.find_editable_element('content', 'office').source_filename.should == 'office.jpg'
end
it 'sets the empty editable file for a page from the site config file' do
page = @site.pages.where(:title => 'Contact').first
page.find_editable_element('content', 'office2').source_filename.should be_nil
end
it 'inserts templatized page' do
page = @site.pages.where(:templatized => true).first
page.should_not be_nil
page.fullpath.should == 'portfolio/content_type_template'
end
it 'inserts redirection page' do
page = @site.pages.where(:redirect => true).first
page.should_not be_nil
page.redirect_url.should == 'http://blog.locomotivecms.com'
end
it 'inserts snippets' do
@site.snippets.count.should == 1
end
after(:all) do
Locomotive::Site.destroy_all
end
end
context 'with an existing site' do
before(:all) do
@site = FactoryGirl.create('existing site')
job = Locomotive::Import::Job.new(FixturedTheme.duplicate_and_open('default.zip'), @site, { :samples => true, :reset => false })
job.perform
job.success nil
end
context 'updates to content_type attributes' do
before(:all) do
@projects = content_type = @site.content_types.where(:slug => 'projects').first
end
it 'includes new name' do
@projects.name.should == 'My projects'
end
it 'includes new description' do
@projects.description.should == 'My portfolio'
end
it 'includes new order by' do
@projects.order_by.should == '_position_in_list'
end
end
end
end

View File

@ -97,13 +97,6 @@ describe Locomotive::Ability do
end
end
context 'importing' do
it 'should allow importing of sites from (designer)' do
should allow_permission_from :import, @designer
should_not allow_permission_from :import, @author
end
end
context 'pointing' do
it 'should allow importing of sites from (designer)' do
should allow_permission_from :point, @designer