Skip to content

Commit

Permalink
add scoped multi-site updates as described at http://blog.zerosum.org…
Browse files Browse the repository at this point in the history
…/2008/4/3/radiant-super-multi

 * scopes user level access to individual sites (admins and developers still have access to all sites)
 * regular users cannot see, edit, or access other user sites that they do not own
 * optionally scopes layouts to sites
 * snippets usable everywhere, but display and editing of snippets limited to admins/developers

updated for radiant 0.6.7
  • Loading branch information
zapnap committed Jun 24, 2008
1 parent b0563cf commit 46a035d
Show file tree
Hide file tree
Showing 20 changed files with 478 additions and 57 deletions.
22 changes: 18 additions & 4 deletions README
@@ -1,10 +1,11 @@
= Multi Site

Created by Sean Cribbs, November 2007. Inspired by the original virtual_domain
behavior.
behavior. Extended by Nick Plante, March 2008.

Multi Site allows you to host multiple websites on a single Radiant
installation.
installation. It also allows you to assign users to specific sites, scoping
all their administrative activity to that one domain.

Each site has its own independent sitemap/page-tree and these attributes:

Expand All @@ -16,10 +17,23 @@ Each site has its own independent sitemap/page-tree and these attributes:
homepage_id: The numerical database ID of the root page (usually
you can just leave this alone).

Any existing pages that you have will be scoped to the 'Default' site, which
is the site that will be seen for requests that don't match a particular
base_domain.

In addition to scoping users to sites, layouts can also be scoped to a
particular site. Use the selection box on the edit layout page to assign a
layout to a site. If no site is chosen, the layout will be available for
selection from any site. Note that snippets are not limited in this way,
but that access to snippets has been DISABLED for "regular" users (those
users who are not developers or admins).

If you don't like it, feel free to make suggestions or change it :).

Included images are slightly modified from FamFamFam Silk Icons by Mark James:
http://www.famfamfam.com/lab/icons/silk/

*** THIS EXTENSION REQUIRES THE 'SHARDS' EXTENSION TO BE INSTALLED ***
*** THIS EXTENSION REQUIRES RADIANT 0.6.7 OR LATER ***

== Installation

Expand All @@ -39,4 +53,4 @@ http://www.famfamfam.com/lab/icons/silk/
== Acknowledgments

Thanks to Digital Pulp, Inc. for funding the initial development of this
extension as part of the Redken.com project.
extension as part of the Redken.com project.
14 changes: 9 additions & 5 deletions app/models/site.rb
@@ -1,6 +1,10 @@
class Site < ActiveRecord::Base
has_many :users
has_many :layouts

acts_as_list
order_by "position ASC"

class << self
def find_for_host(hostname = '')
default, normal = find(:all).partition {|s| s.domain.blank? }
Expand Down Expand Up @@ -29,11 +33,11 @@ def dev_url(path = "/")

def create_homepage
if self.homepage_id.blank?
self.homepage = self.build_homepage(:title => "#{self.name} Homepage",
:slug => "#{self.name.slugify}", :breadcrumb => "Home",
:status => Status[:draft])
self.homepage.parts << PagePart.new(:name => "body", :content => "")
save
self.homepage = self.build_homepage(:title => "#{self.name} Homepage",
:slug => "#{self.name.slugify}", :breadcrumb => "Home",
:status => Status[:draft])
self.homepage.parts << PagePart.new(:name => "body", :content => "")
save
end
end
end
4 changes: 4 additions & 0 deletions app/views/admin/layout/_site.rhtml
@@ -0,0 +1,4 @@
<p>
<label for="layout_site_id">Site </label>
<%= select "layout", "site_id", [['<N/A>', nil]] + Site.find(:all).map { |s| [s.name, s.id] } %>
</p>
13 changes: 7 additions & 6 deletions app/views/admin/page/_site_subnav.rhtml
Expand Up @@ -14,12 +14,13 @@
margin-right: 10px;
}
<% end %>
<div id="sites">
<% Site.find(:all).each do |site| %>
<% name = (@site == site) ? "<strong>#{site.name}</strong>" : site.name -%>
<%= link_to name, page_index_path(:root => site.homepage) %>
<% end %>
</div>
<% if developer? && Site.count > 1 %>
<div id="sites">
<strong>Site:</strong>
<%= select_tag('site-selector', options_from_collection_for_select(Site.find(:all), :homepage_id, :name, @site.nil? ? nil : @site.homepage_id),
:onchange => "document.location = '#{page_index_path}?root=' + this.options[this.selectedIndex].value") %>
</div>
<% end %>
<% if Radiant::Config['multi_site.scoped?'] %>
<% content_for :page_scripts do %>
Expand Down
5 changes: 5 additions & 0 deletions app/views/admin/user/_site.rhtml
@@ -0,0 +1,5 @@
<tr>
<th class="label"><label class="optional" for="user_site_id">Site</label></td>
<td class="field" style="text-align: left"><%= select "user", "site_id", [['<N/A>', nil]] + Site.find(:all).map { |s| [s.name, s.id] } %></td>
<td class="help">Optional. Please specify a site for this user.</td>
</tr>
8 changes: 8 additions & 0 deletions db/migrate/004_create_default_site.rb
@@ -0,0 +1,8 @@
class CreateDefaultSite < ActiveRecord::Migration
def self.up
Site.create(:name => 'Default', :domain => '', :base_domain => 'default', :homepage => Page.find(:first, :conditions => "parent_id IS NULL"))
end

def self.down
end
end
9 changes: 9 additions & 0 deletions db/migrate/005_add_site_id_to_users.rb
@@ -0,0 +1,9 @@
class AddSiteIdToUsers < ActiveRecord::Migration
def self.up
add_column :users, :site_id, :integer
end

def self.down
remove_column :users, :site_id
end
end
9 changes: 9 additions & 0 deletions db/migrate/006_add_site_id_to_layouts.rb
@@ -0,0 +1,9 @@
class AddSiteIdToLayouts < ActiveRecord::Migration
def self.up
add_column :layouts, :site_id, :integer
end

def self.down
remove_column :layouts, :site_id
end
end
12 changes: 12 additions & 0 deletions lib/multi_site/layout_extensions.rb
@@ -0,0 +1,12 @@
module MultiSite::LayoutExtensions
def self.included(base)
base.belongs_to :site
base.extend ClassMethods
end

module ClassMethods
def scoped_to_site(site_id, &block)
with_scope(:find => { :conditions => ["site_id = ? OR site_id IS NULL", site_id] }, &block)
end
end
end
116 changes: 88 additions & 28 deletions lib/multi_site/page_controller_extensions.rb
@@ -1,53 +1,113 @@
module MultiSite::PageControllerExtensions
def self.included(base)
base.class_eval {
before_filter :set_site, :only => [:remove, :new, :edit]
around_filter :scope_layouts_to_site, :only => [:new, :edit]

alias_method_chain :index, :root
alias_method_chain :clear_model_cache, :site
alias_method_chain :continue_url, :site
%w{remove}.each do |m|
alias_method_chain m.to_sym, :back
%w{edit new remove continue_url clear_model_cache}.each do |m|
alias_method_chain m.to_sym, :site
end
}
end

def index_with_root
if params[:root] # If a root page is specified
@homepage = Page.find(params[:root])
@site = @homepage.root.site
elsif @site = Site.find(:first, :order => "position ASC") # If there is a site defined
if @site.homepage
cookies.delete('expanded_rows')
if user_developer? # or admin
if params[:root] # If a root page is specified (should this ever be required for non-developers?)
@homepage = Page.find(params[:root])
@site = @homepage.root.site
elsif (@site = Site.find(:first, :order => "position ASC")) && @site.homepage # If there is a site defined
@homepage = @site.homepage
else
index_without_root
end
else # Just do the default
index_without_root
elsif (@site = current_user.site) && @site.homepage
@homepage = @site.homepage
else
access_denied
end
end

def remove_with_back
@page = Page.find(params[:id])
if request.post?
announce_pages_removed(@page.children.count + 1)
@page.destroy
return_url = session[:came_from]
session[:came_from] = nil
if return_url && return_url != page_index_url(:root => @page)
redirect_to return_url

def remove_with_site
if user_authorized?
if request.post?
announce_pages_removed(@page.children.count + 1)
@page.destroy
return_url = session[:came_from]
session[:came_from] = nil
if return_url && return_url != page_index_url(:root => @page)
redirect_to return_url
else
redirect_to page_index_url(:page => @page.parent)
end
else
redirect_to page_index_url(:page => @page.parent)
session[:came_from] = request.env["HTTP_REFERER"]
end
else
session[:came_from] = request.env["HTTP_REFERER"]
access_denied
end
end

def clear_model_cache_with_site
Page.current_site ||= @site || @page.root.site
clear_model_cache_without_site
end

def continue_url_with_site(options={})
options[:redirect_to] || (params[:continue] ? page_edit_url(:id => model.id) : page_index_url(:root => model.root.id))

def new_with_site
if user_authorized?
if request.get?
@page = Page.new_with_defaults(config)
else
@page = Page.new
end

@page.slug = params[:slug]
@page.breadcrumb = params[:breadcrumb]
@page.parent = Page.find_by_id(params[:parent_id])
render :action => :edit if handle_new_or_edit_post
else
access_denied
end
end

def edit_with_site
if user_authorized?
@old_page_url = @page.url
handle_new_or_edit_post
else
access_denied
end
end
end

protected

def continue_url_with_site(options = {})
options[:redirect_to] || (params[:continue] ? model_edit_url(:id => model.id) : model_index_url(:root => model.root.id))
end

def access_denied
flash[:error] = 'Access denied.'
redirect_to login_url
end

def user_developer?
current_user and (current_user.developer? or current_user.admin?)
end

def user_authorized?
user_developer? || (!current_user.nil? && current_user.owner?(@site))
end

def set_site
id = params[:id] || params[:root] || params[:parent_id]
@page = Page.find(id)
@site = @page.root.site
end

def scope_layouts_to_site
Layout.scoped_to_site(@site.id) do
yield
end
end
end
10 changes: 9 additions & 1 deletion lib/multi_site/response_cache_extensions.rb
Expand Up @@ -2,6 +2,7 @@ module MultiSite::ResponseCacheExtensions

def self.included(base)
base.alias_method_chain :page_cache_path, :site
base.alias_method_chain :clear, :site
end

def page_cache_path_with_site(path)
Expand All @@ -11,4 +12,11 @@ def page_cache_path_with_site(path)
cache_path = File.expand_path(File.join(root_dir, path), root_dir)
cache_path if cache_path.index(root_dir) == 0
end
end

def clear_with_site(site = nil)
dirs = site.nil? ? Dir["#{directory}/*"] : Dir["#{directory}/#{site.base_domain}"]
dirs.each do |f|
FileUtils.rm_rf f
end
end
end
10 changes: 10 additions & 0 deletions lib/multi_site/snippet_controller_extensions.rb
@@ -0,0 +1,10 @@
module MultiSite::SnippetControllerExtensions
def self.included(base)
base.class_eval {
only_allow_access_to :index, :new, :edit, :remove,
:when => [:developer, :admin],
:denied_url => { :controller => 'page', :action => 'index' },
:denied_message => 'You must have developer privileges to perform this action.'
}
end
end
9 changes: 9 additions & 0 deletions lib/multi_site/user_extensions.rb
@@ -0,0 +1,9 @@
module MultiSite::UserExtensions
def self.included(base)
base.belongs_to :site
end

def owner?(multi_site)
site == multi_site
end
end
16 changes: 15 additions & 1 deletion multi_site_extension.rb
Expand Up @@ -22,13 +22,27 @@ def activate
require_dependency 'application'

Page.send :include, MultiSite::PageExtensions
ResponseCache.send :include, MultiSite::ResponseCacheExtensions
User.send :include, MultiSite::UserExtensions
Layout.send :include, MultiSite::LayoutExtensions

SiteController.send :include, MultiSite::SiteControllerExtensions
Admin::PageController.send :include, MultiSite::PageControllerExtensions
ResponseCache.send :include, MultiSite::ResponseCacheExtensions
Admin::SnippetController.send :include, MultiSite::SnippetControllerExtensions

Radiant::Config["dev.host"] = 'preview'

# Add site navigation
admin.page.index.add :top, "site_subnav"
admin.tabs.add "Sites", "/admin/sites", :visibility => [:admin]

# Make snippets visible only to admins and developers
admin.tabs.remove "Snippets"
admin.tabs.add "Snippets", "/admin/snippets", :before => "Layouts", :visibility => [:admin, :developer]

# Add site admin scoping fields
admin.user.edit.add :form, "site", :before => "edit_table_footer"
admin.layout.edit.add :form, "site", :before => "edit_timestamp"
end

def deactivate
Expand Down

0 comments on commit 46a035d

Please sign in to comment.