remove the import/export functionalities (replaced by the API and the new locomotive editor) + upgrade gems (codemirror, ...etc)
This commit is contained in:
parent
9f7ac8630e
commit
b93c7f8f51
7
Gemfile
7
Gemfile
@ -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
|
||||
|
13
Gemfile.lock
13
Gemfile.lock
@ -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
|
||||
|
@ -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
|
||||
...
|
||||
|
2
Rakefile
2
Rakefile
@ -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
|
||||
|
@ -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()
|
@ -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?
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
@ -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
|
@ -56,10 +56,6 @@ module Locomotive
|
||||
site == @site
|
||||
end
|
||||
|
||||
can :import, Site
|
||||
|
||||
can :export, Site
|
||||
|
||||
can :point, Site
|
||||
|
||||
cannot :create, Site
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
10
doc/TODO
10
doc/TODO
@ -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:
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
@ -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
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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 }
|
||||
|
@ -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}/**/*',
|
||||
|
@ -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,
|
||||
|
BIN
spec/fixtures/themes/default.zip
vendored
BIN
spec/fixtures/themes/default.zip
vendored
Binary file not shown.
@ -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
|
@ -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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user