we5-browsercms 3.0.2 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. data/README.markdown +1 -0
  2. data/app/controllers/cms/content_block_controller.rb +25 -2
  3. data/app/controllers/cms/content_controller.rb +31 -2
  4. data/app/controllers/cms/dashboard_controller.rb +2 -1
  5. data/app/controllers/cms/error_handling.rb +9 -2
  6. data/app/controllers/cms/links_controller.rb +2 -0
  7. data/app/controllers/cms/pages_controller.rb +22 -18
  8. data/app/controllers/cms/section_nodes_controller.rb +1 -1
  9. data/app/controllers/cms/sections_controller.rb +12 -7
  10. data/app/controllers/cms/sessions_controller.rb +17 -10
  11. data/app/controllers/cms/users_controller.rb +8 -6
  12. data/app/helpers/cms/application_helper.rb +2 -6
  13. data/app/helpers/cms/menu_helper.rb +118 -146
  14. data/app/helpers/cms/page_helper.rb +2 -2
  15. data/app/models/attachment.rb +2 -2
  16. data/app/models/forgot_password_mailer.rb +12 -0
  17. data/app/models/group.rb +13 -2
  18. data/app/models/guest_user.rb +9 -3
  19. data/app/models/link.rb +2 -2
  20. data/app/models/page.rb +1 -1
  21. data/app/models/section.rb +7 -2
  22. data/app/models/user.rb +35 -17
  23. data/app/portlets/forgot_password_portlet.rb +27 -0
  24. data/app/portlets/reset_password_portlet.rb +28 -0
  25. data/app/views/cms/blocks/_toolbar_for_member.html.erb +3 -3
  26. data/app/views/cms/blocks/index.html.erb +11 -6
  27. data/app/views/cms/content/show.html.erb +3 -3
  28. data/app/views/cms/forgot_password_mailer/reset_password.text.html.erb +3 -0
  29. data/app/views/cms/forgot_password_mailer/reset_password.text.plain.erb +3 -0
  30. data/app/views/cms/menus/_menu.html.erb +9 -0
  31. data/app/views/cms/menus/_menu_item.html.erb +11 -0
  32. data/app/views/cms/pages/_edit_connector.html.erb +1 -1
  33. data/app/views/cms/pages/_edit_container.html.erb +1 -1
  34. data/app/views/cms/section_nodes/_node.html.erb +1 -1
  35. data/app/views/cms/sections/_form.html.erb +36 -34
  36. data/app/views/cms/shared/access_denied.html.erb +3 -0
  37. data/app/views/cms/users/change_password.html.erb +8 -6
  38. data/app/views/cms/users/index.html.erb +1 -1
  39. data/app/views/cms/users/show.html.erb +50 -0
  40. data/app/views/layouts/_cms_toolbar.html.erb +1 -1
  41. data/app/views/layouts/_page_toolbar.html.erb +7 -7
  42. data/app/views/layouts/cms/administration.html.erb +24 -7
  43. data/app/views/portlets/forgot_password/_form.html.erb +5 -0
  44. data/app/views/portlets/forgot_password/render.html.erb +14 -0
  45. data/app/views/portlets/reset_password/_form.html.erb +3 -0
  46. data/app/views/portlets/reset_password/render.html.erb +24 -0
  47. data/{we5-browsercms.gemspec → browsercms.gemspec} +72 -54
  48. data/db/migrate/20091109175123_browsercms_3_0_5.rb +9 -0
  49. data/lib/acts_as_list.rb +8 -4
  50. data/lib/cms/acts/content_block.rb +1 -1
  51. data/lib/cms/authentication/controller.rb +26 -7
  52. data/lib/cms/behaviors/attaching.rb +3 -3
  53. data/lib/cms/behaviors/publishing.rb +12 -1
  54. data/lib/cms/behaviors/rendering.rb +17 -4
  55. data/lib/cms/behaviors/versioning.rb +2 -2
  56. data/lib/cms/routes.rb +4 -0
  57. data/lib/tasks/cms.rake +0 -18
  58. data/public/javascripts/cms/content_library.js +36 -0
  59. data/public/javascripts/cms/sitemap.js +21 -9
  60. data/public/stylesheets/cms/form_layout.css +16 -2
  61. data/public/stylesheets/cms/nav.css +4 -3
  62. data/test/functional/cms/content_block_controller_test.rb +120 -0
  63. data/test/functional/cms/content_controller_test.rb +135 -80
  64. data/test/functional/cms/links_controller_test.rb +89 -1
  65. data/test/functional/cms/pages_controller_test.rb +138 -0
  66. data/test/functional/cms/section_nodes_controller_test.rb +45 -5
  67. data/test/functional/cms/sections_controller_test.rb +148 -1
  68. data/test/functional/cms/sessions_controller_test.rb +26 -2
  69. data/test/functional/cms/users_controller_test.rb +49 -2
  70. data/test/integration/cms/password_management_test.rb +57 -0
  71. data/test/test_helper.rb +3 -1
  72. data/test/unit/behaviors/attaching_test.rb +26 -0
  73. data/test/unit/helpers/menu_helper_test.rb +118 -278
  74. data/test/unit/models/group_test.rb +6 -0
  75. data/test/unit/models/user_test.rb +127 -29
  76. metadata +20 -3
@@ -28,4 +28,4 @@ module Cms
28
28
  end
29
29
  end
30
30
  end
31
- end
31
+ end
@@ -1,3 +1,27 @@
1
+ #
2
+ # Defines the authentication behavior for controllers in BrowserCMS. It can be added to any controller that needs to
3
+ # hook into the BrowserCMS Authentication behavior like so:
4
+ #
5
+ # class MySuperSecureController < ApplicationController
6
+ # include Cms::Authentication::Controller
7
+ #
8
+ # It is based off Restful_Authentication, and adds in behavior to deal with several concepts specific to BrowserCMS.
9
+ #
10
+ # (Note: 10/8/09 - I was comparing this to a very old version of the generated code from Restful_Authentication,
11
+ # so some of the following items may be 'stock' to that. (Especially #2)
12
+ #
13
+ # 1. Guests - These represents users that are not logged in. What guests can see and do can be modified via the CMS UI. Guests
14
+ # are not considered to be 'logged in'.
15
+ # 2. 'Current' User - The currently logged in user is stored in a thread local, and can be accessed anywhere via 'User.current'.
16
+ # This allows model code to easily record which user is making changes to records, for versioning, etc.
17
+ #
18
+ # 3. 'Admin' Access Denied Page - If users try to access a protected controller, they are redirected to the CMS administration Login page
19
+ # which may be different than the 'front end' user login page. (Cms::Controller handles that differently)
20
+ #
21
+ #
22
+ # To Dos: It appears as though we are storing the 'current' user in two places, @current_user and User.current. This is probably not DRY, but
23
+ # more testing would be needed.
24
+ #
1
25
  module Cms
2
26
  module Authentication
3
27
  module Controller
@@ -12,6 +36,7 @@ module Cms
12
36
  # If the user is not logged in, this will be set to the guest user, which represents a public
13
37
  # user, who will likely have more limited permissions
14
38
  def current_user
39
+ # Note: We have disabled basic_http_auth
15
40
  @current_user ||= begin
16
41
  User.current = (login_from_session || login_from_cookie || User.guest)
17
42
  end
@@ -61,7 +86,7 @@ module Cms
61
86
 
62
87
  # Redirect as appropriate when an access request fails.
63
88
  #
64
- # The default action is to redirect to the login screen.
89
+ # The default action is to redirect to the BrowserCMS admin login screen.
65
90
  #
66
91
  # Override this method in your controllers if you want to have special
67
92
  # behavior in case the user is not authorized
@@ -73,11 +98,6 @@ module Cms
73
98
  store_location
74
99
  redirect_to cms_login_path
75
100
  end
76
- # format.any doesn't work in rails version < http://dev.rubyonrails.org/changeset/8987
77
- # you may want to change format.any to e.g. format.any(:js, :xml)
78
- # format.any do
79
- # request_http_basic_authentication 'Web Password'
80
- # end
81
101
  end
82
102
  end
83
103
 
@@ -162,7 +182,6 @@ module Cms
162
182
 
163
183
  # Cookies shouldn't be allowed to persist past their freshness date,
164
184
  # and they should be changed at each login
165
-
166
185
  def valid_remember_cookie?
167
186
  return nil unless User.current
168
187
  (User.current.remember_token?) &&
@@ -118,14 +118,14 @@ module Cms
118
118
 
119
119
  # Override this method if you would like to override the way the section is set
120
120
  def set_attachment_section
121
- if new_record? && !attachment_file.blank?
121
+ if !attachment_file.blank?
122
122
  attachment.section = Section.root.first
123
123
  end
124
124
  end
125
125
 
126
126
  # Override this method if you would like to override the way file_path is set
127
127
  def set_attachment_file_path
128
- if new_record? && !attachment_file.blank?
128
+ if !attachment_file.blank?
129
129
  attachment.file_path = "/attachments/#{File.basename(attachment_file.original_filename).to_s.downcase}"
130
130
  end
131
131
  end
@@ -181,4 +181,4 @@ module Cms
181
181
  end
182
182
  end
183
183
  end
184
- end
184
+ end
@@ -23,7 +23,18 @@ module Cms
23
23
  after_save :publish_for_non_versioned
24
24
 
25
25
  named_scope :published, :conditions => {:published => true}
26
- named_scope :unpublished, :conditions => {:published => false}
26
+ named_scope :unpublished, lambda {
27
+ if versioned?
28
+ { :joins => :versions,
29
+ :conditions =>
30
+ "#{connection.quote_table_name(version_table_name)}.#{connection.quote_column_name('version')} > " +
31
+ "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name('version')}",
32
+ :select => "distinct #{connection.quote_table_name(table_name)}.*" }
33
+ else
34
+ { :conditions => { :published => false } }
35
+ end
36
+ }
37
+
27
38
  end
28
39
  end
29
40
  module ClassMethods
@@ -82,7 +82,7 @@ module Cms
82
82
 
83
83
  end
84
84
  module InstanceMethods
85
- def perform_render(controller)
85
+ def prepare_to_render(controller)
86
86
  # Give this renderable a reference to the controller
87
87
  @controller = controller
88
88
 
@@ -90,12 +90,21 @@ module Cms
90
90
 
91
91
  # This gives the view a reference to this object
92
92
  instance_variable_set(self.class.instance_variable_name_for_view, self)
93
-
93
+
94
94
  # This is like a controller action
95
95
  # We will call it if you have defined a render method
96
96
  # but if you haven't we won't
97
97
  render if respond_to?(:render)
98
+ end
98
99
 
100
+ def perform_render(controller)
101
+ return "Exception: #{@render_exception}" if @render_exception
102
+ unless @controller
103
+ # We haven't prepared to render. This should only happen when logged in, as we don't want
104
+ # errors to bubble up and prevent the page being edited in that case.
105
+ prepare_to_render(controller)
106
+ end
107
+
99
108
  # Create, Instantiate and Initialize the view
100
109
  view_class = Class.new(ActionView::Base)
101
110
  action_view = view_class.new(@controller.view_paths, {}, @controller)
@@ -108,7 +117,7 @@ module Cms
108
117
 
109
118
  # We want content_for to be called on the controller's view, not this inner view
110
119
  def action_view.content_for(name, content=nil, &block)
111
- controller.instance_variable_get("@template").content_for(name, content, &block)
120
+ @controller.instance_variable_get("@template").content_for(name, content, &block)
112
121
  end
113
122
 
114
123
  # Copy instance variables from this renderable object to it's view
@@ -122,6 +131,10 @@ module Cms
122
131
  end
123
132
  end
124
133
 
134
+ def render_exception=(exception)
135
+ @render_exception = exception
136
+ end
137
+
125
138
  protected
126
139
  def copy_instance_variables_from_controller!
127
140
  if @controller.respond_to?(:instance_variables_for_rendering)
@@ -141,4 +154,4 @@ module Cms
141
154
 
142
155
  end
143
156
  end
144
- end
157
+ end
@@ -110,7 +110,7 @@ module Cms
110
110
  def save(perform_validations=true)
111
111
  transaction do
112
112
  #logger.info "..... Calling valid?"
113
- return false unless valid?
113
+ return false unless !perform_validations || valid?
114
114
 
115
115
  if changed?
116
116
  #logger.info "..... Changes => #{changes.inspect}"
@@ -172,7 +172,7 @@ module Cms
172
172
  end
173
173
 
174
174
  def save!(perform_validations=true)
175
- save || raise(ActiveRecord::RecordNotSaved.new(errors.full_messages))
175
+ save(perform_validations) || raise(ActiveRecord::RecordNotSaved.new(errors.full_messages))
176
176
  end
177
177
 
178
178
  def draft
data/lib/cms/routes.rb CHANGED
@@ -119,6 +119,10 @@ module Cms::Routes
119
119
  :enable => :put
120
120
  }
121
121
 
122
+ if RAILS_ENV == "test" && File.expand_path(RAILS_ROOT) == File.expand_path(File.dirname(__FILE__) + "/../..")
123
+ cms.content_blocks :content_block
124
+ end
125
+
122
126
  end
123
127
 
124
128
  if PageRoute.table_exists?
data/lib/tasks/cms.rake CHANGED
@@ -9,24 +9,6 @@ end
9
9
 
10
10
  namespace :cms do
11
11
 
12
- desc "DEPRECATED"
13
- task :install do
14
- puts "This task has been deprecated, please use 'rake install' instead"
15
- end
16
-
17
- desc "Bumps the build number in lib/cms/init.rb"
18
- task :bump_build_number do
19
- init_file = Rails.root.join("lib/cms/init.rb")
20
- s = File.read(init_file)
21
- open(init_file, 'w') do |f|
22
- f << s.sub(/def build_number; (\d+) end/) do |s|
23
- new_build_number = $1.to_i + 1
24
- puts "Build number bumped to #{new_build_number}"
25
- "def build_number; #{new_build_number} end"
26
- end
27
- end
28
- end
29
-
30
12
  desc "Generate guides for the CMS"
31
13
  task :guides do
32
14
  require 'rubygems'
@@ -0,0 +1,36 @@
1
+ jQuery(function($){
2
+
3
+ //----- Helper Functions -----------------------------------------------------
4
+ //In all of this code, we are defining functions that we use later
5
+ //None of this actually manipulates the DOM in any way
6
+
7
+ //This is used to get the id part of an elementId
8
+ //For example, if you have section_node_5,
9
+ //you pass this 'section_node_5', 'section_node'
10
+ //and this returns 5
11
+ var getId = function(elementId, s) {
12
+ return elementId.replace(s,'')
13
+ }
14
+
15
+
16
+ var nodeOnDoubleClick = function() {
17
+ if($('#edit_button').hasClass('disabled')) {
18
+ //$('#view_button').click()
19
+ location.href = $('#view_button')[0].href
20
+ } else {
21
+ //$('#edit_button').click()
22
+ location.href = $('#edit_button')[0].href
23
+ }
24
+ }
25
+
26
+ var addNodeOnDoubleClick = function() {
27
+ $('#blocks tr').dblclick(nodeOnDoubleClick)
28
+ }
29
+
30
+ //----- Init -----------------------------------------------------------------
31
+ //In other words, stuff that happens when the page loads
32
+ //This is where we actually manipulate the DOM, fire events, etc.
33
+
34
+ addNodeOnDoubleClick()
35
+
36
+ })
@@ -187,15 +187,26 @@ jQuery(function($){
187
187
  }
188
188
 
189
189
  var enableButtonsForNode = function(node) {
190
- var id = getId(node.id, /(section|page|link)_/)
191
- if($(node).hasClass('section')) {
192
- enableButtonsForSection(id)
193
- } else if($(node).hasClass('page')) {
194
- enableButtonsForPage(id)
195
- } else if($(node).hasClass('link')) {
196
- enableButtonsForLink(id)
197
- }
198
- }
190
+ var id = getId(node.id, /(section|page|link)_/);
191
+ if(!$(node).is(".non-editable")) {
192
+ if($(node).hasClass('section')) {
193
+ enableButtonsForSection(id);
194
+ } else if($(node).hasClass('page')) {
195
+ enableButtonsForPage(id);
196
+ } else if($(node).hasClass('link')) {
197
+ enableButtonsForLink(id);
198
+ }
199
+ }else if($(node).hasClass('page')) {
200
+ $('#edit-button')
201
+ .html('<span>View Page</span>')
202
+ .removeClass('disabled')
203
+ .attr('href','/cms/pages/'+id)
204
+ .unbind('click')
205
+ .click(function(){return true});
206
+ } else {
207
+ $('#properties-button').attr('href','/cms/sitemap');
208
+ }
209
+ };
199
210
 
200
211
  var enableButtonsForSection = function(id) {
201
212
  $('#properties-button')
@@ -253,6 +264,7 @@ jQuery(function($){
253
264
 
254
265
  var enableButtonsForPage = function(id) {
255
266
  $('#edit-button')
267
+ .html('<span>Edit Page</span>')
256
268
  .removeClass('disabled')
257
269
  .attr('href','/cms/pages/'+id)
258
270
  .unbind('click')
@@ -1,6 +1,6 @@
1
1
  @import url(/stylesheets/cms/selectbox.css);
2
2
 
3
- form {
3
+ form, .faux_form {
4
4
  font-size: 10pt;
5
5
  font-family: "Trebuchet MS", Helvetica, Verdana, Arial, sans-serif;
6
6
  color:#485561;
@@ -21,6 +21,19 @@ padding: 10px 0;
21
21
  background: url(/images/cms/dashed.gif) repeat-x 100% 100%;
22
22
  }
23
23
 
24
+ /* Fake forms */
25
+ .faux_form .fields {
26
+ padding: 22px 0 10px 0;
27
+ font-size: 12px;
28
+ overflow: hidden;
29
+ }
30
+ .faux_form .fields .label {
31
+ padding: 0 0 12px 0;
32
+ float: left;
33
+ width: 140px;
34
+ font-weight: bold;
35
+ }
36
+
24
37
  /* LABELS */
25
38
  .text_fields label,
26
39
  .textarea_fields label,
@@ -39,7 +52,8 @@ font-size: 12px;
39
52
  .select_fields label,
40
53
  .text_editor_fields label,
41
54
  .file_fields label,
42
- .checkboxes label
55
+ .checkboxes label,
56
+ .faux_label
43
57
  {
44
58
  font-weight: bold;
45
59
  font-size: 12px;
@@ -70,13 +70,14 @@ color: #666;
70
70
  font-weight: bold;
71
71
  }
72
72
 
73
- #nav ul#userlinks li a, #nav ul#userlinks li span {
74
- padding: 8px 19px 11px 19px;
73
+ #nav ul#userlinks li a {
74
+ padding: 4px 19px 11px 19px;
75
75
  background: url(/images/cms/usercontrols_bg_cap.png) no-repeat 100% 0;
76
76
  color: #666;
77
77
  display: block;
78
78
  float: left;
79
79
  text-decoration: none;
80
+ line-height: 18px;
80
81
  }
81
82
 
82
83
  #nav ul#userlinks li span {
@@ -88,7 +89,7 @@ padding: 9px 10px;
88
89
  }
89
90
  #nav ul#userlinks li#user_info img {
90
91
  float:left;
91
- margin: 4px 0 0 5px;
92
+ margin: 0 5px 0 0;
92
93
  }
93
94
 
94
95
  #nav .cmssearch {
@@ -0,0 +1,120 @@
1
+ require File.join(File.dirname(__FILE__), '/../../test_helper')
2
+
3
+ class PermissionsForContentBlockControllerTest < ActionController::TestCase
4
+ include Cms::ControllerTestHelper
5
+ tests Cms::ContentBlockController
6
+
7
+ # We're stubbing a lot because we *just* want to isolate the behaviour for checking permissions
8
+ def setup
9
+ login_as_cms_admin
10
+ @user = User.first
11
+ @controller.stubs(:current_user).returns(@user)
12
+ @controller.stubs(:render)
13
+ @controller.stubs(:model_class).returns(ContentBlock)
14
+ @controller.stubs(:set_default_category)
15
+ @controller.stubs(:blocks_path).returns("/cms/content_block")
16
+ @controller.stubs(:redirect_to_first).returns("/cms/content_block")
17
+
18
+ @block = stub_everything("block")
19
+ @block.stubs(:as_of_draft_version).returns(@block)
20
+ @block.stubs(:as_of_version).returns(@block)
21
+ @block.stubs(:connected_pages).returns(stub(:all => stub))
22
+
23
+ ContentBlock.stubs(:find).returns(@block)
24
+ ContentBlock.stubs(:new).returns(@block)
25
+ ContentBlock.stubs(:paginate)
26
+ end
27
+
28
+ def expect_access_denied
29
+ @controller.expects(:render).with(has_entry(:status => 403))
30
+ end
31
+
32
+ def expect_success
33
+ expect_access_denied.never
34
+ end
35
+
36
+ test "GET index allows any user" do
37
+ expect_success
38
+ get :index
39
+ end
40
+
41
+ test "GET show allows any user" do
42
+ expect_success
43
+ get :show, :id => 5
44
+ end
45
+
46
+ test "GET new allows any user" do
47
+ expect_success
48
+ get :new
49
+ end
50
+
51
+ test "POST create allows any user" do
52
+ expect_success
53
+ post :create
54
+ end
55
+
56
+ test "GET version allows any user" do
57
+ expect_success
58
+ get :version, :id => 5, :version => 3
59
+ end
60
+
61
+ test "GET versions allows any user" do
62
+ expect_success
63
+ get :versions, :id => 5
64
+ end
65
+
66
+ test "GET usages allows any user" do
67
+ expect_success
68
+ get :usages, :id => 5
69
+ end
70
+
71
+ test "GET edit allows only users who are able to edit the block" do
72
+ @user.stubs(:able_to_edit?).with(@block).returns(false)
73
+ expect_access_denied
74
+ get :edit, :id => 5
75
+
76
+ @user.stubs(:able_to_edit?).with(@block).returns(true)
77
+ expect_success
78
+ get :edit, :id => 5
79
+ end
80
+
81
+ test "PUT update allows only users who are able to edit the block" do
82
+ @user.stubs(:able_to_edit?).with(@block).returns(false)
83
+ expect_access_denied
84
+ put :update, :id => 5
85
+
86
+ @user.stubs(:able_to_edit?).with(@block).returns(true)
87
+ expect_success
88
+ put :update, :id => 5
89
+ end
90
+
91
+ test "DELETE destroy allows only users who are able to publish the block" do
92
+ @user.stubs(:able_to_publish?).with(@block).returns(false)
93
+ expect_access_denied
94
+ delete :destroy, :id => 5
95
+
96
+ @user.stubs(:able_to_publish?).with(@block).returns(true)
97
+ expect_success
98
+ delete :destroy, :id => 5
99
+ end
100
+
101
+ test "PUT publish allows only users who are able to publish the block" do
102
+ @user.stubs(:able_to_publish?).with(@block).returns(false)
103
+ expect_access_denied
104
+ put :publish, :id => 5
105
+
106
+ @user.stubs(:able_to_publish?).with(@block).returns(true)
107
+ expect_success
108
+ put :publish, :id => 5
109
+ end
110
+
111
+ test "PUT revert_to allows only users who are able to publish the block" do
112
+ @user.stubs(:able_to_publish?).with(@block).returns(false)
113
+ expect_access_denied
114
+ put :revert_to, :id => 5, :version => 1
115
+
116
+ @user.stubs(:able_to_publish?).with(@block).returns(true)
117
+ expect_success
118
+ put :revert_to, :id => 5, :version => 1
119
+ end
120
+ end