trusty-multi-site-extension 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +116 -0
- data/Rakefile +120 -0
- data/app/assets/images/admin/move_higher.png +0 -0
- data/app/assets/images/admin/move_lower.png +0 -0
- data/app/assets/images/admin/move_to_bottom.png +0 -0
- data/app/assets/images/admin/move_to_top.png +0 -0
- data/app/assets/images/admin/remove.png +0 -0
- data/app/assets/stylesheets/admin/multi_site.scss +121 -0
- data/app/assets/stylesheets/admin/site_chooser.scss +0 -0
- data/app/controllers/admin/sites_controller.rb +14 -0
- data/app/helpers/scoped_helper.rb +19 -0
- data/app/helpers/sites_helper.rb +10 -0
- data/app/models/site.rb +86 -0
- data/app/views/admin/layouts/_choose_site.html.haml +8 -0
- data/app/views/admin/layouts/_site_chooser.html.haml +2 -0
- data/app/views/admin/sites/_form.haml +43 -0
- data/app/views/admin/sites/edit.haml +7 -0
- data/app/views/admin/sites/index.haml +43 -0
- data/app/views/admin/sites/new.haml +7 -0
- data/app/views/admin/snippets/_choose_site.html.haml +6 -0
- data/app/views/admin/users/_choose_site.html.haml +8 -0
- data/app/views/site/not_configured.html.haml +7 -0
- data/config/initializers/radiant_config.rb +5 -0
- data/config/routes.rb +13 -0
- data/db/migrate/001_create_sites.rb +13 -0
- data/db/migrate/002_add_order_to_sites.rb +9 -0
- data/db/migrate/003_add_base_domain_to_sites.rb +9 -0
- data/db/migrate/004_add_admin_fields_to_sites.rb +17 -0
- data/db/migrate/005_add_sites.rb +13 -0
- data/db/migrate/006_remove_user_login_index.rb +8 -0
- data/db/migrate/20090810145840_site_abbreviation.rb +9 -0
- data/db/migrate/201411031415046078_recreate_non_unique_index_on_snippets_name.rb +11 -0
- data/lib/multi_site/admin_ui.rb +46 -0
- data/lib/multi_site/application_controller_extensions.rb +40 -0
- data/lib/multi_site/application_controller_filter_extensions.rb +21 -0
- data/lib/multi_site/engine.rb +5 -0
- data/lib/multi_site/page_extensions.rb +43 -0
- data/lib/multi_site/pages_controller_extensions.rb +48 -0
- data/lib/multi_site/resource_controller_extensions.rb +41 -0
- data/lib/multi_site/route_extensions.rb +21 -0
- data/lib/multi_site/route_set_extensions.rb +16 -0
- data/lib/multi_site/scoped_model.rb +145 -0
- data/lib/multi_site/scoped_validation.rb +32 -0
- data/lib/multi_site/site_chooser_helper.rb +14 -0
- data/lib/multi_site/site_controller_extensions.rb +12 -0
- data/lib/tasks/multi_site_extension_tasks.rake +28 -0
- data/lib/tasks/scoped_admin_extension_tasks.rake +28 -0
- data/lib/trusty-multi-site-extension.rb +1 -0
- data/multi_site_extension.rb +66 -0
- data/spec/controllers/extended_site_controller_spec.rb +61 -0
- data/spec/datasets/site_pages_dataset.rb +13 -0
- data/spec/datasets/sites_dataset.rb +10 -0
- data/spec/functional/multi_site_routing_spec.rb +37 -0
- data/spec/lib/multi_site/admin_ui_spec.rb +35 -0
- data/spec/lib/multi_site/scoped_model_spec.rb +182 -0
- data/spec/matchers/route_matcher.rb +38 -0
- data/spec/models/extended_page_spec.rb +48 -0
- data/spec/models/site_spec.rb +83 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +36 -0
- data/trusty-multi-site-extension.gemspec +28 -0
- metadata +164 -0
data/README.markdown
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# Multi Site #
|
2
|
+
|
3
|
+
Created by Sean Cribbs, November 2007. Inspired by the original virtual_domain behavior.
|
4
|
+
|
5
|
+
Multi Site allows you to host multiple websites on a single Radiant installation.
|
6
|
+
|
7
|
+
## (Forked) ##
|
8
|
+
|
9
|
+
This fork adds a flexible but robust way to scope model classes to the current site. It's just a framework - nothing is scoped by default - but very easy to apply. See under scoped resources below.
|
10
|
+
|
11
|
+
### Status ###
|
12
|
+
|
13
|
+
Fairly solid now and quite thoroughly tested. Should be a drop-in replacement for the standard multi_site. The interface is about to change, but the present one will still be supported.
|
14
|
+
|
15
|
+
### Warning ###
|
16
|
+
|
17
|
+
I've just changed the site-finding logic so that Site.default is called in any circumstances. It makes life much easier in tests and console and should let me take out a lot of conditional code. It shouldn't affect normal use, but you know. Please let me know if anything goes wrong.
|
18
|
+
|
19
|
+
### Requirements ###
|
20
|
+
|
21
|
+
There are no absolute requirements but you will need to install our submenu extension since that has taken the job of showing the site-chooser above any site-scoped index page.
|
22
|
+
|
23
|
+
### Installation ###
|
24
|
+
|
25
|
+
$ git submodule add git://github.com/spanner/radiant-multi-site-extension.git vendor/extensions/multi_site
|
26
|
+
$ rake trusty:extensions:multi_site:migrate
|
27
|
+
$ rake trusty:extensions:multi_site:update
|
28
|
+
|
29
|
+
### Compatibility ###
|
30
|
+
|
31
|
+
This differs from the original in that it will create a default site if none exists, but this should happen invisibly.
|
32
|
+
|
33
|
+
This version of multi_site does cause failures in radiant's main tests, usually when a site is required but the tests don't supply it. I will probably add a 'lax mode' at some point that doesn't mind if no site is defined.
|
34
|
+
|
35
|
+
### Scoped resources ###
|
36
|
+
|
37
|
+
If you want to site-scope a model class (let's say you want your assets to be site-specific as well as your pages), all you have to do is add a line to the top of the class:
|
38
|
+
|
39
|
+
is_site_scoped
|
40
|
+
|
41
|
+
If you want the option to share some instances between sites (say you want some of your users to be confined to one site but a few admin users to see all of them):
|
42
|
+
|
43
|
+
is_site_scoped :shareable => true
|
44
|
+
|
45
|
+
The scoping takes effect at the ActiveRecord level - it wraps `with_scope` round every call to find (actually, to find_every) and a few other methods. If an object is out of site scope it is as though it didn't exist. This usually means your controller and view code hardly need to change at all: they just see fewer objects. You can fine-tune the scoping by specifying the `site_scope_condition` method in each scoped class.
|
46
|
+
|
47
|
+
If a site-scoped class includes any calls to `validates_uniqueness_of`, those too will be scoped to the site. There's a hack there, though: the validations are defined with the model and saved as [procs](http://casperfabricius.com/site/2008/12/06/removing-rails-validations-with-metaprogramming/) which causes all sorts of misery when you want to change them. Instead we've alias_chained the `validates_uniqueness_of` method to apply scope from the start. This has to happen very early in the initialisation procedure, when we don't really have much configuration information, so the uniqueness validation scope is applied to every model with a `site_id` column. I hope to find a better solution but it does work.
|
48
|
+
|
49
|
+
**Please Note:** a `site_scoped` class must be watched by the `UserActionObserver` in order to get the before_validation hook that sets the site id.
|
50
|
+
|
51
|
+
There is, or will soon be, more about this [in the wiki](http://wiki.github.com/spanner/radiant-multi-site-extension) and one day I'll get round to posting some [proper documentation](http://spanner.org/radiant/multi_site).
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
### Examples ###
|
56
|
+
|
57
|
+
The [scoped_admin](http://github.com/spanner/radiant-scoped-admin-extension) extension uses this method to confine layouts, snippets and (some)
|
58
|
+
users to sites. It only takes four lines of code and two partials.
|
59
|
+
|
60
|
+
We've also shrunk the [paperclipped_multi_site](http://github.com/spanner/radiant-paperclipped_multisite-extension) extension to a one-liner.
|
61
|
+
|
62
|
+
Our [reader](http://github.com/spanner/radiant-reader-extension) extension - which handles the mechanics of site membership - is site scoped if this extension is present. It includes a useful `fake_site_scope` class that drops a warning in the log if site-scoping is not possible but otherwise lets the extension work in a single-site installation.
|
63
|
+
|
64
|
+
### Security ###
|
65
|
+
|
66
|
+
Is one of the main goals. A couple of our clients are very security-conscious and we needed something in which there was no risk at all of the wrong person seeing a page. This will make more sense when I publish the [reader-groups](http://github.com/spanner/radiant-reader-groups-extension) extension), which is next. If you see a loophole we'll be __very__ glad to know of it.
|
67
|
+
|
68
|
+
### Questions and comments ###
|
69
|
+
|
70
|
+
Would be very welcome. Contact Will on will at spanner.org or drop [something into lighthouse](http://spanner.lighthouseapp.com/projects/26912-radiant-extensions). Github messages also fine.
|
71
|
+
|
72
|
+
- - -
|
73
|
+
|
74
|
+
## Original multi_site ##
|
75
|
+
|
76
|
+
Each site has its own independent
|
77
|
+
sitemap/page-tree and these attributes:
|
78
|
+
|
79
|
+
* name: Whatever you want to call the site
|
80
|
+
* domain: A Ruby regular expression (without the //) to match the request against
|
81
|
+
* base_domain: A canonical domain name for doing quicker matches and for generating absolute URLs against
|
82
|
+
* homepage_id: The numerical database ID of the root page (usually you can just leave this alone).
|
83
|
+
|
84
|
+
Included images are slightly modified from FamFamFam Silk Icons by Mark James: http://www.famfamfam.com/lab/icons/silk/
|
85
|
+
|
86
|
+
### Installation ###
|
87
|
+
|
88
|
+
1) Unpack/checkout/export the extension into vendor/extensions of your
|
89
|
+
project.
|
90
|
+
|
91
|
+
2) Run the extension migrations.
|
92
|
+
|
93
|
+
$ rake production db:migrate:extensions
|
94
|
+
|
95
|
+
3) Run the extension update task.
|
96
|
+
|
97
|
+
$ rake production radiant:extensions:multi_site:update
|
98
|
+
|
99
|
+
4) Restart your server
|
100
|
+
|
101
|
+
### Other Extensions ###
|
102
|
+
|
103
|
+
Multi Site allows you to customize routes within your other extensions. To
|
104
|
+
restrict a route to a particular site, pass the site's name into the
|
105
|
+
conditions hash:
|
106
|
+
|
107
|
+
map.resources :things, :conditions => { :site => 'My Site' }
|
108
|
+
|
109
|
+
You can also scope a route to multiple sites with an array:
|
110
|
+
|
111
|
+
map.resources :things, :conditions => { :site => ['My Site', 'Your Site'] }
|
112
|
+
|
113
|
+
### Acknowledgments ###
|
114
|
+
|
115
|
+
Thanks to Digital Pulp, Inc. for funding the initial development of this
|
116
|
+
extension as part of the Redken.com project.
|
data/Rakefile
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# I think this is the one that should be moved to the extension Rakefile template
|
2
|
+
|
3
|
+
# In rails 1.2, plugins aren't available in the path until they're loaded.
|
4
|
+
# Check to see if the rspec plugin is installed first and require
|
5
|
+
# it if it is. If not, use the gem version.
|
6
|
+
|
7
|
+
# Determine where the RSpec plugin is by loading the boot
|
8
|
+
unless defined? TRUSTY_CMS_ROOT
|
9
|
+
ENV["RAILS_ENV"] = "test"
|
10
|
+
case
|
11
|
+
when ENV["TRUSTY_ENV_FILE"]
|
12
|
+
require File.dirname(ENV["TRUSTY_ENV_FILE"]) + "/boot"
|
13
|
+
when File.dirname(__FILE__) =~ %r{vendor/trusty_cms/vendor/extensions}
|
14
|
+
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
|
15
|
+
else
|
16
|
+
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake'
|
21
|
+
require 'rake/rdoctask'
|
22
|
+
require 'rake/testtask'
|
23
|
+
|
24
|
+
rspec_base = File.expand_path(TRUSTY_CMS_ROOT + '/vendor/plugins/rspec/lib')
|
25
|
+
$LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
|
26
|
+
require 'spec/rake/spectask'
|
27
|
+
# require 'spec/translator'
|
28
|
+
|
29
|
+
# Cleanup the TRUSTY_CMS_ROOT constant so specs will load the environment
|
30
|
+
Object.send(:remove_const, :TRUSTY_CMS_ROOT)
|
31
|
+
|
32
|
+
extension_root = File.expand_path(File.dirname(__FILE__))
|
33
|
+
|
34
|
+
task :default => :spec
|
35
|
+
task :stats => "spec:statsetup"
|
36
|
+
|
37
|
+
desc "Run all specs in spec directory"
|
38
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
39
|
+
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
40
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :spec do
|
44
|
+
desc "Run all specs in spec directory with RCov"
|
45
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
46
|
+
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
47
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
48
|
+
t.rcov = true
|
49
|
+
t.rcov_opts = ['--exclude', 'spec', '--rails']
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Print Specdoc for all specs"
|
53
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
54
|
+
t.spec_opts = ["--format", "specdoc", "--dry-run"]
|
55
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
56
|
+
end
|
57
|
+
|
58
|
+
[:models, :controllers, :views, :helpers].each do |sub|
|
59
|
+
desc "Run the specs under spec/#{sub}"
|
60
|
+
Spec::Rake::SpecTask.new(sub) do |t|
|
61
|
+
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
62
|
+
t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Hopefully no one has written their extensions in pre-0.9 style
|
67
|
+
# desc "Translate specs from pre-0.9 to 0.9 style"
|
68
|
+
# task :translate do
|
69
|
+
# translator = ::Spec::Translator.new
|
70
|
+
# dir = RAILS_ROOT + '/spec'
|
71
|
+
# translator.translate(dir, dir)
|
72
|
+
# end
|
73
|
+
|
74
|
+
# Setup specs for stats
|
75
|
+
task :statsetup do
|
76
|
+
require 'code_statistics'
|
77
|
+
::STATS_DIRECTORIES << %w(Model\ specs spec/models)
|
78
|
+
::STATS_DIRECTORIES << %w(View\ specs spec/views)
|
79
|
+
::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
|
80
|
+
::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
|
81
|
+
::CodeStatistics::TEST_TYPES << "Model specs"
|
82
|
+
::CodeStatistics::TEST_TYPES << "View specs"
|
83
|
+
::CodeStatistics::TEST_TYPES << "Controller specs"
|
84
|
+
::CodeStatistics::TEST_TYPES << "Helper specs"
|
85
|
+
::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
|
86
|
+
end
|
87
|
+
|
88
|
+
namespace :db do
|
89
|
+
namespace :fixtures do
|
90
|
+
desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
|
91
|
+
task :load => :environment do
|
92
|
+
require 'active_record/fixtures'
|
93
|
+
ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
|
94
|
+
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
|
95
|
+
Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
desc 'Generate documentation for the <%= file_name %> extension.'
|
103
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
104
|
+
rdoc.rdoc_dir = 'rdoc'
|
105
|
+
rdoc.title = '<%= class_name %>'
|
106
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
107
|
+
rdoc.rdoc_files.include('README')
|
108
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
109
|
+
end
|
110
|
+
|
111
|
+
# For extensions that are in transition
|
112
|
+
desc 'Test the <%= file_name %> extension.'
|
113
|
+
Rake::TestTask.new(:test) do |t|
|
114
|
+
t.libs << 'lib'
|
115
|
+
t.pattern = 'test/**/*_test.rb'
|
116
|
+
t.verbose = true
|
117
|
+
end
|
118
|
+
|
119
|
+
# Load any custom rakefiles for extension
|
120
|
+
Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,121 @@
|
|
1
|
+
#content table.index tr.node td {
|
2
|
+
padding: .5em;
|
3
|
+
}
|
4
|
+
|
5
|
+
#content table.index .node .site small {
|
6
|
+
color: #666;
|
7
|
+
font-size: 90%;
|
8
|
+
font-style: italic;
|
9
|
+
font-weight: normal;
|
10
|
+
margin-left: .5em;
|
11
|
+
}
|
12
|
+
|
13
|
+
#content table.index .node .site a, #content table.index .node .site a:visited {
|
14
|
+
font-size: 115%;
|
15
|
+
font-weight: bold;
|
16
|
+
color: black;
|
17
|
+
text-decoration: none;
|
18
|
+
}
|
19
|
+
|
20
|
+
#content .form-area p.domain {
|
21
|
+
float: left;
|
22
|
+
width: 40%;
|
23
|
+
margin: 1em 1em 0 0;
|
24
|
+
overflow: hidden;
|
25
|
+
}
|
26
|
+
|
27
|
+
#content .form-area p.subtitle {
|
28
|
+
margin: 1em 0 0 0;
|
29
|
+
}
|
30
|
+
|
31
|
+
#content .form-area p.domain label, #content .form-area p.homepage label {
|
32
|
+
display: block;
|
33
|
+
position: relative;
|
34
|
+
}
|
35
|
+
|
36
|
+
#content div.form-area p.domain .textbox, #content div.form-area p.homepage .textbox, #content div.form-area p.subtitle .textbox, #content div.form-area p.subtitle select {
|
37
|
+
font-family: Georgia,Palatino,"Times New Roman",Times,serif;
|
38
|
+
font-size: 140%;
|
39
|
+
width: 100%;
|
40
|
+
}
|
41
|
+
|
42
|
+
#content div.form-area p.homepage {
|
43
|
+
float: right;
|
44
|
+
width: 16%;
|
45
|
+
margin: 1em 0 0 0;
|
46
|
+
overflow: hidden;
|
47
|
+
}
|
48
|
+
|
49
|
+
p.formnote {
|
50
|
+
clear: both;
|
51
|
+
padding: 2em 0;
|
52
|
+
}
|
53
|
+
|
54
|
+
|
55
|
+
/* BE SURE TO INCLUDE THE CSS RESET FOUND IN THE DEMO PAGE'S CSS */
|
56
|
+
/*------------------------------------*\
|
57
|
+
NAV
|
58
|
+
\*------------------------------------*/
|
59
|
+
#nav{
|
60
|
+
list-style:none;
|
61
|
+
font-weight:bold;
|
62
|
+
margin-bottom:10px;
|
63
|
+
/* Clear floats */
|
64
|
+
width:100%;
|
65
|
+
padding-top: 10px;
|
66
|
+
padding-bottom: 10px;
|
67
|
+
padding-left: 5px;
|
68
|
+
margin: 0px;
|
69
|
+
/* Bring the nav above everything else--uncomment if needed.
|
70
|
+
position:relative;
|
71
|
+
z-index:5;
|
72
|
+
*/
|
73
|
+
}
|
74
|
+
#nav li{
|
75
|
+
margin-right:10px;
|
76
|
+
position:relative;
|
77
|
+
}
|
78
|
+
#nav a{
|
79
|
+
display:block;
|
80
|
+
padding:5px;
|
81
|
+
color:#fff;
|
82
|
+
background:#333;
|
83
|
+
text-decoration:none;
|
84
|
+
}
|
85
|
+
#nav a:hover{
|
86
|
+
color:#fff;
|
87
|
+
background:#CC494C;
|
88
|
+
text-decoration:underline;
|
89
|
+
}
|
90
|
+
|
91
|
+
/*--- DROPDOWN ---*/
|
92
|
+
|
93
|
+
#nav ul{
|
94
|
+
background:#fff; /* Adding a background makes the dropdown work properly in IE7+. Make this as close to your page's background as possible (i.e. white page == white background). */
|
95
|
+
background:rgba(255,255,255,0); /* But! Let's make the background fully transparent where we can, we don't actually want to see it if we can help it... */
|
96
|
+
list-style:none;
|
97
|
+
position:absolute;
|
98
|
+
left:-9999px; /* Hide off-screen when not needed (this is more accessible than display:none;) */
|
99
|
+
}
|
100
|
+
#nav ul li{
|
101
|
+
padding-top:1px; /* Introducing a padding between the li and the a give the illusion spaced items */
|
102
|
+
float:none;
|
103
|
+
}
|
104
|
+
#nav ul a{
|
105
|
+
white-space:nowrap; /* Stop text wrapping and creating multi-line dropdown items */
|
106
|
+
}
|
107
|
+
#nav li:hover ul{ /* Display the dropdown on hover */
|
108
|
+
left:0; /* Bring back on-screen when needed */
|
109
|
+
z-index: 99999;
|
110
|
+
|
111
|
+
}
|
112
|
+
#nav li:hover a{ /* These create persistent hover states, meaning the top-most link stays 'hovered' even when your cursor has moved down the list. */
|
113
|
+
background:#CC494C;
|
114
|
+
text-decoration:underline;
|
115
|
+
}
|
116
|
+
#nav li:hover ul a{ /* The persistent hover state does however create a global style for links even before they're hovered. Here we undo these effects. */
|
117
|
+
text-decoration:none;
|
118
|
+
}
|
119
|
+
#nav li:hover ul li a:hover{ /* Here we define the most explicit hover states--what happens when you hover each individual link. */
|
120
|
+
background:#333;
|
121
|
+
}
|
File without changes
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Admin::SitesController < Admin::ResourceController
|
2
|
+
helper :sites
|
3
|
+
only_allow_access_to :index, :show, :new, :create, :edit, :update, :remove, :destroy,
|
4
|
+
:when => :admin,
|
5
|
+
:denied_url => { :controller => 'pages', :action => 'index' },
|
6
|
+
:denied_message => 'You must have administrative privileges to perform this action.'
|
7
|
+
|
8
|
+
%w(move_higher move_lower move_to_top move_to_bottom).each do |action|
|
9
|
+
define_method action do
|
10
|
+
model.send(action)
|
11
|
+
response_for :update
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ScopedHelper
|
2
|
+
def self.included(base)
|
3
|
+
|
4
|
+
base.module_eval do
|
5
|
+
def title
|
6
|
+
t = current_site.name
|
7
|
+
t = TrustyCms::Config['admin.title'] || 'Radiant CMS' if t.blank?
|
8
|
+
t
|
9
|
+
end
|
10
|
+
|
11
|
+
def subtitle
|
12
|
+
st = current_site.subtitle
|
13
|
+
st = TrustyCms::Config['admin.subtitle'] || 'publishing for small teams' if st.blank?
|
14
|
+
st
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module SitesHelper
|
2
|
+
def order_links(site)
|
3
|
+
String.new.tap do |output|
|
4
|
+
output << link_to(image("move_to_top.png", :alt => "Move to top"), move_to_top_admin_site_path(site), :method => :put)
|
5
|
+
output << link_to(image("move_higher.png", :alt => "Move up"), move_higher_admin_site_path(site), :method => :post)
|
6
|
+
output << link_to(image("move_lower.png", :alt => "Move down"), move_lower_admin_site_path(site), :method => :post)
|
7
|
+
output << link_to(image("move_to_bottom.png", :alt => "Move to bottom"), move_to_bottom_admin_site_path(site), :method => :put)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/app/models/site.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# The site class includes - in find_for_host - some key retrieval and creation logic that is called from ApplicationController to set the current site context.
|
2
|
+
# Otherwise it's just another radiant data model.
|
3
|
+
|
4
|
+
class Site < ActiveRecord::Base
|
5
|
+
acts_as_list
|
6
|
+
belongs_to :created_by, :class_name => 'User'
|
7
|
+
belongs_to :updated_by, :class_name => 'User'
|
8
|
+
belongs_to :production_homepage , :class_name => 'ProductionPage'
|
9
|
+
|
10
|
+
default_scope :order => 'position ASC'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :several
|
14
|
+
|
15
|
+
# I've added one or two sql queries here for the sake of a separate default method
|
16
|
+
|
17
|
+
def find_for_host(hostname = '')
|
18
|
+
return default if hostname.blank?
|
19
|
+
sites = all(:conditions => "domain IS NOT NULL and domain != ''")
|
20
|
+
site = sites.find { |site| hostname == site.base_domain || hostname =~ Regexp.compile(site.domain) }
|
21
|
+
site || default
|
22
|
+
end
|
23
|
+
|
24
|
+
# Site.default returns the the first site it can find with an empty domain pattern.
|
25
|
+
|
26
|
+
def default
|
27
|
+
find_by_domain('') || find_by_domain(nil) || catchall
|
28
|
+
end
|
29
|
+
|
30
|
+
# If none is found, we are probably brand new, so a workable default site is created.
|
31
|
+
|
32
|
+
def catchall
|
33
|
+
create({
|
34
|
+
:domain => '',
|
35
|
+
:name => 'default_site',
|
36
|
+
:base_domain => 'localhost',
|
37
|
+
:homepage => Page.find_by_parent_id(nil)
|
38
|
+
})
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns true if more than one site is present. This is normally only used to make interface decisions, eg whether to show the site-chooser dropdown.
|
42
|
+
|
43
|
+
def several?
|
44
|
+
several = (count > 1) if several.nil?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
belongs_to :homepage, :class_name => "Page", :foreign_key => "homepage_id"
|
49
|
+
validates_presence_of :name, :base_domain
|
50
|
+
validates_uniqueness_of :domain
|
51
|
+
|
52
|
+
after_create :create_homepage
|
53
|
+
after_save :reload_routes
|
54
|
+
|
55
|
+
# Returns the fully specified web address for the supplied path, or the root of this site if no path is given.
|
56
|
+
|
57
|
+
def url(path = "/")
|
58
|
+
uri = URI.join("http://#{self.base_domain}", path)
|
59
|
+
uri.to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the fully specified web address for the development version of this site and the supplied path, or the root of this site if no path is given.
|
63
|
+
|
64
|
+
def dev_url(path = "/")
|
65
|
+
uri = URI.join("http://#{TrustyCms::Config['dev.host'] || 'dev'}.#{self.base_domain}", path)
|
66
|
+
uri.to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_homepage
|
70
|
+
if self.homepage_id.blank?
|
71
|
+
self.homepage = self.build_homepage(:title => "#{self.name} Homepage",
|
72
|
+
:slug => "#{self.name.to_slug}", :breadcrumb => "Home")
|
73
|
+
default_status = TrustyCms::Config['defaults.page.status']
|
74
|
+
self.homepage.status = Status[default_status] if default_status
|
75
|
+
default_parts = TrustyCms::Config['defaults.page.parts'].to_s.strip.split(/\s*,\s*/)
|
76
|
+
default_parts.each do |name|
|
77
|
+
self.homepage.parts << PagePart.new(:name => name, :filter_id => TrustyCms::Config['defaults.page.filter'])
|
78
|
+
end
|
79
|
+
save
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def reload_routes
|
84
|
+
TrustyCms::Application.reload_routes!
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
- unless current_user.site
|
2
|
+
%p{:style => 'float: left; margin-right: 32px'}
|
3
|
+
%label{:for=>"layout_site_id", :Class => "admin_only"}
|
4
|
+
Site
|
5
|
+
- sites = Site.find(:all).map { |s| [s.name, s.id] }
|
6
|
+
- selection = {:include_blank => Layout.is_shareable?}
|
7
|
+
- selection[:selected] = current_site.id if @layout.new_record? && ! Layout.is_shareable?
|
8
|
+
= select :layout, :site_id, sites, selection
|
@@ -0,0 +1,43 @@
|
|
1
|
+
= render_region :form_top
|
2
|
+
.form-area
|
3
|
+
- render_region :form do |form|
|
4
|
+
|
5
|
+
- form.edit_name do
|
6
|
+
%p.title
|
7
|
+
%label{:for => "site_name"} Site title
|
8
|
+
= f.text_field :name, :class => "textbox"
|
9
|
+
|
10
|
+
%p.subtitle
|
11
|
+
%label{:for => "site_subtitle"} and subtitle
|
12
|
+
= f.text_field :subtitle, :class => "textbox"
|
13
|
+
|
14
|
+
|
15
|
+
- form.edit_domain do
|
16
|
+
%p.domain
|
17
|
+
%label{:for => "site_domain"}
|
18
|
+
Domain pattern
|
19
|
+
= f.text_field :domain, :class => "textbox"
|
20
|
+
%p.domain
|
21
|
+
%label{:for => "site_base_domain"}
|
22
|
+
Base domain name
|
23
|
+
= f.text_field :base_domain, :class => "textbox"
|
24
|
+
|
25
|
+
- form.edit_homepage do
|
26
|
+
%p.homepage
|
27
|
+
%label{:for => "site_homepage_id"} Homepage ID
|
28
|
+
= f.text_field :homepage_id, :class => "textbox"
|
29
|
+
|
30
|
+
%p.formnote
|
31
|
+
- if f.object.new_record?
|
32
|
+
A new homepage will be created automatically if none is specified.
|
33
|
+
- else
|
34
|
+
If you change the site homepage ID, be sure it is is to a page with no parents.
|
35
|
+
|
36
|
+
- render_region :form_bottom do |form_bottom|
|
37
|
+
- form_bottom.edit_timestamp do
|
38
|
+
= updated_stamp @site
|
39
|
+
- form_bottom.edit_buttons do
|
40
|
+
%p.buttons
|
41
|
+
= save_model_button(@site)
|
42
|
+
or
|
43
|
+
= link_to "cancel", admin_sites_path
|
@@ -0,0 +1,7 @@
|
|
1
|
+
= stylesheet_link_tag 'admin/multi_site'
|
2
|
+
- render_region :main do |main|
|
3
|
+
- main.edit_header do
|
4
|
+
%h1 Edit Site
|
5
|
+
- main.edit_form do
|
6
|
+
= form_for @site, :url => admin_site_path(@site), :html => {:method => :put} do |f|
|
7
|
+
= render :partial => "form", :locals => {:f => f}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
= stylesheet_link_tag 'admin/multi_site'
|
2
|
+
= render_region :top
|
3
|
+
|
4
|
+
%h1 Sites
|
5
|
+
|
6
|
+
%table{:class => "index", :cellspacing => "0"}
|
7
|
+
%thead
|
8
|
+
%tr
|
9
|
+
- render_region :thead do |thead|
|
10
|
+
- thead.title_header do
|
11
|
+
%th.site Name
|
12
|
+
- thead.domain_header do
|
13
|
+
%th.domain Domain match
|
14
|
+
- thead.basedomain_header do
|
15
|
+
%th.basedomain Base domain
|
16
|
+
- thead.modify_header do
|
17
|
+
%th.remove Delete
|
18
|
+
- thead.order_header do
|
19
|
+
%th.order Order
|
20
|
+
%tbody
|
21
|
+
- @sites.each do |site|
|
22
|
+
%tr.node.level-1
|
23
|
+
- render_region :tbody do |tbody|
|
24
|
+
- tbody.title_cell do
|
25
|
+
%td.site
|
26
|
+
= link_to site.name, edit_admin_site_path(site)
|
27
|
+
- tbody.domain_cell do
|
28
|
+
%td.domain
|
29
|
+
= h(site.domain)
|
30
|
+
- tbody.basedomain_cell do
|
31
|
+
%td.basedomain
|
32
|
+
= h(site.base_domain)
|
33
|
+
- tbody.modify_cell do
|
34
|
+
%td.remove
|
35
|
+
= link_to image("remove.png", :alt => "Remove"), admin_site_path(site), :method => :delete, :confirm => "Are you sure you want to permanently remove #{site.name}?"
|
36
|
+
- tbody.order_cell do
|
37
|
+
%td.order
|
38
|
+
= order_links(site).html_safe
|
39
|
+
|
40
|
+
- render_region :bottom do |bottom|
|
41
|
+
- bottom.new_button do
|
42
|
+
%p
|
43
|
+
= link_to "New Site", new_admin_site_path
|
@@ -0,0 +1,6 @@
|
|
1
|
+
- unless current_user.site
|
2
|
+
%p{:style => 'float: left; margin-right: 32px'}
|
3
|
+
%label{:for=>"snippet_site_id", :Class => "admin_only"}
|
4
|
+
Site
|
5
|
+
- sites = Site.find(:all).map { |s| [s.name, s.id] }
|
6
|
+
= select :snippet, :site_id, sites, :include_blank => Snippet.is_shareable?, :selected => @snippet.site_id || current_site.id
|
@@ -0,0 +1,8 @@
|
|
1
|
+
- unless current_user.site
|
2
|
+
%tr
|
3
|
+
%th.label
|
4
|
+
%label{:for=>"user_admin"} Can edit site
|
5
|
+
%td.field{:style => 'text-align: left;'}
|
6
|
+
= select :user, :site_id, [['<any>', '']] + Site.find(:all).map { |s| [s.name, s.id] }
|
7
|
+
%td.help
|
8
|
+
A user with no site is able to act (to whatever extent their status allows) on any site.
|
data/config/routes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
TrustyCms::Application.routes.draw do
|
3
|
+
namespace :admin do
|
4
|
+
resources :sites do
|
5
|
+
get :remove, on: :member
|
6
|
+
post :move_higher, on: :member
|
7
|
+
post :move_lower, on: :member
|
8
|
+
put :move_to_top, on: :member
|
9
|
+
put :move_to_bottom, on: :member
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|