namespace controllers, models, ...etc (wip)

This commit is contained in:
did 2011-10-31 00:02:41 +01:00
parent 9f8d2e4146
commit 99e442673c
280 changed files with 3771 additions and 3253 deletions

2
.gitignore vendored
View File

@ -34,5 +34,5 @@ sites/
permanent
doc/bushido
*.swp
.sass-cache/

View File

@ -11,7 +11,7 @@ gem 'cancan', '~> 1.6.7'
gem 'mongoid', '~> 2.3.2'
gem 'bson_ext', '~> 1.3.1'
gem 'locomotive_mongoid_acts_as_tree', '0.1.5.7', :require => 'mongoid_acts_as_tree'
gem 'locomotive_mongoid_acts_as_tree', '0.1.5.7', :require => 'mongoid_acts_as_tree', :path => '../gems/acts_as_tree' # TODO: REPLACE IT
gem 'custom_fields', '~> 1.1.0.rc1'
gem 'will_paginate', '~> 3.0.2'
@ -42,7 +42,7 @@ gem 'rubyzip'
gem 'actionmailer-with-request', '~> 0.3.0', :require => 'actionmailer_with_request'
gem 'httparty', '~> 0.8.1'
gem 'delayed_job', '~> 3.0.0.pre2'
gem 'delayed_job', '~> 2.1.1'
gem 'delayed_job_mongoid', '~> 1.0.4'
gem 'SystemTimer', :platforms => :ruby_18

View File

@ -1,58 +1,75 @@
GIT
remote: git://github.com/chriseppstein/compass.git
revision: 22e2458b77519e8eb8463170c1a1fe4bab105f3e
branch: rails31
specs:
compass (0.12.0.alpha.0.22e2458)
chunky_png (~> 1.2)
fssm (>= 0.2.7)
sass (~> 3.1)
GIT
remote: git://github.com/yabawock/handlebars-rails.git
revision: a09077aa91f10e08403af84586b2f2f0b38d9e2f
specs:
handlebars-rails (0.9.1)
PATH
remote: ../gems/acts_as_tree
specs:
locomotive_mongoid_acts_as_tree (0.1.5.7)
GEM
remote: http://rubygems.org/
specs:
POpen4 (0.1.4)
Platform (>= 0.4.0)
open4
Platform (0.4.0)
RedCloth (4.2.8)
SystemTimer (1.2.3)
ZenTest (4.6.2)
abstract (1.0.0)
actionmailer (3.0.10)
actionpack (= 3.0.10)
mail (~> 2.2.19)
actionmailer (3.1.1)
actionpack (= 3.1.1)
mail (~> 2.3.0)
actionmailer-with-request (0.3.0)
rails (>= 3)
actionpack (3.0.10)
activemodel (= 3.0.10)
activesupport (= 3.0.10)
builder (~> 2.1.2)
erubis (~> 2.6.6)
i18n (~> 0.5.0)
rack (~> 1.2.1)
rack-mount (~> 0.6.14)
rack-test (~> 0.5.7)
tzinfo (~> 0.3.23)
activemodel (3.0.10)
activesupport (= 3.0.10)
builder (~> 2.1.2)
i18n (~> 0.5.0)
activerecord (3.0.10)
activemodel (= 3.0.10)
activesupport (= 3.0.10)
arel (~> 2.0.10)
tzinfo (~> 0.3.23)
activeresource (3.0.10)
activemodel (= 3.0.10)
activesupport (= 3.0.10)
activesupport (3.0.10)
actionpack (3.1.1)
activemodel (= 3.1.1)
activesupport (= 3.1.1)
builder (~> 3.0.0)
erubis (~> 2.7.0)
i18n (~> 0.6)
rack (~> 1.3.2)
rack-cache (~> 1.1)
rack-mount (~> 0.8.2)
rack-test (~> 0.6.1)
sprockets (~> 2.0.2)
activemodel (3.1.1)
activesupport (= 3.1.1)
builder (~> 3.0.0)
i18n (~> 0.6)
activerecord (3.1.1)
activemodel (= 3.1.1)
activesupport (= 3.1.1)
arel (~> 2.2.1)
tzinfo (~> 0.3.29)
activeresource (3.1.1)
activemodel (= 3.1.1)
activesupport (= 3.1.1)
activesupport (3.1.1)
multi_json (~> 1.0)
addressable (2.2.6)
archive-tar-minitar (0.5.2)
arel (2.0.10)
arel (2.2.1)
autotest (4.4.6)
ZenTest (>= 4.4.1)
bcrypt-ruby (2.1.4)
bson (1.4.0)
bson_ext (1.4.0)
builder (2.1.2)
bcrypt-ruby (3.0.1)
bson (1.4.1)
bson_ext (1.3.1)
builder (3.0.0)
bushido (0.0.35)
highline (>= 1.6.1)
json (>= 1.4.6)
orm_adapter (~> 0.0.3)
rest-client (>= 1.6.1)
bushido_stub (0.0.3)
activesupport (>= 3.0.7)
cancan (1.6.5)
cancan (1.6.7)
capybara (1.1.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
@ -60,16 +77,22 @@ GEM
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
carrierwave (0.5.6)
carrierwave (0.5.7)
activesupport (~> 3.0)
cells (3.6.6)
carrierwave-mongoid (0.1.3)
carrierwave (>= 0.5.6)
mongoid (~> 2.1)
cells (3.7.0)
actionpack (~> 3.0)
railties (~> 3.0)
childprocess (0.2.2)
ffi (~> 1.0.6)
chunky_png (1.2.5)
coffee-script (2.2.0)
coffee-script-source
execjs
coffee-script-source (1.1.2)
columnize (0.3.4)
configuration (1.3.1)
crack (0.1.8)
cucumber (1.1.0)
builder (>= 2.1.2)
diff-lcs (>= 1.1.2)
@ -80,133 +103,134 @@ GEM
capybara (>= 1.1.1)
cucumber (>= 1.1.0)
nokogiri (>= 1.5.0)
custom_fields (1.0.0.beta.25)
activesupport (~> 3.0.9)
mongoid (= 2.0.2)
custom_fields (1.1.0.rc1)
activesupport (~> 3.1.1)
carrierwave-mongoid (~> 0.1.3)
mongoid (= 2.3.2)
daemons (1.1.4)
database_cleaner (0.6.7)
delayed_job (2.1.4)
activesupport (~> 3.0)
daemons
delayed_job_mongoid (1.0.2)
delayed_job_mongoid (1.0.4)
delayed_job (~> 2.1.1)
mongoid (~> 2.0.0.rc)
devise (1.3.4)
bcrypt-ruby (~> 2.1.2)
mongoid (>= 2.0)
devise (1.4.9)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.0.3)
warden (~> 1.0.3)
devise_bushido_authenticatable (1.0.0.alpha10)
devise
devise (>= 1.0.6)
rubycas-client (>= 2.2.1)
diff-lcs (1.1.3)
dragonfly (0.9.8)
rack
erubis (2.6.6)
abstract (>= 1.0.0)
excon (0.6.6)
factory_girl (2.1.2)
ejs (1.0.0)
erubis (2.7.0)
excon (0.7.6)
execjs (1.2.9)
multi_json (~> 1.0)
factory_girl (2.2.0)
activesupport
factory_girl_rails (1.2.0)
factory_girl (~> 2.1.0)
factory_girl_rails (1.3.0)
factory_girl (~> 2.2.0)
railties (>= 3.0.0)
ffi (1.0.9)
fog (0.8.2)
fog (1.0.0)
builder
excon (~> 0.6.1)
formatador (>= 0.1.3)
json
excon (~> 0.7.3)
formatador (~> 0.2.0)
mime-types
net-ssh (>= 2.1.3)
nokogiri (>= 1.4.4)
multi_json (~> 1.0.3)
net-scp (~> 1.0.4)
net-ssh (~> 2.1.4)
nokogiri (~> 1.5.0)
ruby-hmac
formatador (0.2.1)
formtastic (1.2.4)
actionpack (>= 2.3.7)
activesupport (>= 2.3.7)
i18n (~> 0.4)
gherkin (2.5.2)
fssm (0.2.7)
gherkin (2.5.4)
json (>= 1.4.6)
growl-glue (1.0.7)
haml (3.1.2)
haml (3.1.3)
has_scope (0.5.1)
heroku (1.19.1)
activesupport (>= 2.1.0)
launchy (~> 0.3.2)
rest-client (>= 1.4.0, < 1.7.0)
highline (1.6.2)
httparty (0.7.8)
crack (= 0.1.8)
i18n (0.5.0)
inherited_resources (1.1.2)
hike (1.2.1)
httparty (0.8.1)
multi_json
multi_xml
i18n (0.6.0)
inherited_resources (1.3.0)
has_scope (~> 0.5.0)
responders (~> 0.6.0)
jammit (0.6.3)
yui-compressor (>= 0.9.3)
jquery-rails (1.0.16)
railties (~> 3.0)
thor (~> 0.14)
json (1.6.1)
json_pure (1.6.1)
kgio (2.6.0)
launchy (0.3.7)
configuration (>= 0.0.5)
rake (>= 0.8.1)
launchy (2.0.5)
addressable (~> 2.2.6)
linecache (0.43)
linecache19 (0.5.12)
ruby_core_source (>= 0.1.4)
locomotive_jammit-s3 (0.5.4.4)
jammit (>= 0.5.4)
mimemagic (>= 0.1.7)
s3 (>= 0.3.7)
locomotive_liquid (2.2.2)
locomotive_mongoid_acts_as_tree (0.1.5.7)
mongoid (= 2.0.2)
mail (2.2.19)
activesupport (>= 2.3.6)
mail (2.3.0)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.16)
mimemagic (0.1.8)
mime-types (1.17.2)
mimetype-fu (0.1.2)
mocha (0.9.12)
mongo (1.4.0)
bson (= 1.4.0)
mongoid (2.0.2)
activemodel (~> 3.0)
mongo (~> 1.3)
mongo (1.4.1)
bson (= 1.4.1)
mongoid (2.3.2)
activemodel (~> 3.1)
mongo (~> 1.4)
tzinfo (~> 0.3.22)
net-ssh (2.2.1)
multi_json (1.0.3)
multi_xml (0.4.1)
net-scp (1.0.4)
net-ssh (>= 1.99.1)
net-ssh (2.1.4)
nokogiri (1.5.0)
open4 (1.1.0)
orm_adapter (0.0.5)
pickle (0.4.10)
cucumber (>= 0.8)
rake
polyglot (0.3.2)
proxies (0.2.1)
rack (1.2.4)
rack (1.3.5)
rack-cache (1.1)
rack (>= 0.4)
rack-mount (0.6.14)
rack-mount (0.8.3)
rack (>= 1.0.0)
rack-test (0.5.7)
rack-ssl (1.3.2)
rack
rack-test (0.6.1)
rack (>= 1.0)
rails (3.0.10)
actionmailer (= 3.0.10)
actionpack (= 3.0.10)
activerecord (= 3.0.10)
activeresource (= 3.0.10)
activesupport (= 3.0.10)
rails (3.1.1)
actionmailer (= 3.1.1)
actionpack (= 3.1.1)
activerecord (= 3.1.1)
activeresource (= 3.1.1)
activesupport (= 3.1.1)
bundler (~> 1.0)
railties (= 3.0.10)
railties (3.0.10)
actionpack (= 3.0.10)
activesupport (= 3.0.10)
railties (= 3.1.1)
rails-backbone (0.5.4)
coffee-script (~> 2.2.0)
ejs (~> 1.0.0)
rails (~> 3.1.0)
railties (3.1.1)
actionpack (= 3.1.1)
activesupport (= 3.1.1)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (~> 0.14.4)
raindrops (0.7.0)
thor (~> 0.14.6)
raindrops (0.8.0)
rake (0.9.2)
rdoc (3.9.4)
rdoc (3.11)
json (~> 1.4)
responders (0.6.4)
rest-client (1.6.7)
mime-types (>= 1.16)
@ -215,8 +239,7 @@ GEM
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.6.0)
rspec-cells (0.0.5)
cells (~> 3.4)
rspec-cells (0.1.0)
rails (~> 3.0)
rspec-rails (~> 2.2)
rspec-core (2.6.4)
@ -244,82 +267,91 @@ GEM
ruby-hmac (0.4.0)
ruby_core_source (0.1.5)
archive-tar-minitar (>= 0.5.2)
rubycas-client (2.2.1)
activesupport
rubyzip (0.9.4)
s3 (0.3.8)
proxies (~> 0.2.0)
sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6)
sass (3.1.2)
selenium-webdriver (2.8.0)
nokogiri (< 1.6, >= 1.4.4)
sass (3.1.10)
sass-rails (3.1.4)
actionpack (~> 3.1.0)
railties (~> 3.1.0)
sass (>= 3.1.4)
sprockets (~> 2.0.0)
tilt (~> 1.3.2)
selenium-webdriver (2.10.0)
childprocess (>= 0.2.1)
ffi (>= 1.0.7)
ffi (= 1.0.9)
json_pure
rubyzip
spork (0.9.0.rc9)
sprockets (2.0.3)
hike (~> 1.2)
rack (~> 1.0)
tilt (!= 1.3.0, ~> 1.1)
term-ansicolor (1.0.7)
thor (0.14.6)
tilt (1.3.3)
treetop (1.4.10)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.29)
tzinfo (0.3.30)
uglifier (1.0.4)
execjs (>= 0.3.0)
multi_json (>= 1.0.2)
unicorn (4.1.1)
kgio (~> 2.4)
rack
raindrops (~> 0.6)
warden (1.0.5)
warden (1.0.6)
rack (>= 1.0)
will_paginate (3.0.2)
xpath (0.1.4)
nokogiri (~> 1.3)
yui-compressor (0.9.6)
POpen4 (>= 0.1.4)
PLATFORMS
ruby
DEPENDENCIES
RedCloth (= 4.2.8)
RedCloth (~> 4.2.8)
SystemTimer
ZenTest
actionmailer-with-request
actionmailer-with-request (~> 0.3.0)
autotest
bson_ext (~> 1.4.0)
bson_ext (~> 1.3.1)
bushido (= 0.0.35)
bushido_stub (= 0.0.3)
cancan
cancan (~> 1.6.7)
capybara
carrierwave (= 0.5.6)
cells
carrierwave-mongoid (~> 0.1.3)
cells (~> 3.7.0)
coffee-script (~> 2.2.0)
compass!
cucumber-rails
custom_fields (= 1.0.0.beta.25)
custom_fields (~> 1.1.0.rc1)
database_cleaner
delayed_job (= 2.1.4)
delayed_job_mongoid (= 1.0.2)
devise (= 1.3.4)
devise_bushido_authenticatable (= 1.0.0.alpha10)
dragonfly (~> 0.9.1)
delayed_job (~> 2.1.1)
delayed_job_mongoid (~> 1.0.4)
devise (~> 1.4.9)
dragonfly (~> 0.9.8)
factory_girl_rails (~> 1.1)
fog (= 0.8.2)
fog (~> 1.0.0)
formtastic (~> 1.2.3)
growl-glue
haml (= 3.1.2)
heroku (= 1.19.1)
highline
httparty (= 0.7.8)
inherited_resources (~> 1.1.2)
haml (~> 3.1.3)
handlebars-rails!
highline (~> 1.6.2)
httparty (~> 0.8.1)
inherited_resources (~> 1.3.0)
jquery-rails (~> 1.0.16)
launchy
linecache (= 0.43)
locomotive_jammit-s3
locomotive_liquid (= 2.2.2)
locomotive_mongoid_acts_as_tree (= 0.1.5.7)
mimetype-fu
locomotive_mongoid_acts_as_tree (= 0.1.5.7)!
mimetype-fu (~> 0.1.2)
mocha (= 0.9.12)
mongoid (~> 2.0.2)
mongoid (~> 2.3.2)
pickle
rack-cache
rails (= 3.0.10)
rack-cache (~> 1.1)
rails (~> 3.1.1)
rails-backbone (= 0.5.4)
rake (= 0.9.2)
rmagick (= 2.12.2)
rspec-cells
@ -327,10 +359,10 @@ DEPENDENCIES
ruby-debug
ruby-debug19
rubyzip
sanitize
sass (= 3.1.2)
sanitize (~> 2.0.3)
sass-rails (~> 3.1.4)
spork (~> 0.9.0.rc)
uglifier (~> 1.0.4)
unicorn
warden
will_paginate (~> 3.0.0)
will_paginate (~> 3.0.2)
xpath (~> 0.1.4)

View File

@ -1,34 +0,0 @@
class Admin::GlobalActionsCell < ::Admin::MenuCell
attr_reader :current_admin, :current_site_url
def show(args)
@current_admin = args[:current_admin]
@current_site_url = args[:current_site_url]
super
end
protected
def build_list
add :welcome, :url => edit_admin_my_account_url, :i18n_options => {
:key => 'admin.shared.header.welcome',
:arg => :name,
:value => @current_admin.name
}
add :see, :url => current_site_url, :id => 'viewsite', :target => '_blank'
if Locomotive.config.multi_sites? && current_admin.sites.size > 1
add :switch, :url => '#', :id => 'sites-picker-link'
end
add :help, :url => '#', :class => 'tutorial', :id => 'help'
add :logout, :url => destroy_admin_session_url, :confirm => t('admin.messages.confirm')
end
def localize_label(label, options = {})
I18n.t("admin.shared.header.#{label}", options)
end
end

View File

@ -1,10 +0,0 @@
class Admin::MainMenuCell < ::Admin::MenuCell
protected
def build_list
add :contents, :url => admin_pages_url
add :settings, :url => edit_admin_current_site_url
end
end

View File

@ -1,17 +0,0 @@
class Admin::SettingsMenuCell < ::Admin::SubMenuCell #::Admin::MenuCell
protected
def build_list
add :site, :url => edit_admin_current_site_url
add :theme_assets, :url => admin_theme_assets_url
add :account, :url => edit_admin_my_account_url
end
# def build_item(name, attributes)
# item = super
# enhanced_class = "#{'on' if name.to_s == sections(:sub)} #{item[:class]}"
# item.merge(:class => enhanced_class)
# end
end

View File

@ -0,0 +1,34 @@
class Locomotive::GlobalActionsCell < ::Locomotive::MenuCell
attr_reader :current_account, :current_site_url
def show(args)
@current_account = args[:current_account]
@current_site_url = args[:current_site_url]
super
end
protected
def build_list
add :welcome, :url => edit_locomotive_my_account_url, :i18n_options => {
:key => 'locomotive.shared.header.welcome',
:arg => :name,
:value => @current_account.name
}
add :see, :url => current_site_url, :id => 'viewsite', :target => '_blank'
if Locomotive.config.multi_sites? && current_account.sites.size > 1
add :switch, :url => '#', :id => 'sites-picker-link'
end
add :help, :url => '#', :class => 'tutorial', :id => 'help'
add :logout, :url => destroy_locomotive_session_url, :confirm => t('locomotive.messages.confirm')
end
def localize_label(label, options = {})
I18n.t("locomotive.shared.header.#{label}", options)
end
end

View File

@ -0,0 +1,10 @@
class Locomotive::MainMenuCell < ::Locomotive::MenuCell
protected
def build_list
add :contents, :url => locomotive_pages_url
add :settings, :url => edit_locomotive_current_site_url
end
end

View File

@ -1,4 +1,4 @@
class Admin::MenuCell < Cell::Base
class Locomotive::MenuCell < Cell::Base
include ::Rails.application.routes.url_helpers
@ -94,7 +94,7 @@ class Admin::MenuCell < Cell::Base
end
def localize_label(label)
I18n.t("admin.shared.menu.#{label}")
I18n.t("locomotive.shared.menu.#{label}")
end
end

View File

@ -0,0 +1,11 @@
class Locomotive::SettingsMenuCell < ::Locomotive::SubMenuCell
protected
def build_list
add :site, :url => edit_locomotive_current_site_url
add :theme_assets, :url => locomotive_theme_assets_url
add :account, :url => edit_locomotive_my_account_url
end
end

View File

@ -1,4 +1,4 @@
class Admin::SubMenuCell < ::Admin::MenuCell
class Locomotive::SubMenuCell < ::Locomotive::MenuCell
protected

View File

@ -1,11 +0,0 @@
class ApplicationController < ActionController::Base
protect_from_forgery
protected
# rescue_from Exception, :with => :render_error
#
# def render_error
# render :template => "/admin/errors/500", :layout => '/admin/layouts/box', :status => 500
# end
end

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class AccountsController < BaseController
sections 'settings'
@ -11,7 +11,7 @@ module Admin
@account = Account.create(params[:account])
current_site.memberships.create(:account => @account) if @account.errors.empty?
respond_with @account, :location => edit_admin_current_site_url
respond_with @account, :location => edit_locomotive_current_site_url
end
end

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class ApiContentsController < ActionController::Base
include Locomotive::Routing::SiteDispatcher

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class AssetsController < BaseController
include ActionView::Helpers::SanitizeHelper
@ -48,7 +48,7 @@ module Admin
:content_type => asset.content_type,
:url => asset.source.url,
:vignette_url => asset.vignette_url,
:destroy_url => admin_asset_url(asset, :json)
:destroy_url => locomotive_asset_url(asset, :json)
}
end

View File

@ -1,11 +1,11 @@
module Admin
module Locomotive
class BaseController < InheritedResources::Base
include Locomotive::Routing::SiteDispatcher
layout '/admin/layouts/application'
layout '/locomotive/layouts/application'
before_filter :require_admin
before_filter :require_account
before_filter :require_site
@ -21,12 +21,12 @@ module Admin
# https://rails.lighthouseapp.com/projects/8994/tickets/1905-apphelpers-within-plugin-not-being-mixed-in
Dir[File.dirname(__FILE__) + "/../../helpers/**/*_helper.rb"].each do |file|
helper "admin/#{File.basename(file, '.rb').gsub(/_helper$/, '')}"
helper "locomotive/#{File.basename(file, '.rb').gsub(/_helper$/, '')}"
end
self.responder = Locomotive::AdminResponder # custom responder
defaults :route_prefix => 'admin'
defaults :route_prefix => 'locomotive'
respond_to :html
@ -38,23 +38,23 @@ module Admin
else
flash[:alert] = exception.message
redirect_to admin_pages_url
redirect_to locomotive_pages_url
end
end
protected
def set_current_thread_variables
Thread.current[:admin] = current_admin
Thread.current[:account] = current_account
Thread.current[:site] = current_site
end
def current_ability
@current_ability ||= Ability.new(current_admin, current_site)
@current_ability ||= Ability.new(current_account, current_site)
end
def require_admin
authenticate_admin!
def require_account
authenticate_account!
end
def begin_of_association_chain
@ -78,7 +78,7 @@ module Admin
end
def set_locale
I18n.locale = current_admin.locale rescue Locomotive.config.default_locale
I18n.locale = current_account.locale rescue Locomotive.config.default_locale
end
# ___ site/page urls builder ___

View File

@ -1,10 +1,10 @@
module Admin
module Locomotive
class ContentTypesController < BaseController
sections 'contents'
def destroy
destroy! { admin_pages_url }
destroy! { locomotive_pages_url }
end
end

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class ContentsController < BaseController
sections 'contents'
@ -36,11 +36,11 @@ module Admin
def sort
@content_type.sort_contents!(params[:children])
respond_with(@content_type, :location => admin_contents_url(@content_type.slug))
respond_with(@content_type, :location => locomotive_contents_url(@content_type.slug))
end
def destroy
destroy! { admin_contents_url(@content_type.slug) }
destroy! { locomotive_contents_url(@content_type.slug) }
end
protected
@ -55,7 +55,7 @@ module Admin
def after_create_or_update_url
if params[:breadcrumb_alias].blank?
edit_admin_content_url(@content_type.slug, @content.id)
edit_locomotive_content_url(@content_type.slug, @content.id)
else
self.breadcrumb_url
end
@ -72,11 +72,11 @@ module Admin
end
def breadcrumb_url
edit_admin_content_url(self.breadcrumb_root._parent.slug, self.breadcrumb_root)
edit_locomotive_content_url(self.breadcrumb_root._parent.slug, self.breadcrumb_root)
end
def back_url
self.breadcrumb_root ? self.breadcrumb_url : admin_contents_url(@content_type.slug)
self.breadcrumb_root ? self.breadcrumb_url : locomotive_contents_url(@content_type.slug)
end
end

View File

@ -1,25 +1,25 @@
module Admin
module Locomotive
class CrossDomainSessionsController < BaseController
layout '/admin/layouts/box'
layout '/locomotive/layouts/box'
skip_before_filter :verify_authenticity_token
skip_before_filter :validate_site_membership
before_filter :require_admin, :only => :new
before_filter :require_account, :only => :new
skip_load_and_authorize_resource
def new
if site = current_admin.sites.detect { |s| s._id.to_s == params[:target_id] }
if site = current_account.sites.detect { |s| s._id.to_s == params[:target_id] }
if Rails.env == 'development'
@target = site.full_subdomain
else
@target = site.domains_without_subdomain.first || site.full_subdomain
end
current_admin.reset_switch_site_token!
current_account.reset_switch_site_token!
else
redirect_to admin_pages_path
end
@ -31,7 +31,7 @@ module Admin
sign_in(account)
redirect_to admin_pages_path
else
redirect_to new_admin_session_path, :alert => t('flash.admin.cross_domain_sessions.create.alert')
redirect_to new_admin_session_path, :alert => t('fash.locomotive.cross_domain_sessions.create.alert')
end
end

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class CurrentSiteController < BaseController
defaults :instance_name => 'site'
@ -15,7 +15,7 @@ module Admin
def update
update! do |success, failure|
success.html { redirect_to edit_admin_current_site_url(new_host_if_subdomain_changed) }
success.html { redirect_to edit_locomotive_current_site_url(new_host_if_subdomain_changed) }
end
end

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class CustomFieldsController < BaseController
layout false
@ -18,7 +18,7 @@ module Admin
if @field.update_attributes(params[:custom_field])
render :json => @field.to_json
else
render :json => { :error => t('flash.admin.custom_fields.update.alert') }
render :json => { :error => t('flash.locomotive.custom_fields.update.alert') }
end
end

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class ExportController < BaseController
skip_load_and_authorize_resource

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class ImportController < BaseController
sections 'settings', 'site'
@ -14,7 +14,7 @@ module Admin
respond_to do |format|
format.html do
redirect_to new_admin_import_url if @job.nil?
redirect_to new_locomotive_import_url if @job.nil?
end
format.json { render :json => {
:step => @job.nil? ? 'done' : @job.step,
@ -32,14 +32,14 @@ module Admin
:reset => Boolean.set(params[:reset])
})
flash[:notice] = t("flash.admin.import.create.#{Locomotive.config.delayed_job ? 'notice' : 'done'}")
flash[:notice] = t("fash.locomotive.import.create.#{Locomotive.config.delayed_job ? 'notice' : 'done'}")
redirect_to Locomotive.config.delayed_job ? admin_import_url : new_admin_import_url
redirect_to Locomotive.config.delayed_job ? locomotive_import_url : new_locomotive_import_url
rescue Exception => e
logger.error "[Locomotive import] #{e.message} / #{e.backtrace}"
@error = e.message
flash[:alert] = t('flash.admin.import.create.alert')
flash[:alert] = t('fash.locomotive.import.create.alert')
render 'new'
end

View File

@ -1,11 +1,11 @@
module Admin
module Locomotive
class InstallationController < BaseController
layout '/admin/layouts/box'
layout '/locomotive/layouts/box'
skip_before_filter :require_site
skip_before_filter :require_admin
skip_before_filter :require_account
skip_before_filter :verify_authenticity_token
@ -36,7 +36,7 @@ module Admin
when 1 # create account
@account = Account.create(params[:account])
if @account.valid?
redirect_to admin_installation_step_url(2)
redirect_to locomotive_installation_step_url(2)
else
render 'step_1'
end
@ -58,7 +58,7 @@ module Admin
case params[:step].to_i
when 1 # already an account in db
if account = Account.first
@step_done = I18n.t('admin.installation.step_1.done', :name => account.name, :email => account.email)
@step_done = I18n.t('locomotive.installation.step_1.done', :name => account.name, :email => account.email)
render 'step_1' and return false
end
else
@ -67,14 +67,14 @@ module Admin
end
def allow_installation?
redirect_to admin_pages_url if Site.count > 0 && Account.count > 0
redirect_to locomotive_pages_url if Site.count > 0 && Account.count > 0
end
def last_url
if Locomotive.config.manage_domains?
admin_session_url(:host => Site.first.domains.first, :port => request.port)
locomotive_session_url(:host => Site.first.domains.first, :port => request.port)
else
admin_session_url
locomotive_session_url
end
end

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class MembershipsController < BaseController
sections 'settings'
@ -8,18 +8,18 @@ module Admin
case resource.process!
when :create_account
redirect_to new_admin_account_url(:email => resource.email)
redirect_to new_locomotive_account_url(:email => resource.email)
when :save_it
respond_with resource, :location => edit_admin_current_site_url
respond_with resource, :location => edit_locomotive_current_site_url
when :error
respond_with resource, :flash => true
when :already_created
respond_with resource, :alert => t('flash.admin.memberships.create.already_created'), :location => edit_admin_current_site_url
respond_with resource, :alert => t('flash.locomotive.memberships.create.already_created'), :location => edit_locomotive_current_site_url
end
end
def destroy
destroy! { edit_admin_current_site_url }
destroy! { edit_locomotive_current_site_url }
end
end

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class MyAccountController < BaseController
sections 'settings', 'account'
@ -10,13 +10,13 @@ module Admin
skip_load_and_authorize_resource
def update
update! { edit_admin_my_account_url }
update! { edit_locomotive_my_account_url }
end
protected
def resource
@account = current_admin
@account = current_account
end
def begin_of_association_chain; nil; end # not related directly to current_site

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class PagesController < BaseController
sections 'contents'
@ -17,9 +17,9 @@ module Admin
update! do |success, failure|
success.json do
render :json => {
:notice => t('flash.admin.pages.update.notice'),
:notice => t('flash.locomotive.pages.update.notice'),
:editable_elements => @page.template_changed ?
render_to_string(:partial => 'admin/pages/editable_elements.html.haml') : ''
render_to_string(:partial => 'locomotive/pages/editable_elements.html.haml') : ''
}
end
end

View File

@ -1,13 +1,13 @@
module Admin
module Locomotive
class PasswordsController < Devise::PasswordsController
include Locomotive::Routing::SiteDispatcher
layout '/admin/layouts/box'
layout '/locomotive/layouts/box'
before_filter :require_site
helper 'admin/base', 'admin/box'
helper 'locomotive/base', 'locomotive/box'
end
end

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class RenderingController < ActionController::Base
include Locomotive::Routing::SiteDispatcher

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class RobotsController < ActionController::Base
include Locomotive::Routing::SiteDispatcher

View File

@ -1,18 +1,18 @@
module Admin
module Locomotive
class SessionsController < Devise::SessionsController
include Locomotive::Routing::SiteDispatcher
layout '/admin/layouts/box'
layout '/locomotive/layouts/box'
before_filter :require_site
helper 'admin/base', 'admin/box'
helper 'locomotive/base', 'locomotive/box'
protected
def after_sign_in_path_for(resource)
admin_pages_url
locomotive_pages_url
end
def after_sign_out_path_for(resource)

View File

@ -1,7 +1,7 @@
module Admin
module Locomotive
class SitemapsController < BaseController
skip_before_filter :require_admin, :validate_site_membership, :set_locale
skip_before_filter :require_account, :validate_site_membership, :set_locale
before_filter :require_site

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class SitesController < BaseController
defaults :instance_name => 'site'
@ -7,13 +7,13 @@ module Admin
def create
@site = Site.new(params[:site])
@site.memberships.build :account => @current_admin, :role => 'admin'
@site.memberships.build :account => @current_account, :role => 'admin'
create! { edit_admin_my_account_url }
create! { edit_locomotive_my_account_url }
end
def destroy
@site = current_admin.sites.find(params[:id])
@site = current_account.sites.find(params[:id])
if @site != current_site
@site.destroy
@ -21,7 +21,7 @@ module Admin
@site.errors.add(:base, 'Can not destroy the site you are logging in now')
end
respond_with @site, :location => edit_admin_my_account_url
respond_with @site, :location => edit_locomotive_my_account_url
end
protected

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class SnippetsController < BaseController
sections 'settings', 'theme_assets'
@ -7,7 +7,7 @@ module Admin
def destroy
destroy! do |format|
format.html { redirect_to admin_theme_assets_url }
format.html { redirect_to locomotive_theme_assets_url }
end
end

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class ThemeAssetsController < BaseController
include ActionView::Helpers::SanitizeHelper

View File

@ -1,7 +1,7 @@
module Admin::AccountsHelper
module Locomotive::AccountsHelper
def admin_on?(site = current_site)
site.memberships.detect { |m| m.admin? && m.account == current_admin }
site.memberships.detect { |m| m.admin? && m.account == current_account }
end
end

View File

@ -1,4 +1,4 @@
module Admin::AssetsHelper
module Locomotive::AssetsHelper
def vignette_tag(asset)
if asset.image?

View File

@ -1,4 +1,4 @@
module Admin::BaseHelper
module Locomotive::BaseHelper
def title(title = nil)
if title.nil?
@ -15,7 +15,7 @@ module Admin::BaseHelper
css = "#{'on' if name == sections(:sub)} #{options[:css]}"
label_link = default_options[:i18n] ? t("admin.shared.menu.#{name}") : name
label_link = default_options[:i18n] ? t("locomotive.shared.menu.#{name}") : name
if block_given?
popup = content_tag(:div, capture(&block), :class => 'popup', :style => 'display: none')
link = link_to(content_tag(:span, preserve(label_link) + content_tag(:em)) + content_tag(:em), url, :class => css)

View File

@ -1,4 +1,4 @@
module Admin::BoxHelper
module Locomotive::BoxHelper
def box_flash_message
if not flash.empty?
@ -15,7 +15,7 @@ module Admin::BoxHelper
end
def next_installation_step_link(step = 1, label = nil)
link_to(content_tag(:span, label || t('admin.installation.common.next')), admin_installation_step_url(step), :class => 'button')
link_to(content_tag(:span, label || t('locomotive.installation.common.next')), locomotive_installation_step_url(step), :class => 'button')
end
end

View File

@ -1,4 +1,4 @@
module Admin::ContentTypesHelper
module Locomotive::ContentTypesHelper
MAX_DISPLAYED_CONTENTS = 4
@ -42,7 +42,7 @@ module Admin::ContentTypesHelper
item_on = (content_type.slug == @content_type.slug) rescue nil
label = truncate(content_type.name, :length => 15)
url = admin_contents_url(content_type.slug)
url = locomotive_contents_url(content_type.slug)
css = @content_type && content_type.slug == @content_type.slug ? 'on' : ''
html = admin_content_menu_item(label, url, :i18n => false, :css => css) do
@ -79,7 +79,7 @@ module Admin::ContentTypesHelper
registers = {
:controller => self,
:site => current_site,
:current_admin => current_admin
:current_account => current_account
}
preserve(content._parent.item_template.render(::Liquid::Context.new({}, assigns, registers)))

View File

@ -1,4 +1,4 @@
module Admin::CustomFieldsHelper
module Locomotive::CustomFieldsHelper
def options_for_field_kind
%w(string text category boolean date file has_one has_many).map do |kind|
@ -8,14 +8,14 @@ module Admin::CustomFieldsHelper
def options_for_order_by(content_type, collection_name)
options = %w{created_at updated_at _position_in_list}.map do |type|
[t("admin.content_types.form.order_by.#{type.gsub(/^_/, '')}"), type]
[t("locomotive.content_types.form.order_by.#{type.gsub(/^_/, '')}"), type]
end
options + options_for_highlighted_field(content_type, collection_name)
end
def options_for_order_direction
%w(asc desc).map do |direction|
[t("admin.content_types.form.order_direction.#{direction}"), direction]
[t("locomotive.content_types.form.order_direction.#{direction}"), direction]
end
end
@ -35,7 +35,7 @@ module Admin::CustomFieldsHelper
def options_for_text_formatting
options = %w(none html).map do |option|
[t("admin.custom_fields.text_formatting.#{option}"), option]
[t("locomotive.custom_fields.text_formatting.#{option}"), option]
end
end
@ -128,10 +128,10 @@ module Admin::CustomFieldsHelper
options.merge!(
:new_item => {
:label => t('admin.contents.form.has_many.new_item'),
:url => new_admin_content_url(field.target_klass._parent.slug, url_options)
:label => t('locomotive.contents.form.has_many.new_item'),
:url => new_locomotive_content_url(field.target_klass._parent.slug, url_options)
},
:edit_item_url => edit_admin_content_url(field.target_klass._parent.slug, 42, url_options)
:edit_item_url => edit_locomotive_content_url(field.target_klass._parent.slug, 42, url_options)
)
end

View File

@ -1,4 +1,4 @@
module Admin::PagesHelper
module Locomotive::PagesHelper
def parent_pages_options
roots = current_site.pages.roots.where(:slug.ne => '404').and(:_id.ne => @page.id)

View File

@ -1,4 +1,4 @@
module Admin::SitesHelper
module Locomotive::SitesHelper
def application_domain
domain = Locomotive.config.domain

View File

@ -1,4 +1,4 @@
module Admin
module Locomotive
class Notifications < ActionMailer::Base
default :from => Locomotive.config.mailer_sender
@ -6,7 +6,7 @@ module Admin
def new_content_instance(account, content)
@account, @content = account, content
subject = t('admin.notifications.new_content_instance.subject', :type => content.content_type.name, :locale => account.locale)
subject = t('locomotive.notifications.new_content_instance.subject', :type => content.content_type.name, :locale => account.locale)
mail :subject => subject, :to => account.email
end

View File

@ -1,83 +0,0 @@
class Ability
include CanCan::Ability
ROLES = %w(admin designer author)
def initialize(account, site)
@account, @site = account, site
alias_action :index, :show, :edit, :update, :to => :touch
@membership = @site.memberships.where(:account_id => @account.id).first
return false if @membership.blank?
if @membership.admin?
setup_admin_permissions!
else
setup_default_permissions!
setup_designer_permissions! if @membership.designer?
setup_author_permissions! if @membership.author?
end
end
def setup_default_permissions!
cannot :manage, :all
end
def setup_author_permissions!
can :touch, [Page, ThemeAsset]
can :sort, Page
can :manage, [ContentInstance, Asset]
can :touch, Site do |site|
site == @site
end
end
def setup_designer_permissions!
can :manage, Page
can :manage, ContentInstance
can :manage, ContentType
can :manage, Snippet
can :manage, ThemeAsset
can :manage, Asset
can :manage, Site do |site|
site == @site
end
can :import, Site
can :export, Site
can :point, Site
cannot :create, Site
can :manage, Membership
cannot :grant_admin, Membership
cannot [:update, :destroy], Membership do |membership|
@membership.account_id == membership.account_id || # can not edit myself
membership.admin? # can not modify an administrator
end
end
def setup_admin_permissions!
can :manage, :all
cannot [:update, :destroy], Membership do |membership|
@membership.account_id == membership.account_id # can not edit myself
end
end
end

View File

@ -1,60 +0,0 @@
require 'digest'
class Account
include Locomotive::Mongoid::Document
devise *Locomotive.config.devise_modules
## attributes ##
field :name
field :locale, :default => Locomotive.config.default_locale.to_s or 'en'
field :switch_site_token
## validations ##
validates_presence_of :name
## associations ##
## callbacks ##
before_destroy :remove_memberships!
## methods ##
def sites
@sites ||= Site.where({ 'memberships.account_id' => self._id })
end
def reset_switch_site_token!
self.switch_site_token = ActiveSupport::SecureRandom.base64(8).gsub("/", "_").gsub(/=+$/, "")
self.save
end
def self.find_using_switch_site_token(token, age = 1.minute)
return if token.blank?
self.where(:switch_site_token => token, :updated_at.gt => age.ago.utc).first
end
def self.find_using_switch_site_token!(token, age = 1.minute)
self.find_using_switch_site_token(token, age) || raise(Mongoid::Errors::DocumentNotFound.new(self, token))
end
protected
def password_required?
!persisted? || !password.blank? || !password_confirmation.blank?
end
def remove_memberships!
self.sites.each do |site|
membership = site.memberships.where(:account_id => self._id).first
if site.admin_memberships.size == 1 && membership.admin?
raise I18n.t('errors.messages.needs_admin_account')
else
membership.destroy
end
end
end
end

View File

@ -1,39 +0,0 @@
class Asset
include Mongoid::Document
include Mongoid::Timestamps
## extensions ##
include Extensions::Asset::Types
include Extensions::Asset::Vignette
## fields ##
field :content_type, :type => String
field :width, :type => Integer
field :height, :type => Integer
field :size, :type => Integer
field :position, :type => Integer, :default => 0
mount_uploader :source, AssetUploader
## associations ##
referenced_in :site
## validations ##
validates_presence_of :source
## behaviours ##
## methods ##
alias :name :source_filename
def extname
return nil unless self.source?
File.extname(self.source_filename).gsub(/^\./, '')
end
def to_liquid
{ :url => self.source.url }.merge(self.attributes).stringify_keys
end
end

View File

@ -1,125 +0,0 @@
class ContentInstance
include Mongoid::Document
include Mongoid::Timestamps
## extensions ##
include CustomFields::ProxyClassEnabler
include Extensions::Shared::Seo
## fields (dynamic fields) ##
field :_slug
field :_position_in_list, :type => Integer, :default => 0
field :_visible, :type => Boolean, :default => true
## validations ##
validate :require_highlighted_field
validate :validate_uniqueness_of_slug
validates_presence_of :_slug
## associations ##
embedded_in :content_type, :inverse_of => :contents
## callbacks ##
before_validation :set_slug
before_save :set_visibility
before_create :add_to_list_bottom
after_create :send_notifications
## named scopes ##
scope :latest_updated, :order_by => :updated_at.desc, :limit => Locomotive.config.lastest_items_nb
## methods ##
delegate :site, :to => :content_type
alias :visible? :_visible?
alias :_permalink :_slug
alias :_permalink= :_slug=
def site_id # needed by the uploader of custom fields
self.content_type.site_id
end
def highlighted_field_value
self.send(self.content_type.highlighted_field_name)
end
alias :_label :highlighted_field_value
def visible?
self._visible || self._visible.nil?
end
def next
content_type.contents.where(:_position_in_list => _position_in_list + 1).first()
end
def previous
content_type.contents.where(:_position_in_list => _position_in_list - 1).first()
end
def errors_to_hash
Hash.new.replace(self.errors)
end
def reload_parent!
self.class.reload_parent!
end
def self.reload_parent!
self._parent = self._parent.reload
end
def to_liquid
Locomotive::Liquid::Drops::Content.new(self)
end
protected
def set_slug
self._slug = self.highlighted_field_value.dup if self._slug.blank? && self.highlighted_field_value.present?
self._slug.permalink! if self._slug.present?
end
def set_visibility
field = self.content_type.content_custom_fields.detect { |f| %w{visible active}.include?(f._alias) }
self._visible = self.send(field._name) rescue true
end
def add_to_list_bottom
self._position_in_list = self.content_type.contents.size
end
def require_highlighted_field
_alias = self.highlighted_field_alias
if self.send(_alias).blank?
self.errors.add(_alias, :blank)
end
end
def validate_uniqueness_of_slug
if self._parent.contents.any? { |c| c._id != self._id && c._slug == self._slug }
self.errors.add(:_slug, :taken)
end
end
def highlighted_field_alias
self.content_type.highlighted_field._alias.to_sym
end
def send_notifications
return unless self.content_type.api_enabled? && !self.content_type.api_accounts.blank?
accounts = self.content_type.site.accounts.to_a
self.content_type.api_accounts.each do |account_id|
next if account_id.blank?
account = accounts.detect { |a| a.id.to_s == account_id.to_s }
Admin::Notifications.new_content_instance(account, self).deliver
end
end
end

View File

@ -1,145 +0,0 @@
class ContentType
include Locomotive::Mongoid::Document
## extensions ##
include Extensions::ContentType::ItemTemplate
## fields ##
field :name
field :description
field :slug
field :order_by
field :order_direction, :default => 'asc'
field :highlighted_field_name
field :group_by_field_name
field :api_enabled, :type => Boolean, :default => false
field :api_accounts, :type => Array
## associations ##
referenced_in :site
embeds_many :contents, :class_name => 'ContentInstance', :validate => false do
def visible
@target.find_all { |c| c.visible? }
end
end
## named scopes ##
scope :ordered, :order_by => :updated_at.desc
## indexes ##
index [[:site_id, Mongo::ASCENDING], [:slug, Mongo::ASCENDING]]
## callbacks ##
before_validation :normalize_slug
before_save :set_default_values
after_destroy :remove_uploaded_files
## validations ##
validates_presence_of :site, :name, :slug
validates_uniqueness_of :slug, :scope => :site_id
validates_size_of :content_custom_fields, :minimum => 1, :message => :array_too_short
## behaviours ##
custom_fields_for :contents
## methods ##
def groupable?
self.group_by_field && group_by_field.category?
end
def order_manually?
self.order_by == '_position_in_list'
end
def asc_order?
self.order_direction.blank? || self.order_direction == 'asc'
end
def list_or_group_contents
if self.groupable?
groups = self.contents.klass.send(:"group_by_#{self.group_by_field._alias}", :ordered_contents)
# look for items with no category or unknown ones
items_without_category = self.contents.find_all { |c| !self.group_by_field.category_ids.include?(c.send(self.group_by_field_name)) }
if not items_without_category.empty?
groups << { :name => nil, :items => items_without_category }
else
groups
end
else
self.ordered_contents
end
end
def latest_updated_contents
self.contents.latest_updated.reject { |c| !c.persisted? }
end
def ordered_contents(conditions = {})
column = self.order_by.to_sym
list = (if conditions.nil? || conditions.empty?
self.contents
else
conditions_with_names = {}
conditions.each do |key, value|
# convert alias (key) to name
field = self.content_custom_fields.detect { |f| f._alias == key }
case field.kind.to_sym
when :category
if (category_item = field.category_items.where(:name => value).first).present?
conditions_with_names[field._name.to_sym] = category_item._id
end
else
conditions_with_names[field._name.to_sym] = value
end
end
self.contents.where(conditions_with_names)
end).sort { |a, b| (a.send(column) || 0) <=> (b.send(column) || 0) }
return list if self.order_manually?
self.asc_order? ? list : list.reverse
end
def sort_contents!(ids)
ids.each_with_index do |id, position|
self.contents.find(BSON::ObjectId(id))._position_in_list = position
end
self.save
end
def highlighted_field
self.content_custom_fields.detect { |f| f._name == self.highlighted_field_name }
end
def group_by_field
@group_by_field ||= self.content_custom_fields.detect { |f| f._name == self.group_by_field_name }
end
protected
def set_default_values
self.order_by ||= 'created_at'
self.highlighted_field_name ||= self.content_custom_fields.first._name
end
def normalize_slug
self.slug = self.name.clone if self.slug.blank? && self.name.present?
self.slug.permalink! if self.slug.present?
end
def remove_uploaded_files # callbacks are not called on each content so we do it manually
self.contents.each do |content|
self.content_custom_fields.each do |field|
content.send(:"remove_#{field._name}!") if field.kind == 'file'
end
end
end
end

View File

@ -1,27 +0,0 @@
class EditableElement
include Mongoid::Document
## fields ##
field :slug
field :block
field :default_content
field :default_attribute
field :hint
field :priority, :type => Integer, :default => 0
field :disabled, :type => Boolean, :default => false
field :assignable, :type => Boolean, :default => true
field :from_parent, :type => Boolean, :default => false
## associations ##
embedded_in :page, :inverse_of => :editable_elements
## validations ##
validates_presence_of :slug
## scopes ##
scope :by_priority, :order_by => [[:priority, :desc]]
## methods ##
end

View File

@ -1,9 +0,0 @@
class EditableFile < EditableElement
mount_uploader :source, EditableFileUploader
def content
self.source? ? self.source.url : self.default_content
end
end

View File

@ -1,3 +0,0 @@
class EditableLongText < EditableShortText
end

View File

@ -1,12 +0,0 @@
class EditableShortText < EditableElement
## fields ##
field :content
## methods ##
def content
self.read_attribute(:content).blank? ? self.default_content : self.read_attribute(:content)
end
end

View File

@ -1,19 +0,0 @@
module Extensions
module Asset
module Types
extend ActiveSupport::Concern
included do
%w{media image stylesheet javascript font pdf}.each do |type|
scope :"only_#{type}", where(:content_type => type)
define_method("#{type}?") do
self.content_type.to_s == type
end
end
end
end
end
end

View File

@ -1,17 +0,0 @@
module Extensions
module Asset
module Vignette
def vignette_url
if self.image?
if self.width < 80 && self.height < 80
self.source.url
else
Locomotive::Dragonfly.resize_url(self.source, '80x80#')
end
end
end
end
end
end

View File

@ -1,49 +0,0 @@
module Extensions
module ContentType
module ItemTemplate
extend ActiveSupport::Concern
included do
field :raw_item_template
field :serialized_item_template, :type => Binary
before_validation :serialize_item_template
validate :item_template_must_be_valid
end
module InstanceMethods
def item_template
@item_template ||= Marshal.load(read_attribute(:serialized_item_template).to_s) rescue nil
end
protected
def serialize_item_template
if self.new_record? || self.raw_item_template_changed?
@item_parsing_errors = []
begin
self._parse_and_serialize_item_template
rescue ::Liquid::SyntaxError => error
@item_parsing_errors << I18n.t(:liquid_syntax, :error => error.to_s, :scope => [:errors, :messages])
end
end
end
def _parse_and_serialize_item_template
item_template = ::Liquid::Template.parse(self.raw_item_template, {})
self.serialized_item_template = BSON::Binary.new(Marshal.dump(item_template))
end
def item_template_must_be_valid
@item_parsing_errors.try(:each) { |msg| self.errors.add :item_template, msg }
end
end
end
end
end

View File

@ -1,118 +0,0 @@
module Extensions
module Page
module EditableElements
extend ActiveSupport::Concern
included do
embeds_many :editable_elements
after_save :remove_disabled_editable_elements
# editable file callbacks
after_save :store_file_sources!
before_save :write_file_source_identifiers
after_destroy :remove_file_sources!
accepts_nested_attributes_for :editable_elements
end
module InstanceMethods
def disable_parent_editable_elements(block)
self.editable_elements.each { |el| el.disabled = true if el.from_parent? && el.block == block }
end
def disable_all_editable_elements
self.editable_elements.each { |el| el.disabled = true }
end
def editable_element_blocks
self.editable_elements.collect(&:block)
end
def editable_elements_grouped_by_blocks
all_enabled = self.editable_elements.by_priority.reject { |el| el.disabled? }
groups = all_enabled.group_by(&:block)
groups.delete_if { |block, elements| elements.empty? }
end
def find_editable_element(block, slug)
self.editable_elements.detect { |el| el.block == block && el.slug == slug }
end
def find_editable_files
self.editable_elements.find_all { |el| el.respond_to?(:source) }
end
def add_or_update_editable_element(attributes, type)
element = self.find_editable_element(attributes[:block], attributes[:slug])
if element
element.attributes = attributes
else
self.editable_elements.build(attributes, type)
end
end
def enable_editable_elements(block)
self.editable_elements.each { |el| el.disabled = false if el.block == block }
end
def merge_editable_elements_from_page(source)
source.editable_elements.each do |el|
next if el.disabled? or !el.assignable?
existing_el = self.find_editable_element(el.block, el.slug)
if existing_el.nil? # new one from parents
new_attributes = el.attributes.merge(:from_parent => true)
if new_attributes['default_attribute'].present?
new_attributes['default_content'] = self.send(new_attributes['default_attribute']) || el.content
else
if el.respond_to?(:content) # only for text
new_attributes['default_content'] = el.content
end
end
self.editable_elements.build(new_attributes, el.class)
elsif existing_el.default_attribute.nil?
existing_el.attributes = { :disabled => false, :default_content => el.content }
else
existing_el.attributes = { :disabled => false }
end
end
end
def remove_disabled_editable_elements
return unless self.editable_elements.any? { |el| el.disabled? }
# super fast way to remove useless elements all in once (TODO callbacks)
self.collection.update(self._selector, '$pull' => { 'editable_elements' => { 'disabled' => true } })
end
protected
## callbacks for editable files
# equivalent to "after_save :store_source!" in EditableFile
def store_file_sources!
self.find_editable_files.collect(&:store_source!)
end
# equivalent to "before_save :write_source_identifier" in EditableFile
def write_file_source_identifiers
self.find_editable_files.collect(&:write_source_identifier)
end
# equivalent to "after_destroy :remove_source!" in EditableFile
def remove_file_sources!
self.find_editable_files.collect(&:remove_source!)
end
end
end
end
end

View File

@ -1,15 +0,0 @@
module Extensions
module Page
module Listed
extend ActiveSupport::Concern
included do
field :listed, :type => Boolean, :default => true
end
end
end
end

View File

@ -1,111 +0,0 @@
module Extensions
module Page
module Parse
extend ActiveSupport::Concern
included do
field :serialized_template, :type => Binary
field :template_dependencies, :type => Array, :default => []
field :snippet_dependencies, :type => Array, :default => []
attr_reader :template_changed
before_validation :serialize_template
after_save :update_template_descendants
validate :template_must_be_valid
scope :pages, lambda { |domain| { :any_in => { :domains => [*domain] } } }
end
module InstanceMethods
def template
@template ||= Marshal.load(read_attribute(:serialized_template).to_s) rescue nil
end
protected
def serialize_template
if self.new_record? || self.raw_template_changed?
@template_changed = true
@parsing_errors = []
begin
self._parse_and_serialize_template
rescue ::Liquid::SyntaxError => error
@parsing_errors << I18n.t(:liquid_syntax, :fullpath => self.fullpath, :error => error.to_s, :scope => [:errors, :messages, :page])
rescue ::Locomotive::Liquid::PageNotFound => error
@parsing_errors << I18n.t(:liquid_extend, :fullpath => self.fullpath, :scope => [:errors, :messages, :page])
end
end
end
def _parse_and_serialize_template(context = {})
self.parse(context)
self._serialize_template
end
def _serialize_template
self.serialized_template = BSON::Binary.new(Marshal.dump(@template))
end
def parse(context = {})
self.disable_all_editable_elements
default_context = { :site => self.site, :page => self, :templates => [], :snippets => [] }
context = default_context.merge(context)
@template = ::Liquid::Template.parse(self.raw_template, context)
self.template_dependencies = context[:templates]
self.snippet_dependencies = context[:snippets]
@template.root.context.clear
end
def template_must_be_valid
@parsing_errors.try(:each) { |msg| self.errors.add :template, msg }
end
def update_template_descendants
return unless @template_changed == true
# we admit at this point that the current template is up-to-date
template_descendants = self.site.pages.any_in(:template_dependencies => [self.id]).to_a
# group them by fullpath for better performance
cached = template_descendants.inject({}) { |memo, page| memo[page.fullpath] = page; memo }
self._update_direct_template_descendants(template_descendants.clone, cached)
# finally save them all
::Page.without_callback(:save, :after, :update_template_descendants) do
template_descendants.each do |page|
page.save(:validate => false)
end
end
end
def _update_direct_template_descendants(template_descendants, cached)
direct_descendants = template_descendants.select do |page|
((self.template_dependencies || []) + [self._id]) == (page.template_dependencies || [])
end
direct_descendants.each do |page|
page.send(:_parse_and_serialize_template, { :cached_parent => self, :cached_pages => cached })
template_descendants.delete(page) # no need to loop over it next time
page.send(:_update_direct_template_descendants, template_descendants, cached) # move down
end
end
end
end
end
end

View File

@ -1,21 +0,0 @@
module Extensions
module Page
module Redirect
extend ActiveSupport::Concern
included do
field :redirect, :type => Boolean, :default => false
field :redirect_url, :type => String
validates_presence_of :redirect_url, :if => :redirect
validates_format_of :redirect_url, :with => Locomotive::Regexps::URL, :allow_blank => true
end
end
end
end

View File

@ -1,11 +0,0 @@
module Extensions
module Page
module Render
def render(context)
self.template.render(context)
end
end
end
end

View File

@ -1,28 +0,0 @@
module Extensions
module Page
module Templatized
extend ActiveSupport::Concern
included do
referenced_in :content_type
field :templatized, :type => Boolean, :default => false
field :content_type_visible_column
before_validation :set_slug_if_templatized
end
module InstanceMethods
def set_slug_if_templatized
self.slug = 'content_type_template' if self.templatized?
end
end
end
end
end

View File

@ -1,158 +0,0 @@
module Extensions
module Page
module Tree
extend ActiveSupport::Concern
included do
include Mongoid::Acts::Tree
## fields ##
field :position, :type => Integer
## indexes ##
index :position
index [[:depth, :asc], [:position, :asc]]
## behaviours ##
acts_as_tree :order => ['position', 'asc']
## callbacks ##
before_validation :reset_parent
before_save { |p| p.send(:write_attribute, :parent_id, nil) if p.parent_id.blank? }
before_save :change_parent
before_create { |p| p.send(:fix_position, false) }
before_create :add_to_list_bottom
before_destroy :remove_from_list
# Fixme (Didier L.): Instances methods are defined before the include itself
alias :fix_position :hacked_fix_position
alias :descendants :hacked_descendants
end
module ClassMethods
# Warning: should be used only in read-only
def quick_tree(site, minimal_attributes = true)
pages = (minimal_attributes ? site.pages.minimal_attributes : site.pages).order_by([[:depth, :asc], [:position, :asc]]).to_a
tmp = []
while !pages.empty?
tmp << _quick_tree(pages.delete_at(0), pages)
end
tmp
end
def _quick_tree(current_page, pages)
i, children = 0, []
while !pages.empty?
page = pages[i]
break if page.nil?
if page.parent_id == current_page.id
page = pages.delete_at(i)
children << _quick_tree(page, pages)
else
i += 1
end
end
current_page.instance_eval do
def children=(list); @children = list; end
def children; @children || []; end
end
current_page.children = children
current_page
end
end
module InstanceMethods
def children?
self.class.where(self.parent_id_field => self.id).count
end
def children_with_minimal_attributes
self.class.where(self.parent_id_field => self.id).
order_by(self.tree_order).
minimal_attributes
end
def sort_children!(ids)
ids.each_with_index do |id, position|
child = self.children.detect { |p| p._id == BSON::ObjectId(id) }
child.position = position
child.save
end
end
def parent=(owner) # missing in acts_as_tree
@_parent = owner
self.fix_position(false)
self.instance_variable_set :@_will_move, true
end
def hacked_descendants
return [] if new_record?
self.class.all_in(path_field => [self._id]).order_by tree_order
end
protected
def change_parent
if self.parent_id_changed?
self.fix_position(false)
unless self.parent_id_was.nil?
self.position = nil # make it move to bottom
self.add_to_list_bottom
end
self.instance_variable_set :@_will_move, true
end
end
def hacked_fix_position(perform_save = true)
if parent.nil?
self.write_attribute parent_id_field, nil
self[path_field] = []
self[depth_field] = 0
else
self.write_attribute parent_id_field, parent._id
self[path_field] = parent[path_field] + [parent._id]
self[depth_field] = parent[depth_field] + 1
self.save if perform_save
end
end
def reset_parent
if self.parent_id_changed?
@_parent = nil
end
end
def add_to_list_bottom
self.position ||= (::Page.where(:_id.ne => self._id).and(:parent_id => self.parent_id).max(:position) || 0) + 1
end
def remove_from_list
return if (self.site rescue nil).nil?
::Page.where(:parent_id => self.parent_id).and(:position.gt => self.position).each do |p|
p.position -= 1
p.save
end
end
end
end
end
end

View File

@ -1,14 +0,0 @@
module Extensions
module Shared
module Seo
extend ActiveSupport::Concern
included do
field :seo_title, :type => String
field :meta_keywords, :type => String
field :meta_description, :type => String
end
end # Seo
end # Shared
end # Extensions

View File

@ -1,36 +0,0 @@
module Extensions
module Site
module FirstInstallation
# only called during the installation workflow, just after the admin account has been created
def create_first_one(attributes)
site = self.new(attributes)
site.memberships.build :account => Account.first, :role => 'admin'
site.save
site
end
def install_template(site, options = {})
default_template = Boolean.set(options.delete(:default_site_template)) || false
zipfile = options.delete(:zipfile)
# do not try to process anything if said so
return unless default_template || zipfile.present?
# default template options has a higher priority than the zipfile
source = default_template ? Locomotive.default_site_template_path : zipfile
begin
Locomotive::Import::Job.run!(source, site, { :samples => true })
rescue Exception => e
Rails.logger.error "The import of the site template failed because of #{e.message}"
end
end
end
end
end

View File

@ -1,84 +0,0 @@
module Extensions
module Site
module SubdomainDomains
def enable_subdomain_n_domains_if_multi_sites
# puts "multi_sites? #{Locomotive.config.multi_sites?} / manage_domains? #{Locomotive.config.manage_domains?} / heroku? #{Locomotive.heroku?} / bushido? #{Locomotive.bushido?}"
if Locomotive.config.multi_sites? || Locomotive.config.manage_domains?
## fields ##
field :subdomain
field :domains, :type => Array, :default => []
## indexes
index :domains
## validations ##
validates_presence_of :subdomain
validates_uniqueness_of :subdomain
validates_exclusion_of :subdomain, :in => Locomotive.config.reserved_subdomains
validates_format_of :subdomain, :with => Locomotive::Regexps::SUBDOMAIN, :allow_blank => true
validate :domains_must_be_valid_and_unique
## callbacks ##
before_save :add_subdomain_to_domains
## named scopes ##
scope :match_domain, lambda { |domain| { :any_in => { :domains => [*domain] } } }
scope :match_domain_with_exclusion_of, lambda { |domain, site|
{ :any_in => { :domains => [*domain] }, :where => { :_id.ne => site.id } }
}
send :include, InstanceMethods
end
end
module InstanceMethods
def domains=(array)
array = [] if array.blank?; super(array)
end
def add_subdomain_to_domains
self.domains ||= []
(self.domains << self.full_subdomain).uniq!
end
def domains_without_subdomain
(self.domains || []) - [self.full_subdomain_was] - [self.full_subdomain]
end
def domains_with_subdomain
((self.domains || []) + [self.full_subdomain]).uniq
end
def full_subdomain
"#{self.subdomain}.#{Locomotive.config.domain}"
end
def full_subdomain_was
"#{self.subdomain_was}.#{Locomotive.config.domain}"
end
protected
def domains_must_be_valid_and_unique
return if self.domains.empty?
self.domains_without_subdomain.each do |domain|
if self.class.match_domain_with_exclusion_of(domain, self).any?
self.errors.add(:domains, :domain_taken, :value => domain)
end
if not domain =~ Locomotive::Regexps::DOMAIN
self.errors.add(:domains, :invalid_domain, :value => domain)
end
end
end
end
end
end
end

View File

@ -0,0 +1,85 @@
Locomotive
class Ability
include CanCan::Ability
ROLES = %w(admin designer author)
def initialize(account, site)
@account, @site = account, site
alias_action :index, :show, :edit, :update, :to => :touch
@membership = @site.memberships.where(:account_id => @account.id).first
return false if @membership.blank?
if @membership.admin?
setup_admin_permissions!
else
setup_default_permissions!
setup_designer_permissions! if @membership.designer?
setup_author_permissions! if @membership.author?
end
end
def setup_default_permissions!
cannot :manage, :all
end
def setup_author_permissions!
can :touch, [Page, ThemeAsset]
can :sort, Page
can :manage, [ContentInstance, Asset]
can :touch, Site do |site|
site == @site
end
end
def setup_designer_permissions!
can :manage, Page
can :manage, ContentInstance
can :manage, ContentType
can :manage, Snippet
can :manage, ThemeAsset
can :manage, Asset
can :manage, Site do |site|
site == @site
end
can :import, Site
can :export, Site
can :point, Site
cannot :create, Site
can :manage, Membership
cannot :grant_admin, Membership
cannot [:update, :destroy], Membership do |membership|
@membership.account_id == membership.account_id || # can not edit myself
membership.admin? # can not modify an administrator
end
end
def setup_admin_permissions!
can :manage, :all
cannot [:update, :destroy], Membership do |membership|
@membership.account_id == membership.account_id # can not edit myself
end
end
end
end

View File

@ -0,0 +1,62 @@
require 'digest'
module Locomotive
class Account
include Locomotive::Mongoid::Document
devise *Locomotive.config.devise_modules
## attributes ##
field :name
field :locale, :default => Locomotive.config.default_locale.to_s or 'en'
field :switch_site_token
## validations ##
validates_presence_of :name
## associations ##
## callbacks ##
before_destroy :remove_memberships!
## methods ##
def sites
@sites ||= Site.where({ 'memberships.account_id' => self._id })
end
def reset_switch_site_token!
self.switch_site_token = ActiveSupport::SecureRandom.base64(8).gsub("/", "_").gsub(/=+$/, "")
self.save
end
def self.find_using_switch_site_token(token, age = 1.minute)
return if token.blank?
self.where(:switch_site_token => token, :updated_at.gt => age.ago.utc).first
end
def self.find_using_switch_site_token!(token, age = 1.minute)
self.find_using_switch_site_token(token, age) || raise(Mongoid::Errors::DocumentNotFound.new(self, token))
end
protected
def password_required?
!persisted? || !password.blank? || !password_confirmation.blank?
end
def remove_memberships!
self.sites.each do |site|
membership = site.memberships.where(:account_id => self._id).first
if site.admin_memberships.size == 1 && membership.admin?
raise I18n.t('errors.messages.needs_admin_account')
else
membership.destroy
end
end
end
end
end

View File

@ -0,0 +1,41 @@
module Locomotive
class Asset
include Mongoid::Document
include Mongoid::Timestamps
## extensions ##
include Extensions::Asset::Types
include Extensions::Asset::Vignette
## fields ##
field :content_type, :type => String
field :width, :type => Integer
field :height, :type => Integer
field :size, :type => Integer
field :position, :type => Integer, :default => 0
mount_uploader :source, AssetUploader
## associations ##
referenced_in :site
## validations ##
validates_presence_of :source
## behaviours ##
## methods ##
alias :name :source_filename
def extname
return nil unless self.source?
File.extname(self.source_filename).gsub(/^\./, '')
end
def to_liquid
{ :url => self.source.url }.merge(self.attributes).stringify_keys
end
end
end

View File

@ -0,0 +1,127 @@
module Locomotive
class ContentInstance
include Mongoid::Document
include Mongoid::Timestamps
## extensions ##
include CustomFields::ProxyClassEnabler
include Extensions::Shared::Seo
## fields (dynamic fields) ##
field :_slug
field :_position_in_list, :type => Integer, :default => 0
field :_visible, :type => Boolean, :default => true
## validations ##
validate :require_highlighted_field
validate :validate_uniqueness_of_slug
validates_presence_of :_slug
## associations ##
embedded_in :content_type, :inverse_of => :contents
## callbacks ##
before_validation :set_slug
before_save :set_visibility
before_create :add_to_list_bottom
after_create :send_notifications
## named scopes ##
scope :latest_updated, :order_by => :updated_at.desc, :limit => Locomotive.config.lastest_items_nb
## methods ##
delegate :site, :to => :content_type
alias :visible? :_visible?
alias :_permalink :_slug
alias :_permalink= :_slug=
def site_id # needed by the uploader of custom fields
self.content_type.site_id
end
def highlighted_field_value
self.send(self.content_type.highlighted_field_name)
end
alias :_label :highlighted_field_value
def visible?
self._visible || self._visible.nil?
end
def next
content_type.contents.where(:_position_in_list => _position_in_list + 1).first()
end
def previous
content_type.contents.where(:_position_in_list => _position_in_list - 1).first()
end
def errors_to_hash
Hash.new.replace(self.errors)
end
def reload_parent!
self.class.reload_parent!
end
def self.reload_parent!
self._parent = self._parent.reload
end
def to_liquid
Locomotive::Liquid::Drops::Content.new(self)
end
protected
def set_slug
self._slug = self.highlighted_field_value.dup if self._slug.blank? && self.highlighted_field_value.present?
self._slug.permalink! if self._slug.present?
end
def set_visibility
field = self.content_type.content_custom_fields.detect { |f| %w{visible active}.include?(f._alias) }
self._visible = self.send(field._name) rescue true
end
def add_to_list_bottom
self._position_in_list = self.content_type.contents.size
end
def require_highlighted_field
_alias = self.highlighted_field_alias
if self.send(_alias).blank?
self.errors.add(_alias, :blank)
end
end
def validate_uniqueness_of_slug
if self._parent.contents.any? { |c| c._id != self._id && c._slug == self._slug }
self.errors.add(:_slug, :taken)
end
end
def highlighted_field_alias
self.content_type.highlighted_field._alias.to_sym
end
def send_notifications
return unless self.content_type.api_enabled? && !self.content_type.api_accounts.blank?
accounts = self.content_type.site.accounts.to_a
self.content_type.api_accounts.each do |account_id|
next if account_id.blank?
account = accounts.detect { |a| a.id.to_s == account_id.to_s }
Locomotive::Notifications.new_content_instance(account, self).deliver
end
end
end
end

View File

@ -0,0 +1,147 @@
module Locomotive
class ContentType
include Locomotive::Mongoid::Document
## extensions ##
include Extensions::ContentType::ItemTemplate
## fields ##
field :name
field :description
field :slug
field :order_by
field :order_direction, :default => 'asc'
field :highlighted_field_name
field :group_by_field_name
field :api_enabled, :type => Boolean, :default => false
field :api_accounts, :type => Array
## associations ##
referenced_in :site
embeds_many :contents, :class_name => 'Locomotive::ContentInstance', :validate => false do
def visible
@target.find_all { |c| c.visible? }
end
end
## named scopes ##
scope :ordered, :order_by => :updated_at.desc
## indexes ##
index [[:site_id, Mongo::ASCENDING], [:slug, Mongo::ASCENDING]]
## callbacks ##
before_validation :normalize_slug
before_save :set_default_values
after_destroy :remove_uploaded_files
## validations ##
validates_presence_of :site, :name, :slug
validates_uniqueness_of :slug, :scope => :site_id
validates_size_of :content_custom_fields, :minimum => 1, :message => :array_too_short
## behaviours ##
custom_fields_for :contents
## methods ##
def groupable?
self.group_by_field && group_by_field.category?
end
def order_manually?
self.order_by == '_position_in_list'
end
def asc_order?
self.order_direction.blank? || self.order_direction == 'asc'
end
def list_or_group_contents
if self.groupable?
groups = self.contents.klass.send(:"group_by_#{self.group_by_field._alias}", :ordered_contents)
# look for items with no category or unknown ones
items_without_category = self.contents.find_all { |c| !self.group_by_field.category_ids.include?(c.send(self.group_by_field_name)) }
if not items_without_category.empty?
groups << { :name => nil, :items => items_without_category }
else
groups
end
else
self.ordered_contents
end
end
def latest_updated_contents
self.contents.latest_updated.reject { |c| !c.persisted? }
end
def ordered_contents(conditions = {})
column = self.order_by.to_sym
list = (if conditions.nil? || conditions.empty?
self.contents
else
conditions_with_names = {}
conditions.each do |key, value|
# convert alias (key) to name
field = self.content_custom_fields.detect { |f| f._alias == key }
case field.kind.to_sym
when :category
if (category_item = field.category_items.where(:name => value).first).present?
conditions_with_names[field._name.to_sym] = category_item._id
end
else
conditions_with_names[field._name.to_sym] = value
end
end
self.contents.where(conditions_with_names)
end).sort { |a, b| (a.send(column) || 0) <=> (b.send(column) || 0) }
return list if self.order_manually?
self.asc_order? ? list : list.reverse
end
def sort_contents!(ids)
ids.each_with_index do |id, position|
self.contents.find(BSON::ObjectId(id))._position_in_list = position
end
self.save
end
def highlighted_field
self.content_custom_fields.detect { |f| f._name == self.highlighted_field_name }
end
def group_by_field
@group_by_field ||= self.content_custom_fields.detect { |f| f._name == self.group_by_field_name }
end
protected
def set_default_values
self.order_by ||= 'created_at'
self.highlighted_field_name ||= self.content_custom_fields.first._name
end
def normalize_slug
self.slug = self.name.clone if self.slug.blank? && self.name.present?
self.slug.permalink! if self.slug.present?
end
def remove_uploaded_files # callbacks are not called on each content so we do it manually
self.contents.each do |content|
self.content_custom_fields.each do |field|
content.send(:"remove_#{field._name}!") if field.kind == 'file'
end
end
end
end
end

View File

@ -0,0 +1,29 @@
module Locomotive
class EditableElement
include Mongoid::Document
## fields ##
field :slug
field :block
field :default_content
field :default_attribute
field :hint
field :priority, :type => Integer, :default => 0
field :disabled, :type => Boolean, :default => false
field :assignable, :type => Boolean, :default => true
field :from_parent, :type => Boolean, :default => false
## associations ##
embedded_in :page, :inverse_of => :editable_elements
## validations ##
validates_presence_of :slug
## scopes ##
scope :by_priority, :order_by => [[:priority, :desc]]
## methods ##
end
end

View File

@ -0,0 +1,11 @@
module Locomotive
class EditableFile < EditableElement
mount_uploader :source, EditableFileUploader
def content
self.source? ? self.source.url : self.default_content
end
end
end

View File

@ -0,0 +1,5 @@
module Locomotive
class EditableLongText < EditableShortText
end
end

View File

@ -0,0 +1,14 @@
module Locomotive
class EditableShortText < EditableElement
## fields ##
field :content
## methods ##
def content
self.read_attribute(:content).blank? ? self.default_content : self.read_attribute(:content)
end
end
end

View File

@ -0,0 +1,21 @@
module Locomotive
module Extensions
module Asset
module Types
extend ActiveSupport::Concern
included do
%w{media image stylesheet javascript font pdf}.each do |type|
scope :"only_#{type}", where(:content_type => type)
define_method("#{type}?") do
self.content_type.to_s == type
end
end
end
end
end
end
end

View File

@ -0,0 +1,19 @@
module Locomotive
module Extensions
module Asset
module Vignette
def vignette_url
if self.image?
if self.width < 80 && self.height < 80
self.source.url
else
Locomotive::Dragonfly.resize_url(self.source, '80x80#')
end
end
end
end
end
end
end

View File

@ -0,0 +1,51 @@
module Locomotive
module Extensions
module ContentType
module ItemTemplate
extend ActiveSupport::Concern
included do
field :raw_item_template
field :serialized_item_template, :type => Binary
before_validation :serialize_item_template
validate :item_template_must_be_valid
end
module InstanceMethods
def item_template
@item_template ||= Marshal.load(read_attribute(:serialized_item_template).to_s) rescue nil
end
protected
def serialize_item_template
if self.new_record? || self.raw_item_template_changed?
@item_parsing_errors = []
begin
self._parse_and_serialize_item_template
rescue ::Liquid::SyntaxError => error
@item_parsing_errors << I18n.t(:liquid_syntax, :error => error.to_s, :scope => [:errors, :messages])
end
end
end
def _parse_and_serialize_item_template
item_template = ::Liquid::Template.parse(self.raw_item_template, {})
self.serialized_item_template = BSON::Binary.new(Marshal.dump(item_template))
end
def item_template_must_be_valid
@item_parsing_errors.try(:each) { |msg| self.errors.add :item_template, msg }
end
end
end
end
end
end

View File

@ -0,0 +1,120 @@
module Locomotive
module Extensions
module Page
module EditableElements
extend ActiveSupport::Concern
included do
embeds_many :editable_elements
after_save :remove_disabled_editable_elements
# editable file callbacks
after_save :store_file_sources!
before_save :write_file_source_identifiers
after_destroy :remove_file_sources!
accepts_nested_attributes_for :editable_elements
end
module InstanceMethods
def disable_parent_editable_elements(block)
self.editable_elements.each { |el| el.disabled = true if el.from_parent? && el.block == block }
end
def disable_all_editable_elements
self.editable_elements.each { |el| el.disabled = true }
end
def editable_element_blocks
self.editable_elements.collect(&:block)
end
def editable_elements_grouped_by_blocks
all_enabled = self.editable_elements.by_priority.reject { |el| el.disabled? }
groups = all_enabled.group_by(&:block)
groups.delete_if { |block, elements| elements.empty? }
end
def find_editable_element(block, slug)
self.editable_elements.detect { |el| el.block == block && el.slug == slug }
end
def find_editable_files
self.editable_elements.find_all { |el| el.respond_to?(:source) }
end
def add_or_update_editable_element(attributes, type)
element = self.find_editable_element(attributes[:block], attributes[:slug])
if element
element.attributes = attributes
else
self.editable_elements.build(attributes, type)
end
end
def enable_editable_elements(block)
self.editable_elements.each { |el| el.disabled = false if el.block == block }
end
def merge_editable_elements_from_page(source)
source.editable_elements.each do |el|
next if el.disabled? or !el.assignable?
existing_el = self.find_editable_element(el.block, el.slug)
if existing_el.nil? # new one from parents
new_attributes = el.attributes.merge(:from_parent => true)
if new_attributes['default_attribute'].present?
new_attributes['default_content'] = self.send(new_attributes['default_attribute']) || el.content
else
if el.respond_to?(:content) # only for text
new_attributes['default_content'] = el.content
end
end
self.editable_elements.build(new_attributes, el.class)
elsif existing_el.default_attribute.nil?
existing_el.attributes = { :disabled => false, :default_content => el.content }
else
existing_el.attributes = { :disabled => false }
end
end
end
def remove_disabled_editable_elements
return unless self.editable_elements.any? { |el| el.disabled? }
# super fast way to remove useless elements all in once (TODO callbacks)
self.collection.update(self._selector, '$pull' => { 'editable_elements' => { 'disabled' => true } })
end
protected
## callbacks for editable files
# equivalent to "after_save :store_source!" in EditableFile
def store_file_sources!
self.find_editable_files.collect(&:store_source!)
end
# equivalent to "before_save :write_source_identifier" in EditableFile
def write_file_source_identifiers
self.find_editable_files.collect(&:write_source_identifier)
end
# equivalent to "after_destroy :remove_source!" in EditableFile
def remove_file_sources!
self.find_editable_files.collect(&:remove_source!)
end
end
end
end
end
end

View File

@ -0,0 +1,17 @@
module Locomotive
module Extensions
module Page
module Listed
extend ActiveSupport::Concern
included do
field :listed, :type => Boolean, :default => true
end
end
end
end
end

View File

@ -0,0 +1,113 @@
module Locomotive
module Extensions
module Page
module Parse
extend ActiveSupport::Concern
included do
field :serialized_template, :type => Binary
field :template_dependencies, :type => Array, :default => []
field :snippet_dependencies, :type => Array, :default => []
attr_reader :template_changed
before_validation :serialize_template
after_save :update_template_descendants
validate :template_must_be_valid
scope :pages, lambda { |domain| { :any_in => { :domains => [*domain] } } }
end
module InstanceMethods
def template
@template ||= Marshal.load(read_attribute(:serialized_template).to_s) rescue nil
end
protected
def serialize_template
if self.new_record? || self.raw_template_changed?
@template_changed = true
@parsing_errors = []
begin
self._parse_and_serialize_template
rescue ::Liquid::SyntaxError => error
@parsing_errors << I18n.t(:liquid_syntax, :fullpath => self.fullpath, :error => error.to_s, :scope => [:errors, :messages, :page])
rescue ::Locomotive::Liquid::PageNotFound => error
@parsing_errors << I18n.t(:liquid_extend, :fullpath => self.fullpath, :scope => [:errors, :messages, :page])
end
end
end
def _parse_and_serialize_template(context = {})
self.parse(context)
self._serialize_template
end
def _serialize_template
self.serialized_template = BSON::Binary.new(Marshal.dump(@template))
end
def parse(context = {})
self.disable_all_editable_elements
default_context = { :site => self.site, :page => self, :templates => [], :snippets => [] }
context = default_context.merge(context)
@template = ::Liquid::Template.parse(self.raw_template, context)
self.template_dependencies = context[:templates]
self.snippet_dependencies = context[:snippets]
@template.root.context.clear
end
def template_must_be_valid
@parsing_errors.try(:each) { |msg| self.errors.add :template, msg }
end
def update_template_descendants
return unless @template_changed == true
# we admit at this point that the current template is up-to-date
template_descendants = self.site.pages.any_in(:template_dependencies => [self.id]).to_a
# group them by fullpath for better performance
cached = template_descendants.inject({}) { |memo, page| memo[page.fullpath] = page; memo }
self._update_direct_template_descendants(template_descendants.clone, cached)
# finally save them all
::Page.without_callback(:save, :after, :update_template_descendants) do
template_descendants.each do |page|
page.save(:validate => false)
end
end
end
def _update_direct_template_descendants(template_descendants, cached)
direct_descendants = template_descendants.select do |page|
((self.template_dependencies || []) + [self._id]) == (page.template_dependencies || [])
end
direct_descendants.each do |page|
page.send(:_parse_and_serialize_template, { :cached_parent => self, :cached_pages => cached })
template_descendants.delete(page) # no need to loop over it next time
page.send(:_update_direct_template_descendants, template_descendants, cached) # move down
end
end
end
end
end
end
end

View File

@ -0,0 +1,23 @@
module Locomotive
module Extensions
module Page
module Redirect
extend ActiveSupport::Concern
included do
field :redirect, :type => Boolean, :default => false
field :redirect_url, :type => String
validates_presence_of :redirect_url, :if => :redirect
validates_format_of :redirect_url, :with => Locomotive::Regexps::URL, :allow_blank => true
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Locomotive
module Extensions
module Page
module Render
def render(context)
self.template.render(context)
end
end
end
end
end

View File

@ -0,0 +1,30 @@
module Locomotive
module Extensions
module Page
module Templatized
extend ActiveSupport::Concern
included do
referenced_in :content_type
field :templatized, :type => Boolean, :default => false
field :content_type_visible_column
before_validation :set_slug_if_templatized
end
module InstanceMethods
def set_slug_if_templatized
self.slug = 'content_type_template' if self.templatized?
end
end
end
end
end
end

View File

@ -0,0 +1,160 @@
module Locomotive
module Extensions
module Page
module Tree
extend ActiveSupport::Concern
included do
include ::Mongoid::Acts::Tree
## fields ##
field :position, :type => Integer
## indexes ##
index :position
index [[:depth, :asc], [:position, :asc]]
## behaviours ##
acts_as_tree :order => ['position', 'asc']
## callbacks ##
before_validation :reset_parent
before_save { |p| p.send(:write_attribute, :parent_id, nil) if p.parent_id.blank? }
before_save :change_parent
before_create { |p| p.send(:fix_position, false) }
before_create :add_to_list_bottom
before_destroy :remove_from_list
# Fixme (Didier L.): Instances methods are defined before the include itself
alias :fix_position :hacked_fix_position
alias :descendants :hacked_descendants
end
module ClassMethods
# Warning: should be used only in read-only
def quick_tree(site, minimal_attributes = true)
pages = (minimal_attributes ? site.pages.minimal_attributes : site.pages).order_by([[:depth, :asc], [:position, :asc]]).to_a
tmp = []
while !pages.empty?
tmp << _quick_tree(pages.delete_at(0), pages)
end
tmp
end
def _quick_tree(current_page, pages)
i, children = 0, []
while !pages.empty?
page = pages[i]
break if page.nil?
if page.parent_id == current_page.id
page = pages.delete_at(i)
children << _quick_tree(page, pages)
else
i += 1
end
end
current_page.instance_eval do
def children=(list); @children = list; end
def children; @children || []; end
end
current_page.children = children
current_page
end
end
module InstanceMethods
def children?
self.class.where(self.parent_id_field => self.id).count
end
def children_with_minimal_attributes
self.class.where(self.parent_id_field => self.id).
order_by(self.tree_order).
minimal_attributes
end
def sort_children!(ids)
ids.each_with_index do |id, position|
child = self.children.detect { |p| p._id == BSON::ObjectId(id) }
child.position = position
child.save
end
end
def parent=(owner) # missing in acts_as_tree
@_parent = owner
self.fix_position(false)
self.instance_variable_set :@_will_move, true
end
def hacked_descendants
return [] if new_record?
self.class.all_in(path_field => [self._id]).order_by tree_order
end
protected
def change_parent
if self.parent_id_changed?
self.fix_position(false)
unless self.parent_id_was.nil?
self.position = nil # make it move to bottom
self.add_to_list_bottom
end
self.instance_variable_set :@_will_move, true
end
end
def hacked_fix_position(perform_save = true)
if parent.nil?
self.write_attribute parent_id_field, nil
self[path_field] = []
self[depth_field] = 0
else
self.write_attribute parent_id_field, parent._id
self[path_field] = parent[path_field] + [parent._id]
self[depth_field] = parent[depth_field] + 1
self.save if perform_save
end
end
def reset_parent
if self.parent_id_changed?
@_parent = nil
end
end
def add_to_list_bottom
self.position ||= (::Page.where(:_id.ne => self._id).and(:parent_id => self.parent_id).max(:position) || 0) + 1
end
def remove_from_list
return if (self.site rescue nil).nil?
::Page.where(:parent_id => self.parent_id).and(:position.gt => self.position).each do |p|
p.position -= 1
p.save
end
end
end
end
end
end
end

View File

@ -0,0 +1,16 @@
module Locomotive
module Extensions
module Shared
module Seo
extend ActiveSupport::Concern
included do
field :seo_title, :type => String
field :meta_keywords, :type => String
field :meta_description, :type => String
end
end # Seo
end # Shared
end # Extensions
end # Locomotive

View File

@ -0,0 +1,38 @@
module Locomotive
module Extensions
module Site
module FirstInstallation
# only called during the installation workflow, just after the admin account has been created
def create_first_one(attributes)
site = self.new(attributes)
site.memberships.build :account => Account.first, :role => 'admin'
site.save
site
end
def install_template(site, options = {})
default_template = Boolean.set(options.delete(:default_site_template)) || false
zipfile = options.delete(:zipfile)
# do not try to process anything if said so
return unless default_template || zipfile.present?
# default template options has a higher priority than the zipfile
source = default_template ? Locomotive.default_site_template_path : zipfile
begin
Locomotive::Import::Job.run!(source, site, { :samples => true })
rescue Exception => e
Rails.logger.error "The import of the site template failed because of #{e.message}"
end
end
end
end
end
end

View File

@ -0,0 +1,86 @@
module Locomotive
module Extensions
module Site
module SubdomainDomains
def enable_subdomain_n_domains_if_multi_sites
# puts "multi_sites? #{Locomotive.config.multi_sites?} / manage_domains? #{Locomotive.config.manage_domains?} / heroku? #{Locomotive.heroku?} / bushido? #{Locomotive.bushido?}"
if Locomotive.config.multi_sites? || Locomotive.config.manage_domains?
## fields ##
field :subdomain
field :domains, :type => Array, :default => []
## indexes
index :domains
## validations ##
validates_presence_of :subdomain
validates_uniqueness_of :subdomain
validates_exclusion_of :subdomain, :in => Locomotive.config.reserved_subdomains
validates_format_of :subdomain, :with => Locomotive::Regexps::SUBDOMAIN, :allow_blank => true
validate :domains_must_be_valid_and_unique
## callbacks ##
before_save :add_subdomain_to_domains
## named scopes ##
scope :match_domain, lambda { |domain| { :any_in => { :domains => [*domain] } } }
scope :match_domain_with_exclusion_of, lambda { |domain, site|
{ :any_in => { :domains => [*domain] }, :where => { :_id.ne => site.id } }
}
send :include, InstanceMethods
end
end
module InstanceMethods
def domains=(array)
array = [] if array.blank?; super(array)
end
def add_subdomain_to_domains
self.domains ||= []
(self.domains << self.full_subdomain).uniq!
end
def domains_without_subdomain
(self.domains || []) - [self.full_subdomain_was] - [self.full_subdomain]
end
def domains_with_subdomain
((self.domains || []) + [self.full_subdomain]).uniq
end
def full_subdomain
"#{self.subdomain}.#{Locomotive.config.domain}"
end
def full_subdomain_was
"#{self.subdomain_was}.#{Locomotive.config.domain}"
end
protected
def domains_must_be_valid_and_unique
return if self.domains.empty?
self.domains_without_subdomain.each do |domain|
if self.class.match_domain_with_exclusion_of(domain, self).any?
self.errors.add(:domains, :domain_taken, :value => domain)
end
if not domain =~ Locomotive::Regexps::DOMAIN
self.errors.add(:domains, :invalid_domain, :value => domain)
end
end
end
end
end
end
end
end

View File

@ -0,0 +1,75 @@
module Locomotive
class Membership
include Locomotive::Mongoid::Document
## fields ##
field :role, :default => 'author'
## associations ##
referenced_in :account, :validate => false
embedded_in :site, :inverse_of => :memberships
## validations ##
validates_presence_of :account
validate :can_change_role, :if => :role_changed?
## callbacks ##
before_save :define_role
## methods ##
Ability::ROLES.each do |_role|
define_method("#{_role}?") do
self.role == _role
end
end
def email; @email; end
def email=(email)
@email = email
self.account = Account.where(:email => email).first
end
def process!
if @email.blank?
self.errors.add_on_blank(:email)
:error
elsif self.account.blank?
:create_account
elsif self.site.memberships.any? { |m| m.account_id == self.account_id && m._id != self._id }
self.errors.add(:base, 'Already created')
:already_created
else
self.save
:save_it
end
end
def ability
@ability ||= Ability.new(self.account, self.site)
end
protected
def define_role
self.role = Ability::ROLES.include?(role.downcase) ? role.downcase : Ability::ROLES.first
end
# Users should not be able to set the role of another user to be higher than
# their own. A designer for example should not be able to set another user to
# be an administrator
def can_change_role
current_site = Thread.current[:site]
current_membership = current_site.memberships.where(:account_id => Thread.current[:account].id).first if current_site.present?
if current_membership.present?
# The role cannot be set higher than the current one (we use the index in
# the roles array to check role presidence)
errors.add(:role, :invalid) if Ability::ROLES.index(role) < Ability::ROLES.index(current_membership.role)
end
end
end
end

View File

@ -0,0 +1,109 @@
module Locomotive
class Page
include Locomotive::Mongoid::Document
## Extensions ##
include Extensions::Page::Tree
include Extensions::Page::EditableElements
include Extensions::Page::Parse
include Extensions::Page::Render
include Extensions::Page::Templatized
include Extensions::Page::Redirect
include Extensions::Page::Listed
include Extensions::Shared::Seo
## fields ##
field :title
field :slug
field :fullpath
field :raw_template
field :published, :type => Boolean, :default => false
field :cache_strategy, :default => 'none'
## associations ##
referenced_in :site
## indexes ##
index :site_id
index :parent_id
index [[:fullpath, Mongo::ASCENDING], [:site_id, Mongo::ASCENDING]]
## callbacks ##
after_initialize :set_default_raw_template
before_validation :normalize_slug
before_save { |p| p.fullpath = p.fullpath(true) }
before_destroy :do_not_remove_index_and_404_pages
## validations ##
validates_presence_of :site, :title, :slug
validates_uniqueness_of :slug, :scope => [:site_id, :parent_id]
validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 }
## named scopes ##
scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb
scope :root, :where => { :slug => 'index', :depth => 0 }
scope :not_found, :where => { :slug => '404', :depth => 0 }
scope :published, :where => { :published => true }
scope :fullpath, lambda { |fullpath| { :where => { :fullpath => fullpath } } }
scope :minimal_attributes, :only => %w(title slug fullpath position depth published templatized redirect listed parent_id created_at updated_at)
## methods ##
def index?
self.slug == 'index' && self.depth.to_i == 0
end
def not_found?
self.slug == '404' && self.depth.to_i == 0
end
def index_or_not_found?
self.index? || self.not_found?
end
def fullpath(force = false)
if read_attribute(:fullpath).present? && !force
return read_attribute(:fullpath)
end
if self.index? || self.not_found?
self.slug
else
slugs = self.self_and_ancestors.sort_by(&:depth).map(&:slug)
slugs.shift unless slugs.size == 1
File.join slugs
end
end
def with_cache?
self.cache_strategy != 'none'
end
def to_liquid
Locomotive::Liquid::Drops::Page.new(self)
end
protected
def do_not_remove_index_and_404_pages
return if self.site.nil? || self.site.destroyed?
if self.index? || self.not_found?
self.errors[:base] << I18n.t('errors.messages.protected_page')
end
self.errors.empty?
end
def normalize_slug
self.slug = self.title.clone if self.slug.blank? && self.title.present?
self.slug.permalink! if self.slug.present?
end
def set_default_raw_template
self.raw_template ||= I18n.t('attributes.defaults.pages.other.body')
end
end
end

View File

@ -0,0 +1,71 @@
module Locomotive
class Site
include Locomotive::Mongoid::Document
## Extensions ##
extend Extensions::Site::SubdomainDomains
extend Extensions::Site::FirstInstallation
include Extensions::Shared::Seo
## fields ##
field :name
field :robots_txt
## associations ##
references_many :pages, :validate => false
references_many :snippets, :dependent => :destroy, :validate => false
references_many :theme_assets, :dependent => :destroy, :validate => false
references_many :assets, :dependent => :destroy, :validate => false
references_many :content_types, :dependent => :destroy, :validate => false
embeds_many :memberships
## validations ##
validates_presence_of :name
## callbacks ##
after_create :create_default_pages!
after_destroy :destroy_pages
## behaviours ##
enable_subdomain_n_domains_if_multi_sites
accepts_nested_attributes_for :memberships
## methods ##
def all_pages_in_once
Page.quick_tree(self)
end
def accounts
Account.criteria.in(:_id => self.memberships.collect(&:account_id))
end
def admin_memberships
self.memberships.find_all { |m| m.admin? }
end
def to_liquid
Locomotive::Liquid::Drops::Site.new(self)
end
protected
def create_default_pages!
%w{index 404}.each do |slug|
self.pages.create({
:slug => slug,
:title => I18n.t("attributes.defaults.pages.#{slug}.title"),
:raw_template => I18n.t("attributes.defaults.pages.#{slug}.body"),
:published => true
})
end
end
def destroy_pages
# pages is a tree so we just need to delete the root (as well as the page not found page)
self.pages.root.first.try(:destroy) && self.pages.not_found.first.try(:destroy)
end
end
end

View File

@ -0,0 +1,65 @@
module
class Snippet
include Locomotive::Mongoid::Document
## fields ##
field :name
field :slug
field :template
## associations ##
referenced_in :site
## callbacks ##
before_validation :normalize_slug
after_save :update_templates
after_destroy :update_templates
## validations ##
validates_presence_of :site, :name, :slug, :template
validates_uniqueness_of :slug, :scope => :site_id
## methods ##
protected
def normalize_slug
# TODO: refactor it
self.slug = self.name.clone if self.slug.blank? && self.name.present?
self.slug.permalink! if self.slug.present?
end
def update_templates
return unless (self.site rescue false) # not run if the site is being destroyed
pages = self.site.pages.any_in(:snippet_dependencies => [self.slug]).to_a
pages.each do |page|
self._change_snippet_inside_template(page.template.root)
page.send(:_serialize_template)
Page.without_callback(:save, :after, :update_template_descendants) do
page.save(:validate => false)
end
end
end
def _change_snippet_inside_template(node)
case node
when Locomotive::Liquid::Tags::Snippet
node.refresh(self) if node.slug == self.slug
when Locomotive::Liquid::Tags::InheritedBlock
_change_snippet_inside_template(node.parent) if node.parent
end
# Walk the children of this entry if they're available.
if node.respond_to?(:nodelist)
(node.nodelist || []).each do |child|
self._change_snippet_inside_template(child)
end
end
end
end
end

View File

@ -0,0 +1,154 @@
module Locomotive
class ThemeAsset
include Locomotive::Mongoid::Document
## extensions ##
include Extensions::Asset::Types
## fields ##
field :local_path
field :content_type
field :width, :type => Integer
field :height, :type => Integer
field :size, :type => Integer
field :folder, :default => nil
mount_uploader :source, ThemeAssetUploader
## associations ##
referenced_in :site
## indexes ##
index :site_id
index [[:site_id, Mongo::ASCENDING], [:local_path, Mongo::ASCENDING]]
## callbacks ##
before_validation :store_plain_text
before_validation :sanitize_folder
before_validation :build_local_path
## validations ##
validates_presence_of :site, :source
validates_presence_of :plain_text_name, :if => Proc.new { |a| a.performing_plain_text? }
validates_uniqueness_of :local_path, :scope => :site_id
validates_integrity_of :source
validate :content_type_can_not_changed
## named scopes ##
## accessors ##
attr_accessor :plain_text_name, :plain_text, :plain_text_type, :performing_plain_text
## methods ##
def stylesheet_or_javascript?
self.stylesheet? || self.javascript?
end
def local_path(short = false)
if short
self.read_attribute(:local_path).gsub(/^#{self.content_type.to_s.pluralize}\//, '')
else
self.read_attribute(:local_path)
end
end
def plain_text_name
if not @plain_text_name_changed
@plain_text_name ||= self.safe_source_filename
end
@plain_text_name.gsub(/(\.[a-z0-9A-Z]+)$/, '') rescue nil
end
def plain_text_name=(name)
@plain_text_name_changed = true
@plain_text_name = name
end
def plain_text
if RUBY_VERSION =~ /1\.9/
@plain_text ||= (self.source.read.force_encoding('UTF-8') rescue nil)
else
@plain_text ||= self.source.read
end
end
def plain_text_type
@plain_text_type || (stylesheet_or_javascript? ? self.content_type : nil)
end
def performing_plain_text?
Boolean.set(self.performing_plain_text) || false
end
def store_plain_text
self.content_type ||= @plain_text_type if self.performing_plain_text?
data = self.performing_plain_text? ? self.plain_text : self.source.read
return if !self.stylesheet_or_javascript? || self.plain_text_name.blank? || data.blank?
sanitized_source = self.escape_shortcut_urls(data)
self.source = CarrierWave::SanitizedFile.new({
:tempfile => StringIO.new(sanitized_source),
:filename => "#{self.plain_text_name}.#{self.stylesheet? ? 'css' : 'js'}"
})
end
def to_liquid
{ :url => self.source.url }.merge(self.attributes).stringify_keys
end
def self.all_grouped_by_folder(site)
assets = site.theme_assets.order_by([[:slug, :asc]])
assets.group_by { |a| a.folder.split('/').first.to_sym }
end
protected
def safe_source_filename
self.source_filename || self.source.send(:original_filename) rescue nil
end
def sanitize_folder
self.folder = self.content_type.to_s.pluralize if self.folder.blank?
# no accents, no spaces, no leading and ending trails
self.folder = ActiveSupport::Inflector.transliterate(self.folder).gsub(/(\s)+/, '_').gsub(/^\//, '').gsub(/\/$/, '')
# folder should begin by a root folder
if (self.folder =~ /^(stylesheets|javascripts|images|medias|fonts)/).nil?
self.folder = File.join(self.content_type.to_s.pluralize, self.folder)
end
end
def build_local_path
if filename = self.safe_source_filename
self.local_path = File.join(self.folder, filename)
else
nil
end
end
def escape_shortcut_urls(text)
return if text.blank?
text.gsub(/[("'](\/(stylesheets|javascripts|images|medias)\/(([^;.]+)\/)*([a-z_\-0-9]+)\.[a-z]{2,3})[)"']/) do |path|
sanitized_path = path.gsub(/[("')]/, '').gsub(/^\//, '')
if asset = self.site.theme_assets.where(:local_path => sanitized_path).first
"#{path.first}#{asset.source.url}#{path.last}"
else
path
end
end
end
def content_type_can_not_changed
self.errors.add(:source, :extname_changed) if !self.new_record? && self.content_type_changed?
end
end
end

View File

@ -1,73 +0,0 @@
class Membership
include Locomotive::Mongoid::Document
## fields ##
field :role, :default => 'author'
## associations ##
referenced_in :account, :validate => false
embedded_in :site, :inverse_of => :memberships
## validations ##
validates_presence_of :account
validate :can_change_role, :if => :role_changed?
## callbacks ##
before_save :define_role
## methods ##
Ability::ROLES.each do |_role|
define_method("#{_role}?") do
self.role == _role
end
end
def email; @email; end
def email=(email)
@email = email
self.account = Account.where(:email => email).first
end
def process!
if @email.blank?
self.errors.add_on_blank(:email)
:error
elsif self.account.blank?
:create_account
elsif self.site.memberships.any? { |m| m.account_id == self.account_id && m._id != self._id }
self.errors.add(:base, 'Already created')
:already_created
else
self.save
:save_it
end
end
def ability
@ability ||= Ability.new(self.account, self.site)
end
protected
def define_role
self.role = Ability::ROLES.include?(role.downcase) ? role.downcase : Ability::ROLES.first
end
# Users should not be able to set the role of another user to be higher than
# their own. A designer for example should not be able to set another user to
# be an administrator
def can_change_role
current_site = Thread.current[:site]
current_membership = current_site.memberships.where(:account_id => Thread.current[:admin].id).first if current_site.present?
if current_membership.present?
# The role cannot be set higher than the current one (we use the index in
# the roles array to check role presidence)
errors.add(:role, :invalid) if Ability::ROLES.index(role) < Ability::ROLES.index(current_membership.role)
end
end
end

View File

@ -1,107 +0,0 @@
class Page
include Locomotive::Mongoid::Document
## Extensions ##
include Extensions::Page::Tree
include Extensions::Page::EditableElements
include Extensions::Page::Parse
include Extensions::Page::Render
include Extensions::Page::Templatized
include Extensions::Page::Redirect
include Extensions::Page::Listed
include Extensions::Shared::Seo
## fields ##
field :title
field :slug
field :fullpath
field :raw_template
field :published, :type => Boolean, :default => false
field :cache_strategy, :default => 'none'
## associations ##
referenced_in :site
## indexes ##
index :site_id
index :parent_id
index [[:fullpath, Mongo::ASCENDING], [:site_id, Mongo::ASCENDING]]
## callbacks ##
after_initialize :set_default_raw_template
before_validation :normalize_slug
before_save { |p| p.fullpath = p.fullpath(true) }
before_destroy :do_not_remove_index_and_404_pages
## validations ##
validates_presence_of :site, :title, :slug
validates_uniqueness_of :slug, :scope => [:site_id, :parent_id]
validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 }
## named scopes ##
scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb
scope :root, :where => { :slug => 'index', :depth => 0 }
scope :not_found, :where => { :slug => '404', :depth => 0 }
scope :published, :where => { :published => true }
scope :fullpath, lambda { |fullpath| { :where => { :fullpath => fullpath } } }
scope :minimal_attributes, :only => %w(title slug fullpath position depth published templatized redirect listed parent_id created_at updated_at)
## methods ##
def index?
self.slug == 'index' && self.depth.to_i == 0
end
def not_found?
self.slug == '404' && self.depth.to_i == 0
end
def index_or_not_found?
self.index? || self.not_found?
end
def fullpath(force = false)
if read_attribute(:fullpath).present? && !force
return read_attribute(:fullpath)
end
if self.index? || self.not_found?
self.slug
else
slugs = self.self_and_ancestors.sort_by(&:depth).map(&:slug)
slugs.shift unless slugs.size == 1
File.join slugs
end
end
def with_cache?
self.cache_strategy != 'none'
end
def to_liquid
Locomotive::Liquid::Drops::Page.new(self)
end
protected
def do_not_remove_index_and_404_pages
return if self.site.nil? || self.site.destroyed?
if self.index? || self.not_found?
self.errors[:base] << I18n.t('errors.messages.protected_page')
end
self.errors.empty?
end
def normalize_slug
self.slug = self.title.clone if self.slug.blank? && self.title.present?
self.slug.permalink! if self.slug.present?
end
def set_default_raw_template
self.raw_template ||= I18n.t('attributes.defaults.pages.other.body')
end
end

View File

@ -1,69 +0,0 @@
class Site
include Locomotive::Mongoid::Document
## Extensions ##
extend Extensions::Site::SubdomainDomains
extend Extensions::Site::FirstInstallation
include Extensions::Shared::Seo
## fields ##
field :name
field :robots_txt
## associations ##
references_many :pages, :validate => false
references_many :snippets, :dependent => :destroy, :validate => false
references_many :theme_assets, :dependent => :destroy, :validate => false
references_many :assets, :dependent => :destroy, :validate => false
references_many :content_types, :dependent => :destroy, :validate => false
embeds_many :memberships
## validations ##
validates_presence_of :name
## callbacks ##
after_create :create_default_pages!
after_destroy :destroy_pages
## behaviours ##
enable_subdomain_n_domains_if_multi_sites
accepts_nested_attributes_for :memberships
## methods ##
def all_pages_in_once
Page.quick_tree(self)
end
def accounts
Account.criteria.in(:_id => self.memberships.collect(&:account_id))
end
def admin_memberships
self.memberships.find_all { |m| m.admin? }
end
def to_liquid
Locomotive::Liquid::Drops::Site.new(self)
end
protected
def create_default_pages!
%w{index 404}.each do |slug|
self.pages.create({
:slug => slug,
:title => I18n.t("attributes.defaults.pages.#{slug}.title"),
:raw_template => I18n.t("attributes.defaults.pages.#{slug}.body"),
:published => true
})
end
end
def destroy_pages
# pages is a tree so we just need to delete the root (as well as the page not found page)
self.pages.root.first.try(:destroy) && self.pages.not_found.first.try(:destroy)
end
end

Some files were not shown because too many files have changed in this diff Show More