browsercms 3.0.2 → 3.0.3

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 (64) hide show
  1. data/app/controllers/cms/content_block_controller.rb +25 -2
  2. data/app/controllers/cms/content_controller.rb +31 -2
  3. data/app/controllers/cms/dashboard_controller.rb +2 -1
  4. data/app/controllers/cms/error_handling.rb +9 -2
  5. data/app/controllers/cms/links_controller.rb +2 -0
  6. data/app/controllers/cms/pages_controller.rb +22 -18
  7. data/app/controllers/cms/section_nodes_controller.rb +1 -1
  8. data/app/controllers/cms/sections_controller.rb +12 -7
  9. data/app/controllers/cms/sessions_controller.rb +17 -10
  10. data/app/controllers/cms/users_controller.rb +8 -6
  11. data/app/helpers/cms/application_helper.rb +2 -6
  12. data/app/helpers/cms/menu_helper.rb +118 -146
  13. data/app/helpers/cms/page_helper.rb +2 -2
  14. data/app/models/attachment.rb +2 -2
  15. data/app/models/group.rb +13 -2
  16. data/app/models/guest_user.rb +9 -3
  17. data/app/models/link.rb +2 -2
  18. data/app/models/page.rb +1 -1
  19. data/app/models/section.rb +7 -2
  20. data/app/models/user.rb +35 -17
  21. data/app/views/cms/blocks/_toolbar_for_member.html.erb +3 -3
  22. data/app/views/cms/blocks/index.html.erb +11 -6
  23. data/app/views/cms/content/show.html.erb +3 -3
  24. data/app/views/cms/menus/_menu.html.erb +9 -0
  25. data/app/views/cms/menus/_menu_item.html.erb +11 -0
  26. data/app/views/cms/pages/_edit_connector.html.erb +1 -1
  27. data/app/views/cms/pages/_edit_container.html.erb +1 -1
  28. data/app/views/cms/section_nodes/_node.html.erb +1 -1
  29. data/app/views/cms/sections/_form.html.erb +36 -34
  30. data/app/views/cms/shared/access_denied.html.erb +3 -0
  31. data/app/views/cms/users/change_password.html.erb +8 -6
  32. data/app/views/cms/users/index.html.erb +1 -1
  33. data/app/views/cms/users/show.html.erb +50 -0
  34. data/app/views/layouts/_cms_toolbar.html.erb +1 -1
  35. data/app/views/layouts/_page_toolbar.html.erb +7 -7
  36. data/app/views/layouts/cms/administration.html.erb +24 -7
  37. data/browsercms.gemspec +13 -7
  38. data/lib/acts_as_list.rb +8 -4
  39. data/lib/cms/acts/content_block.rb +1 -1
  40. data/lib/cms/authentication/controller.rb +26 -7
  41. data/lib/cms/behaviors/attaching.rb +3 -3
  42. data/lib/cms/behaviors/publishing.rb +12 -1
  43. data/lib/cms/behaviors/rendering.rb +17 -4
  44. data/lib/cms/behaviors/versioning.rb +2 -2
  45. data/lib/cms/routes.rb +4 -0
  46. data/lib/tasks/cms.rake +0 -18
  47. data/public/javascripts/cms/content_library.js +36 -0
  48. data/public/javascripts/cms/sitemap.js +21 -9
  49. data/public/stylesheets/cms/form_layout.css +16 -2
  50. data/public/stylesheets/cms/nav.css +4 -3
  51. data/test/functional/cms/content_block_controller_test.rb +120 -0
  52. data/test/functional/cms/content_controller_test.rb +135 -80
  53. data/test/functional/cms/links_controller_test.rb +89 -1
  54. data/test/functional/cms/pages_controller_test.rb +138 -0
  55. data/test/functional/cms/section_nodes_controller_test.rb +45 -5
  56. data/test/functional/cms/sections_controller_test.rb +148 -1
  57. data/test/functional/cms/sessions_controller_test.rb +26 -2
  58. data/test/functional/cms/users_controller_test.rb +49 -2
  59. data/test/test_helper.rb +3 -1
  60. data/test/unit/behaviors/attaching_test.rb +26 -0
  61. data/test/unit/helpers/menu_helper_test.rb +118 -278
  62. data/test/unit/models/group_test.rb +6 -0
  63. data/test/unit/models/user_test.rb +127 -29
  64. metadata +12 -4
@@ -1,30 +1,63 @@
1
1
  module Cms
2
2
  module MenuHelper
3
- # This will render a menu based on the page
4
- # With no options passed, it will render a menu that shows all the child sections of the root
5
- # and then it will show the path of decendent sections all the way to the current page.
6
- # The resulting HTML is a DIV with a UL in it. Each LI will have an A in it. If the item is a Section,
7
- # the HREF of the A will be the URL of the first non-archived page that is a direct child of that Section.
8
- # Hidden pages will not show up, but if the first page in a Section is hidden, it will be used as the URL
9
- # for that Section. This is commonly done to have a page for a Section and avoid having duplicates in the
10
- # navigation.
3
+ # Renders a menu. There are two options, neither are required:
4
+ #
5
+ # ==== Options
6
+ # * <tt>:items</tt> - The items which should appear in the menu. This defaults to calling
7
+ # menu_items which generates items automatically based on the current page. But you can use
8
+ # this option to pass in a custom menu structure.
9
+ # * <tt>:partial</tt> - The partial used to render the menu. By default this is "partials/menu",
10
+ # which can be customised through the CMS. The partial gets a local variable <tt>items</tt>.
11
+ #
12
+ # ==== Structure of items
11
13
  #
14
+ # The items should be an array of hashes, in a tree. Each hash can have the following keys (name
15
+ # and url are required, others are optional):
16
+ #
17
+ # * <tt>:name</tt> - The name which appears in the menu
18
+ # * <tt>:url</tt> - The URL to link to
19
+ # * <tt>:id</tt> - The id for the menu item
20
+ # * <tt>:selected</tt> - Boolean value to indicate whether the menu item is the current page
21
+ # * <tt>:target</tt> - The target attribute for the link
22
+ # * <tt>:children</tt> - An array of hashes containing the child menu items. This is where the
23
+ # tree structure comes in.
24
+ def render_menu(options = {})
25
+ options[:items] ||= menu_items
26
+ options[:partial] ||= "cms/menus/menu"
27
+ options[:id] ||= "menu"
28
+ options[:class] ||= "menu"
29
+ render :partial => options[:partial], :locals => { :items => options[:items], :css_id => options[:id], :css_class => options[:class] }
30
+ end
31
+
32
+ # This will render generate an array-of-hashes tree structure based on the page, which can be
33
+ # passed to render_menu in order to generate a menu.
34
+ #
35
+ # With no options passed, it will generate a structure that includes all the child sections of
36
+ # the root and then it will include the path of decendent sections all the way to the current
37
+ # page.
38
+ #
39
+ # Hidden pages will not be included, but if the first page in a Section is hidden, it will be
40
+ # used as the URL for that Section. This is commonly done to have a page for a Section and avoid
41
+ # having duplicates in the navigation.
42
+ #
12
43
  # You can change the behavior with the following options, all of these are optional:
13
44
  #
14
45
  # ==== Options
15
- # * <tt>:page</tt> - What page should be used as the current page. If this value is omitted, the value in @page will be used.
16
- # * <tt>:path</tt> - This will be used to look up a section and that section will used to generate the menu. The current page will
17
- # still be the value of the page option or @page. Note that this is the path to a section, not a path to a page.
46
+ # * <tt>:page</tt> - What page should be used as the current page. If this value is omitted,
47
+ # the value in @page will be used.
48
+ # * <tt>:path</tt> - This will be used to look up a section and that section will used to
49
+ # generate the menu structure. The current page will still be the value of the page option or
50
+ # @page. Note that this is the path to a section, not a path to a page.
18
51
  # * <tt>:from_top</tt> - How many below levels from the root the tree should start at.
19
52
  # All sections at this level will be shown. The default is 0, which means show all
20
- # section that are direct children of the root
53
+ # nodes that are direct children of the root
21
54
  # * <tt>:depth</tt> - How many levels deep should the tree go, relative to from_top.
22
55
  # If no value is supplied, the tree will go all the way down to the current page.
23
56
  # If a value is supplied, the tree will be that many levels underneath from_top deep.
24
57
  # * <tt>:limit</tt> - Limits the number of top-level elements that will be included in the list
25
- # * <tt>:class</tt> - The CSS Class that will be applied to the div. The default value is "menu".
26
- # * <tt>:show_all_siblings</tt> - Passing true for this option will make all sibilings appear in the tree.
27
- # the default is false, in which case only the siblings of nodes within the open path will appear.
58
+ # * <tt>:show_all_siblings</tt> - Passing true for this option will make all sibilings appear in
59
+ # the tree. The default is false, in which case only the siblings of nodes within the open
60
+ # path will appear.
28
61
  #
29
62
  # ==== Examples
30
63
  #
@@ -32,89 +65,41 @@ module Cms
32
65
  # with teams being a Page, everything else a Section. Also, assume we are on the
33
66
  # Baltimore Ravens page. If you're not a footbal fan, see http://sports.yahoo.com/nfl/teams
34
67
  #
35
- # render_menu
36
- # # => <div class="menu">
37
- # <ul>
38
- # <li id="section_2" class="first open">
39
- # <a href="/buf">AFC</a>
40
- # <ul>
41
- # <li id="section_3" class="first">
42
- # <a href="/buf">East</a>
43
- # </li>
44
- # <li id="section_4" class="open">
45
- # <a href="/bal">North</a>
46
- # <ul>
47
- # <li id="page_5" class="first on">
48
- # <a href="/bal">Baltimore Ravens</a>
49
- # </li>
50
- # <li id="page_6">
51
- # <a href="/cin">Cincinnati Bengals</a>
52
- # </li>
53
- # <li id="page_7">
54
- # <a href="/cle">Cleveland Browns</a>
55
- # </li>
56
- # <li id="page_8" class="last">
57
- # <a href="/pit">Pittsburgh Steelers</a>
58
- # </li>
59
- # </ul>
60
- # </li>
61
- # <li id="section_5">
62
- # <a href="/hou">South</a>
63
- # </li>
64
- # <li id="section_6" class="last">
65
- # <a href="/den">West</a>
66
- # </li>
67
- # </ul>
68
- # </li>
69
- # <li id="section_7" class="last">
70
- # <a href="/dal">NFC</a>
71
- # </li>
72
- # </ul>
73
- # </div>
68
+ # menu_items
69
+ # # => [
70
+ # { :id => "section_2", :url => "/buf", :name => "AFC", :children => [
71
+ # { :id => "section_3", :url => "/buf", :name => "East" },
72
+ # { :id => "section_4", :url => "/bal", :name => "North", :children => [
73
+ # { :id => "page_5", :selected => true, :url => "/bal", :name => "Baltimore Ravens" },
74
+ # { :id => "page_6", :url => "/cin", :name => "Cincinnati Bengals" },
75
+ # { :id => "page_7", :url => "/cle", :name => "Cleveland Browns" },
76
+ # { :id => "page_8", :url => "/pit", :name => "Pittsburgh Steelers" }
77
+ # ] },
78
+ # { :id => "section_9", :url => "/hou", :name => "South" },
79
+ # { :id => "section_10}", :url => "/den", :name => "West" }
80
+ # ] },
81
+ # { :id => "section_11", :url => "/dal", :name => "NFC" }
82
+ # ]
74
83
  #
75
- # render_menu(:depth => 2, :show_all_siblings => true)
76
- # # => <div class="menu">
77
- # <ul>
78
- # <li id="section_2" class="first open">
79
- # <a href="/buf">AFC</a>
80
- # <ul>
81
- # <li id="section_3" class="first">
82
- # <a href="/buf">East</a>
83
- # </li>
84
- # <li id="section_4" class="open">
85
- # <a href="/bal">North</a>
86
- # </li>
87
- # <li id="section_5">
88
- # <a href="/hou">South</a>
89
- # </li>
90
- # <li id="section_6" class="last">
91
- # <a href="/den">West</a>
92
- # </li>
93
- # </ul>
94
- # </li>
95
- # <li id="section_7" class="last">
96
- # <a href="/dal">NFC</a>
97
- # <ul>
98
- # <li id="section_8" class="first">
99
- # <a href="/dal">East</a>
100
- # </li>
101
- # <li id="section_9">
102
- # <a href="/chi">North</a>
103
- # </li>
104
- # <li id="section_10">
105
- # <a href="/atl">South</a>
106
- # </li>
107
- # <li id="section_11" class="last">
108
- # <a href="/ari">West</a>
109
- # </li>
110
- # </ul>
111
- # </li>
112
- # </ul>
113
- # </div>
114
- def render_menu(options={})
115
- #Intialize parameters
116
- page = options[:page] || @page
117
- return nil unless page
84
+ # menu_items(:depth => 2, :show_all_siblings => true)
85
+ # # => [
86
+ # { :id => "section_2", :url => "/buf", :name => "AFC", :children => [
87
+ # { :id => "section_3", :url => "/buf", :name => "East" },
88
+ # { :id => "section_4", :url => "/bal", :name => "North" },
89
+ # { :id => "section_5", :url => "/hou", :name => "South" },
90
+ # { :id => "section_6", :url => "/den", :name => "West" }
91
+ # ] },
92
+ # { :id => "section_7", :url => "/dal", :name => "NFC", :children => [
93
+ # { :id => "section_8", :url => "/dal", :name => "East" },
94
+ # { :id => "section_9", :url => "/chi", :name => "North" },
95
+ # { :id => "section_10", :url => "/atl", :name => "South" },
96
+ # { :id => "section_11", :url => "/ari", :name => "West" }
97
+ # ] }
98
+ # ]
99
+ def menu_items(options = {})
100
+ # Intialize parameters
101
+ selected_page = options[:page] || @page
102
+ return nil unless selected_page
118
103
 
119
104
  # Path to the section
120
105
  if options.has_key?(:path)
@@ -122,65 +107,52 @@ module Cms
122
107
  raise "Could not find section for path '#{options[:path]}'" unless section_for_path
123
108
  ancestors = section_for_path.ancestors(:include_self => true)
124
109
  else
125
- ancestors = page.ancestors
110
+ ancestors = selected_page.ancestors
126
111
  end
127
112
 
128
- from_top = options.has_key?(:from_top) ? options[:from_top].to_i : 0
129
- depth = options.has_key?(:depth) ? options[:depth].to_i : 1.0/0
130
- id = options[:id] || "menu"
131
- css_class = options[:class] || "menu"
132
- show_all_siblings = !!(options[:show_all_siblings])
133
- limit = options[:limit]
134
-
135
- html = "<div id=\"#{id}\" class=\"#{css_class}\">\n"
136
-
137
- if from_top > ancestors.size
138
- return html << "</div>\n"
139
- else
140
- ancestors = ancestors[from_top..-1]
113
+ if options.has_key?(:from_top)
114
+ ancestors = ancestors[options[:from_top].to_i..-1] || []
141
115
  end
142
116
 
143
- #We are defining a recursive lambda that takes the top-level sections
144
- #d is the current depth
145
- fn = lambda do |nodes, d|
146
- indent = (d-1)*4
147
- html << "<ul>\n".indent(indent+2)
148
- nodes.each_with_index do |sn, i|
149
-
150
- #Construct the CSS classes that the LI should have
151
- classes = ["depth-#{d}"]
152
- if i == 0
153
- classes << "first"
154
- elsif i == nodes.size-1
155
- classes << "last"
156
- end
157
- classes << "open" if ancestors.include?(sn.node)
158
- classes << "on" if page == sn.node
159
- cls = classes.empty? ? nil : classes.join(" ")
117
+ depth = options.has_key?(:depth) ? options[:depth].to_i : 1.0/0
118
+ show_all_siblings = options[:show_all_siblings] || false
119
+
120
+ # We are defining a recursive lambda that takes the top-level sections
121
+ fn = lambda do |section_nodes, current_depth|
122
+ section_nodes.map do |section_node|
123
+ node = section_node.node
160
124
 
161
- html << %Q{<li id="#{sn.node_type.underscore}_#{sn.node.id}"#{cls ? " class=\"#{cls}\"" : ''}>\n}.indent(indent+4)
125
+ item = {}
126
+ item[:selected] = true if selected_page == node
127
+ item[:id] = "#{section_node.node_type.underscore}_#{section_node.node_id}"
162
128
 
163
- #Figure out what this link for this node should be
164
- #If it is a page, then the page will simply be used
165
- #But if is a page, we call the first_page_or_link method
166
- p = sn.node_type == "Section" ? sn.node.first_page_or_link : sn.node
167
- html << %Q{<a href="#{p ? p.path : '#'}"#{(p.respond_to?(:new_window) && p.new_window?) ? ' target="_blank"' : ''}>#{sn.node.name}</a>\n}.indent(indent+6)
129
+ # If we are showing a section item, we want to use the path for the first page
130
+ page = section_node.section? ? node.first_page_or_link : node
168
131
 
169
- #Now if this is a section, we do the child nodes,
170
- #but only if the show_all_siblings parameter is true,
171
- #or if this section is one of the current page's ancestors
172
- #and also if the current depth is less than the target depth
173
- if sn.node_type == "Section" && (show_all_siblings || ancestors.include?(sn.node)) && d < depth
174
- fn.call(sn.node.visible_child_nodes, d+1)
175
- end
132
+ item[:url] = page && page.path || '#'
133
+ item[:name] = node.name
134
+ item[:target] = "_blank" if page.respond_to?(:new_window?) && page.new_window?
176
135
 
177
- html << %Q{</li>\n}.indent(indent+4)
136
+ # Now if this is a section, we do the child nodes,
137
+ # but only if the show_all_siblings parameter is true,
138
+ # or if this section is one of the current page's ancestors
139
+ # and also if the current depth is less than the target depth
140
+ if section_node.section? &&
141
+ current_depth < depth &&
142
+ (show_all_siblings || ancestors.include?(node)) &&
143
+ !node.visible_child_nodes.empty?
144
+ item[:children] = fn.call(node.visible_child_nodes, current_depth + 1)
145
+ end
178
146
 
147
+ item
179
148
  end
180
- html << "</ul>\n".indent(indent+2)
181
149
  end
182
- fn.call(ancestors.first.visible_child_nodes(:limit => limit), 1) unless ancestors.first.blank?
183
- html << "</div>\n"
150
+
151
+ if ancestors.empty?
152
+ []
153
+ else
154
+ fn.call(ancestors.first.visible_child_nodes(:limit => options[:limit]), 1)
155
+ end
184
156
  end
185
157
  end
186
- end
158
+ end
@@ -14,7 +14,7 @@ module Cms
14
14
 
15
15
  def container(name)
16
16
  content = instance_variable_get("@content_for_#{name}")
17
- if logged_in? && @page && @mode == "edit"
17
+ if logged_in? && @page && @mode == "edit" && current_user.able_to_edit?(@page)
18
18
  render :partial => 'cms/pages/edit_container', :locals => {:name => name, :content => content}
19
19
  else
20
20
  content
@@ -65,4 +65,4 @@ module Cms
65
65
  end
66
66
 
67
67
  end
68
- end
68
+ end
@@ -1,5 +1,5 @@
1
1
  require 'digest/sha1'
2
- require 'ftools'
2
+ require 'fileutils'
3
3
 
4
4
  class Attachment < ActiveRecord::Base
5
5
 
@@ -116,7 +116,7 @@ class Attachment < ActiveRecord::Base
116
116
  unless temp_file.blank?
117
117
  FileUtils.mkdir_p File.dirname(full_file_location)
118
118
  if temp_file.local_path
119
- File.copy temp_file.local_path, full_file_location
119
+ FileUtils.copy temp_file.local_path, full_file_location
120
120
  else
121
121
  open(full_file_location, 'w') {|f| f << temp_file.read }
122
122
  end
data/app/models/group.rb CHANGED
@@ -1,5 +1,11 @@
1
+ #
2
+ # A group represents a collection of permissions. Each User can be assigned to one or more groups, and the sum of
3
+ # their permissions from all groups combined represents what they can do.
4
+ #
1
5
  class Group < ActiveRecord::Base
2
-
6
+
7
+ GUEST_CODE = "guest"
8
+
3
9
  has_many :user_group_memberships
4
10
  has_many :users, :through => :user_group_memberships
5
11
 
@@ -26,5 +32,10 @@ class Group < ActiveRecord::Base
26
32
  def cms_access?
27
33
  group_type && group_type.cms_access?
28
34
  end
29
-
35
+
36
+ # Finds the guest group, which is a special group that represents public non-logged in users.
37
+ def self.guest
38
+ with_code(GUEST_CODE).first
39
+ end
40
+
30
41
  end
@@ -1,7 +1,13 @@
1
+ #
2
+ # Guests are a special user that represents a non-logged in user. The main reason to create an explicit
3
+ # instance of this type of user is so that the permissions a Guest user can have can be set via the Admin interface.
4
+ #
5
+ # Every request that a non-logged in user makes will use this User's permissions to determine what they can/can't do.
6
+ #
1
7
  class GuestUser < User
2
-
8
+
3
9
  def initialize(attributes={})
4
- super({:login => "guest", :first_name => "Anonymous", :last_name => "User"}.merge(attributes))
10
+ super({:login => Group::GUEST_CODE, :first_name => "Anonymous", :last_name => "User"}.merge(attributes))
5
11
  @guest = true
6
12
  end
7
13
 
@@ -18,7 +24,7 @@ class GuestUser < User
18
24
  end
19
25
 
20
26
  def group
21
- @group ||= Group.find_by_code("guest")
27
+ @group ||= Group.guest
22
28
  end
23
29
 
24
30
  def groups
data/app/models/link.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  class Link < ActiveRecord::Base
2
- acts_as_content_block
2
+ acts_as_content_block :connectable => false
3
3
 
4
4
  named_scope :named, lambda{|name| {:conditions => ['links.name = ?', name]}}
5
5
 
@@ -32,4 +32,4 @@ class Link < ActiveRecord::Base
32
32
  url
33
33
  end
34
34
 
35
- end
35
+ end
data/app/models/page.rb CHANGED
@@ -150,7 +150,7 @@ class Page < ActiveRecord::Base
150
150
 
151
151
  def delete_connectors
152
152
  connectors.for_page_version(version).all.each{|c| c.destroy }
153
- end
153
+ end
154
154
 
155
155
  #This is done to let copy_connectors know which version to pull from
156
156
  #copy_connectors will get called later as an after_update callback
@@ -75,7 +75,12 @@ class Section < ActiveRecord::Base
75
75
  ancs = node ? node.ancestors : []
76
76
  options[:include_self] ? ancs + [self] : ancs
77
77
  end
78
-
78
+
79
+ def with_ancestors(options = {})
80
+ options.merge! :include_self => true
81
+ self.ancestors(options)
82
+ end
83
+
79
84
  def move_to(section)
80
85
  if root?
81
86
  false
@@ -139,4 +144,4 @@ class Section < ActiveRecord::Base
139
144
  end
140
145
  end
141
146
 
142
- end
147
+ end
data/app/models/user.rb CHANGED
@@ -11,8 +11,7 @@ class User < ActiveRecord::Base
11
11
  validates_presence_of :email
12
12
  #validates_length_of :email, :within => 6..100 #r@a.wk
13
13
  #validates_uniqueness_of :email, :case_sensitive => false
14
- validates_format_of :email, :with => /[^@]{2,}@[^.]{2,}\..{2,}/, :message => "should be an email address, ex. xx@xx.com"
15
-
14
+ validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => "should be an email address, ex. xx@xx.com"
16
15
  attr_accessible :login, :email, :name, :first_name, :last_name, :password, :password_confirmation, :expires_at
17
16
 
18
17
  has_many :user_group_memberships
@@ -89,12 +88,12 @@ class User < ActiveRecord::Base
89
88
  @viewable_sections ||= Section.find(:all, :include => {:groups => :users}, :conditions => ["users.id = ?", id])
90
89
  end
91
90
 
92
- def editable_sections
93
- @editable_sections ||= Section.find(:all, :include => {:groups => [:group_type, :users]}, :conditions => ["users.id = ? and group_types.cms_access = ?", id, true])
91
+ def modifiable_sections
92
+ @modifiable_sections ||= Section.find(:all, :include => {:groups => [:group_type, :users]}, :conditions => ["users.id = ? and group_types.cms_access = ?", id, true])
94
93
  end
95
94
 
96
- #Expects a list of names of Permissions
97
- #true if the user has any of the permissions
95
+ # Expects a list of names of Permissions
96
+ # true if the user has any of the permissions
98
97
  def able_to?(*required_permissions)
99
98
  perms = required_permissions.map(&:to_sym)
100
99
  permissions.any? do |p|
@@ -102,24 +101,43 @@ class User < ActiveRecord::Base
102
101
  end
103
102
  end
104
103
 
105
- #Expects object to be an object or a section
106
- #If it's a section, that will be used
107
- #If it's not a section, it will call section on the object
108
- #returns true if any of the sections of the groups the user is in matches the page's section.
104
+ # Expects object to be an object or a section
105
+ # If it's a section, that will be used
106
+ # If it's not a section, it will call section on the object
107
+ # returns true if any of the sections of the groups the user is in matches the page's section.
109
108
  def able_to_view?(object)
110
109
  section = object.is_a?(Section) ? object : object.section
111
- !!(viewable_sections.include?(section) || groups.cms_access.count > 0)
110
+ viewable_sections.include?(section) || groups.cms_access.count > 0
111
+ end
112
+
113
+ def able_to_modify?(object)
114
+ case object
115
+ when Section
116
+ modifiable_sections.include?(object)
117
+ when Page, Link
118
+ modifiable_sections.include?(object.section)
119
+ else
120
+ if object.class.respond_to?(:connectable?) && object.class.connectable?
121
+ object.connected_pages.all? { |page| able_to_modify?(page) }
122
+ else
123
+ true
124
+ end
125
+ end
126
+ end
127
+
128
+ # Expects node to be a Section, Page or Link
129
+ # Returns true if the specified node, or any of its ancestor sections, is editable by any of
130
+ # the user's 'CMS User' groups.
131
+ def able_to_edit?(object)
132
+ able_to?(:edit_content) && able_to_modify?(object)
112
133
  end
113
134
 
114
- #Expects section to be a Section
115
- #Returns true if any of the sections of the groups that have group_type = 'CMS User'
116
- #that the user is in match the section.
117
- def able_to_edit?(section)
118
- !!(editable_sections.include?(section) && able_to?(:edit_content))
135
+ def able_to_publish?(object)
136
+ able_to?(:publish_content) && able_to_modify?(object)
119
137
  end
120
138
 
121
139
  def able_to_edit_or_publish_content?
122
140
  able_to?(:edit_content, :publish_content)
123
141
  end
124
142
 
125
- end
143
+ end
@@ -1,7 +1,7 @@
1
1
  <% able_to? :publish_content do -%>
2
2
  <% if @block.respond_to?(:live?) && !@block.live? %>
3
3
  <%= link_to span_tag('Publish'), block_path(:publish),
4
- :class => "http_put button left",
4
+ :class => "http_put button left#{' disabled' unless current_user.able_to_publish?(@block)}",
5
5
  :id => "publish_button" %>
6
6
  <% else %>
7
7
  <%= link_to span_tag('Publish'), "#",
@@ -15,7 +15,7 @@
15
15
  :id => "view_button" %>
16
16
 
17
17
  <%= link_to span_tag('Edit Content'), block_path(:edit),
18
- :class => "button right#{ ' off' if action_name == 'edit'}",
18
+ :class => "button right#{ ' off' if action_name == 'edit'}#{' disabled' unless current_user.able_to_edit?(@block)}",
19
19
  :id => "edit_button" %>
20
20
 
21
21
  <%= link_to span_tag("Add New Content"), new_block_path,
@@ -33,6 +33,6 @@
33
33
  <% end %>
34
34
 
35
35
  <%= link_to span_tag("<span class=\"delete_img\">&nbsp;</span>Delete"), block_path,
36
- :class => "http_delete confirm_with_title button",
36
+ :class => "http_delete confirm_with_title button#{' disabled' unless current_user.able_to_publish?(@block)}",
37
37
  :title => "Are you sure you want to delete '#{@block.name}'?",
38
38
  :id => "delete_button" %>
@@ -1,4 +1,5 @@
1
1
  <% content_for(:html_head) do %>
2
+ <%= javascript_include_tag "cms/content_library" %>
2
3
  <% javascript_tag do %>
3
4
  jQuery(function($){
4
5
  var collectionName = '<%= content_type.model_class.name.underscore.pluralize %>'
@@ -11,12 +12,14 @@
11
12
  var match = this.id.match(/(.*)_(\d+)/)
12
13
  var type = match[1]
13
14
  var id = match[2]
15
+ var editable = !$(this).hasClass("non-editable")
16
+ var publishable = !$(this).hasClass("non-publishable")
14
17
  $('table.data tbody tr').removeClass('selected')
15
18
  $(this).addClass('selected')
16
19
  $('#functions .button').addClass('disabled').attr('href','#')
17
20
  $('#add_button').removeClass('disabled').attr('href', '/cms/'+collectionName+'/new')
18
21
  $('#view_button').removeClass('disabled').attr('href', '/cms/'+collectionName+'/'+id)
19
- $('#edit_button').removeClass('disabled').attr('href', '/cms/'+collectionName+'/'+id+'/edit')
22
+ if (editable) $('#edit_button').removeClass('disabled').attr('href', '/cms/'+collectionName+'/'+id+'/edit')
20
23
  <% if content_type.model_class.versioned? %>
21
24
  $('#revisions_button').removeClass('disabled').attr('href', '/cms/'+collectionName+'/'+id+'/versions')
22
25
  <% else %>
@@ -28,12 +31,14 @@
28
31
  $('#delete_button').addClass('disabled')
29
32
  .attr('title', $.trim(cannot_be_deleted_message.text()))
30
33
  } else {
31
- $('#delete_button').removeClass('disabled')
32
- .attr('href', '/cms/'+collectionName+'/'+id)
33
- .attr('title', 'Are You Sure You Want To Delete This Record?')
34
+ if (publishable) {
35
+ $('#delete_button').removeClass('disabled')
36
+ .attr('href', '/cms/'+collectionName+'/'+id)
37
+ .attr('title', 'Are You Sure You Want To Delete This Record?')
38
+ }
34
39
  }
35
40
  <% able_to? :publish_content do -%>
36
- if($(this).hasClass('draft')) {
41
+ if($(this).hasClass('draft') && publishable) {
37
42
  $('#publish_button').removeClass('disabled').attr('href', '/cms/'+collectionName+'/'+id+'/publish?_redirect_to='+location.href)
38
43
  }
39
44
  <% end %>
@@ -85,7 +90,7 @@
85
90
  col_ct += 1 if content_type.model_class.publishable? %>
86
91
  <% @blocks.each do |b| %>
87
92
  <% block = b.class.versioned? ? b.as_of_draft_version : b %>
88
- <tr id="<%= block.class.name.underscore %>_<%= block.id %>" class="<%= block.class.name.underscore %> <%= block.class.publishable? && !block.published? ? 'draft' : 'published' %>">
93
+ <tr id="<%= block.class.name.underscore %>_<%= block.id %>" class="<%= block.class.name.underscore %> <%= block.class.publishable? && !block.published? ? 'draft' : 'published' %> <%= 'non-editable' unless current_user.able_to_edit?(block) %> <%= 'non-publishable' unless current_user.able_to_publish?(block) %>">
89
94
  <td class="first"></td>
90
95
  <% content_type.columns_for_index.each_with_index do |column, i| %>
91
96
  <td class="<%= column[:label].gsub(' ', '').underscore %>">
@@ -12,9 +12,9 @@
12
12
  <iframe src="<%=h cms_toolbar_path(:page_id => @page.id, :page_version => @page.version, :mode => @mode, :page_toolbar => @show_page_toolbar ? 1 : 0) %>" width="100%" height="<%= @show_page_toolbar ? 159 : 100 %>px" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" name="cms_toolbar"></iframe>
13
13
  <% end %>
14
14
 
15
- <% @page.connectors.for_page_version(@page.version).each do |c| %>
16
- <% content_for(c.container.to_sym) do %>
17
- <%= render_connector c %>
15
+ <% @_connectors.each_with_index do |connector, i| %>
16
+ <% content_for(connector.container.to_sym) do %>
17
+ <%= render_connector_and_connectable(connector, @_connectables[i]) %>
18
18
  <% end %>
19
19
  <% end %>
20
20
 
@@ -0,0 +1,9 @@
1
+ <div id="<%= css_id %>" class="<%= css_class %>">
2
+ <% unless items.empty?
3
+ %> <ul>
4
+ <% items.each_with_index do |item, i|
5
+ %><%= render :partial => "/cms/menus/menu_item", :object => item, :locals => { :depth => 1, :position => i + 1, :item_count => items.length }
6
+ %><% end
7
+ %> </ul>
8
+ <% end
9
+ %></div>
@@ -0,0 +1,11 @@
1
+ <% indent = (depth - 1) * 4
2
+ %><%= " "*(indent + 4) %><li id="<%= menu_item[:id] %>" class="depth-<%= depth %><%= ' first' if position == 1 %><%= ' last' if position == item_count %><%= ' on' if menu_item[:selected] %><%= ' open' unless menu_item[:children].blank? %>">
3
+ <%= " "*(indent + 6) %><a href="<%= menu_item[:url] %>"<%= ' target=#{menu_item[:target]}' if menu_item[:target] %>><%= menu_item[:name] %></a>
4
+ <% unless menu_item[:children].blank?
5
+ %><%= " "*(indent + 6) %><ul>
6
+ <% menu_item[:children].each_with_index do |item, i|
7
+ %><%= render :partial => "/cms/menus/menu_item", :object => item, :locals => { :depth => depth + 1, :position => i + 1, :item_count => menu_item[:children].length }
8
+ %><% end
9
+ %><%= " "*(indent + 6) %></ul>
10
+ <% end
11
+ %><%= " "*(indent + 4) %></li>