browsercms 3.1.4 → 3.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. data/app/controllers/cms/content_block_controller.rb +2 -2
  2. data/app/controllers/cms/section_nodes_controller.rb +6 -1
  3. data/app/controllers/cms/sections_controller.rb +1 -1
  4. data/app/helpers/cms/application_helper.rb +1 -1
  5. data/app/helpers/cms/content_block_helper.rb +27 -0
  6. data/app/helpers/cms/section_nodes_helper.rb +43 -5
  7. data/app/models/abstract_file_block.rb +16 -1
  8. data/app/models/attachment.rb +17 -35
  9. data/app/models/file_block.rb +0 -12
  10. data/app/models/image_block.rb +0 -12
  11. data/app/models/link.rb +4 -21
  12. data/app/models/page.rb +31 -34
  13. data/app/models/section.rb +82 -44
  14. data/app/models/section_node.rb +39 -24
  15. data/app/models/user.rb +5 -0
  16. data/app/views/cms/blocks/index.html.erb +4 -4
  17. data/app/views/cms/file_blocks/_form.html.erb +1 -1
  18. data/app/views/cms/image_blocks/_form.html.erb +1 -1
  19. data/app/views/cms/section_nodes/_link.html.erb +6 -3
  20. data/app/views/cms/section_nodes/_node.html.erb +11 -1
  21. data/app/views/cms/section_nodes/_page.html.erb +13 -7
  22. data/app/views/cms/section_nodes/_section.html.erb +24 -8
  23. data/app/views/cms/section_nodes/index.html.erb +28 -16
  24. data/app/views/layouts/templates/default.html.erb +17 -0
  25. data/browsercms.gemspec +28 -1413
  26. data/db/migrate/20120117144039_browsercms315.rb +94 -0
  27. data/db/migrate/{20081114172307_load_seed_data.rb → 20121114172307_load_seeds.rb} +8 -1
  28. data/lib/acts_as_list.rb +1 -1
  29. data/lib/browsercms.rb +2 -0
  30. data/lib/cms/addressable.rb +83 -0
  31. data/lib/cms/behaviors/attaching.rb +44 -24
  32. data/lib/cms/behaviors/connecting.rb +2 -1
  33. data/lib/cms/behaviors/publishing.rb +12 -3
  34. data/lib/cms/behaviors/versioning.rb +83 -53
  35. data/lib/cms/content_rendering_support.rb +3 -3
  36. data/lib/cms/error_pages.rb +8 -0
  37. data/lib/cms/init.rb +5 -3
  38. data/lib/cms/version.rb +1 -1
  39. data/templates/blank.rb +2 -0
  40. data/templates/demo.rb +2 -0
  41. data/templates/module.rb +2 -0
  42. data/test/custom_assertions.rb +7 -1
  43. data/test/factories.rb +3 -1
  44. data/test/factories/sitemap_factories.rb +28 -0
  45. data/test/fixtures/connectors.yml +97 -0
  46. data/test/fixtures/content_type_groups.yml +13 -0
  47. data/test/fixtures/content_types.yml +50 -0
  48. data/test/fixtures/dynamic_view_versions.yml +26 -0
  49. data/test/fixtures/dynamic_views.yml +26 -0
  50. data/test/fixtures/group_permissions.yml +16 -0
  51. data/test/fixtures/group_sections.yml +31 -0
  52. data/test/fixtures/group_type_permissions.yml +11 -0
  53. data/test/fixtures/group_types.yml +25 -0
  54. data/test/fixtures/groups.yml +25 -0
  55. data/test/fixtures/html_block_versions.yml +67 -0
  56. data/test/fixtures/html_blocks.yml +63 -0
  57. data/test/fixtures/page_versions.yml +265 -0
  58. data/test/fixtures/pages.yml +85 -0
  59. data/test/fixtures/permissions.yml +28 -0
  60. data/test/fixtures/section_nodes.yml +46 -0
  61. data/test/fixtures/sections.yml +19 -0
  62. data/test/fixtures/sites.yml +9 -0
  63. data/test/fixtures/user_group_memberships.yml +11 -0
  64. data/test/fixtures/users.yml +15 -0
  65. data/test/functional/cms/content_controller_test.rb +6 -1
  66. data/test/functional/cms/file_blocks_controller_test.rb +1 -0
  67. data/test/functional/cms/html_blocks_controller_test.rb +1 -0
  68. data/test/functional/cms/image_blocks_controller_test.rb +39 -32
  69. data/test/functional/cms/section_nodes_controller_test.rb +48 -20
  70. data/test/functional/cms/sections_controller_test.rb +3 -1
  71. data/test/functional/tests/pretend_controller_test.rb +6 -3
  72. data/test/integration/cms/ckeditor_test.rb +5 -2
  73. data/test/integration/sitemap_performance_test.rb +26 -0
  74. data/test/selenium-core/Blank.html +7 -0
  75. data/test/selenium-core/InjectedRemoteRunner.html +8 -0
  76. data/test/selenium-core/RemoteRunner.html +110 -0
  77. data/test/selenium-core/SeleniumLog.html +109 -0
  78. data/test/selenium-core/TestPrompt.html +145 -0
  79. data/test/selenium-core/TestRunner-splash.html +55 -0
  80. data/test/selenium-core/TestRunner.hta +176 -0
  81. data/test/selenium-core/TestRunner.html +176 -0
  82. data/test/selenium-core/domviewer/butmin.gif +0 -0
  83. data/test/selenium-core/domviewer/butplus.gif +0 -0
  84. data/test/selenium-core/domviewer/domviewer.css +298 -0
  85. data/test/selenium-core/domviewer/domviewer.html +16 -0
  86. data/test/selenium-core/domviewer/selenium-domviewer.js +205 -0
  87. data/test/selenium-core/icons/all.png +0 -0
  88. data/test/selenium-core/icons/continue.png +0 -0
  89. data/test/selenium-core/icons/continue_disabled.png +0 -0
  90. data/test/selenium-core/icons/pause.png +0 -0
  91. data/test/selenium-core/icons/pause_disabled.png +0 -0
  92. data/test/selenium-core/icons/selected.png +0 -0
  93. data/test/selenium-core/icons/step.png +0 -0
  94. data/test/selenium-core/icons/step_disabled.png +0 -0
  95. data/test/selenium-core/iedoc-core.xml +1515 -0
  96. data/test/selenium-core/iedoc.xml +1469 -0
  97. data/test/selenium-core/lib/cssQuery/cssQuery-p.js +6 -0
  98. data/test/selenium-core/lib/cssQuery/src/cssQuery-level2.js +142 -0
  99. data/test/selenium-core/lib/cssQuery/src/cssQuery-level3.js +150 -0
  100. data/test/selenium-core/lib/cssQuery/src/cssQuery-standard.js +53 -0
  101. data/test/selenium-core/lib/cssQuery/src/cssQuery.js +356 -0
  102. data/test/selenium-core/lib/prototype.js +2006 -0
  103. data/test/selenium-core/lib/scriptaculous/builder.js +101 -0
  104. data/test/selenium-core/lib/scriptaculous/controls.js +815 -0
  105. data/test/selenium-core/lib/scriptaculous/dragdrop.js +915 -0
  106. data/test/selenium-core/lib/scriptaculous/effects.js +958 -0
  107. data/test/selenium-core/lib/scriptaculous/scriptaculous.js +47 -0
  108. data/test/selenium-core/lib/scriptaculous/slider.js +283 -0
  109. data/test/selenium-core/lib/scriptaculous/unittest.js +383 -0
  110. data/test/selenium-core/scripts/find_matching_child.js +69 -0
  111. data/test/selenium-core/scripts/htmlutils.js +894 -0
  112. data/test/selenium-core/scripts/injection.html +72 -0
  113. data/test/selenium-core/scripts/js2html.js +70 -0
  114. data/test/selenium-core/scripts/narcissus-defs.js +175 -0
  115. data/test/selenium-core/scripts/narcissus-exec.js +1054 -0
  116. data/test/selenium-core/scripts/narcissus-parse.js +1003 -0
  117. data/test/selenium-core/scripts/se2html.js +63 -0
  118. data/test/selenium-core/scripts/selenium-api.js +2409 -0
  119. data/test/selenium-core/scripts/selenium-browserbot.js +2203 -0
  120. data/test/selenium-core/scripts/selenium-browserdetect.js +150 -0
  121. data/test/selenium-core/scripts/selenium-commandhandlers.js +377 -0
  122. data/test/selenium-core/scripts/selenium-executionloop.js +175 -0
  123. data/test/selenium-core/scripts/selenium-logging.js +147 -0
  124. data/test/selenium-core/scripts/selenium-remoterunner.js +571 -0
  125. data/test/selenium-core/scripts/selenium-testrunner.js +1333 -0
  126. data/test/selenium-core/scripts/selenium-version.js +5 -0
  127. data/test/selenium-core/scripts/user-extensions.js +3 -0
  128. data/test/selenium-core/scripts/user-extensions.js.sample +75 -0
  129. data/test/selenium-core/scripts/xmlextras.js +153 -0
  130. data/test/selenium-core/selenium-logo.png +0 -0
  131. data/test/selenium-core/selenium-test.css +43 -0
  132. data/test/selenium-core/selenium.css +299 -0
  133. data/test/selenium-core/xpath/dom.js +428 -0
  134. data/test/selenium-core/xpath/misc.js +252 -0
  135. data/test/selenium-core/xpath/xpath.js +2223 -0
  136. data/test/selenium/_login_as_cmsadmin.rsel +4 -0
  137. data/test/selenium/dashboard.rsel +5 -0
  138. data/test/selenium/html_blocks.rsel +4 -0
  139. data/test/selenium/login/failed_login.rsel +8 -0
  140. data/test/selenium/login/successful_login.rsel +9 -0
  141. data/test/selenium/page_templates.rsel +12 -0
  142. data/test/selenium/pages/edit_properties.rsel +5 -0
  143. data/test/selenium/site/view_home_page.rsel +4 -0
  144. data/test/selenium/sitemap/move_page.rsel +9 -0
  145. data/test/selenium/sitemap/open_section.rsel +6 -0
  146. data/test/selenium/sitemap/select_page.rsel +12 -0
  147. data/test/selenium/sitemap/select_section.rsel +17 -0
  148. data/test/test_helper.rb +30 -12
  149. data/test/unit/behaviors/attaching_test.rb +4 -6
  150. data/test/unit/behaviors/connectable_test.rb +29 -0
  151. data/test/unit/behaviors/publishable_test.rb +40 -9
  152. data/test/unit/behaviors/versioning_test.rb +36 -0
  153. data/test/unit/helpers/menu_helper_test.rb +5 -2
  154. data/test/unit/helpers/page_helper_test.rb +2 -0
  155. data/test/unit/lib/cms/sitemap_test.rb +206 -0
  156. data/test/unit/models/attachment_test.rb +51 -31
  157. data/test/unit/models/file_block_test.rb +74 -55
  158. data/test/unit/models/link_test.rb +44 -0
  159. data/test/unit/models/page_test.rb +290 -224
  160. data/test/unit/models/sections_test.rb +144 -44
  161. data/test/unit/models/user_test.rb +28 -18
  162. metadata +581 -350
  163. data/app/views/cms/section_nodes/_section_node.html.erb +0 -10
  164. data/test/unit/models/section_node_test.rb +0 -92
@@ -121,8 +121,8 @@ class Cms::ContentBlockController < Cms::BaseController
121
121
  def load_blocks
122
122
  options = {}
123
123
  if params[:section_id] && params[:section_id] != 'all'
124
- options[:include] = { :attachment => { :section_node => :section }}
125
- options[:conditions] = ["sections.id = ?", params[:section_id]]
124
+ options[:include] = { :attachment => :section_node }
125
+ options[:conditions] = ["section_nodes.ancestry = ?", Section.find(params[:section_id]).ancestry_path]
126
126
  end
127
127
  options[:page] = params[:page]
128
128
  options[:order] = model_class.default_order if model_class.respond_to?(:default_order)
@@ -3,7 +3,12 @@ class Cms::SectionNodesController < Cms::BaseController
3
3
 
4
4
  def index
5
5
  @toolbar_tab = :sitemap
6
- @section = Section.root.first
6
+ @modifiable_sections = current_user.modifiable_sections
7
+ @public_sections = Group.guest.sections.all # Load once here so that every section doesn't need to.
8
+
9
+ @sitemap = Section.sitemap
10
+ @root_section_node = @sitemap.keys.first
11
+ @section = @root_section_node.node
7
12
  end
8
13
  def move_before
9
14
  move(:before)
@@ -16,7 +16,7 @@ class Cms::SectionsController < Cms::BaseController
16
16
  end
17
17
 
18
18
  def new
19
- @section = @parent.sections.build
19
+ @section = @parent.build_section
20
20
  @section.groups = @parent.groups
21
21
  end
22
22
 
@@ -23,7 +23,7 @@ module Cms
23
23
  def searchable_sections(selected = nil)
24
24
  root = Section.root.first
25
25
  options = [['All sections', 'all'], [root.name, root.id]]
26
- root.all_children_with_name.each { |s| options << [s.full_path, s.id] }
26
+ root.master_section_list.each { |s| options << [s.full_path, s.id] }
27
27
  options_for_select(options, selected.to_i)
28
28
  end
29
29
 
@@ -0,0 +1,27 @@
1
+ module Cms
2
+ module ContentBlockHelper
3
+
4
+
5
+ # Prints the <tr> for each block. Adds classes based on:
6
+ # * Name/id of the block
7
+ # * If a block is published/draft
8
+ # * If the user can edit/publish it
9
+ def block_row_tag(block)
10
+ cname = class_name_for(block)
11
+ can_modify = current_user.able_to_modify?(block)
12
+
13
+ options = {
14
+ :id => "#{cname}_#{block.id}",
15
+ :class => cname
16
+ }
17
+ options[:class] += block.class.publishable? && !block.published? ? ' draft' : ' published'
18
+ options[:class] += ' non-editable' unless can_modify && current_user.able_to?(:edit_content)
19
+ options[:class] += ' non-publishable' unless can_modify && current_user.able_to?(:publish_content)
20
+ tag "tr", options, true
21
+ end
22
+
23
+ def class_name_for(block)
24
+ block.class.name.underscore
25
+ end
26
+ end
27
+ end
@@ -1,13 +1,51 @@
1
1
  module Cms
2
2
  module SectionNodesHelper
3
- def section_icons(node)
4
-
5
- if (node.root? || node.parent.root? || node.parent.parent.root?)
6
- node.child_nodes.empty? ? image_tag("cms/sitemap/no_contents.png", :class => "no_folder_toggle large") : image_tag("cms/sitemap/gray_expand.png", :class => "folder_toggle large")
3
+
4
+ def access_status(section_node, public_sections)
5
+ access_icon = :unlocked
6
+ unless public_sections.include?(section_node)
7
+ access_icon = :locked
8
+ end
9
+ access_icon
10
+ end
11
+
12
+ def section_icons(section_node, children=[])
13
+ folder_style = ""
14
+ expander_image = "expand.png"
15
+ if top_level_section?(section_node)
16
+ folder_style = " large"
17
+ expander_image = "gray_expand.png"
18
+ end
19
+ if children.empty?
20
+ image_tag("cms/sitemap/no_contents.png", :class => "no_folder_toggle#{folder_style}")
7
21
  else
8
- node.child_nodes.empty? ? image_tag("cms/sitemap/no_contents.png", :class => "no_folder_toggle") : image_tag("cms/sitemap/expand.png", :class => "folder_toggle")
22
+ image_tag("cms/sitemap/#{expander_image}", :class => "folder_toggle#{folder_style}")
9
23
  end
10
24
  end
11
25
 
26
+ # Renders the ul for a given node (Page/Section/Link/etc)
27
+ # Default look:
28
+ # - First level pages/sections use 'big' icons
29
+ # - All non-first level items should be hidden.
30
+ def sitemap_ul_tag(node)
31
+ opts = {
32
+ :id => "section_node_#{node.section_node.id}",
33
+ :class => "section_node"
34
+ }
35
+ opts[:class] += " rootlet" if in_first_level?(node)
36
+ opts[:style] = "display: none" unless in_first_level?(node)
37
+ tag("ul", opts, true)
38
+ end
39
+
40
+ def in_first_level?(node)
41
+ node.section_node.depth == 1
42
+ end
43
+
44
+ private
45
+
46
+ def top_level_section?(node)
47
+ node.depth <= 2
48
+ end
49
+
12
50
  end
13
51
  end
@@ -4,7 +4,10 @@ class AbstractFileBlock < ActiveRecord::Base
4
4
 
5
5
  validates_presence_of :name
6
6
 
7
- named_scope :by_section, lambda { |section| { :include => {:attachment => :section_node }, :conditions => ["section_nodes.section_id = ?", section.id] } }
7
+ named_scope :by_section, lambda { |section| {
8
+ :include => {:attachment => :section_node },
9
+ :conditions => ["section_nodes.ancestry = ?", section.node.ancestry_path] }
10
+ }
8
11
 
9
12
  def path
10
13
  attachment_file_path
@@ -14,4 +17,16 @@ class AbstractFileBlock < ActiveRecord::Base
14
17
  true
15
18
  end
16
19
 
20
+ def set_attachment_path
21
+ if @attachment_file_path && @attachment_file_path != attachment.file_path
22
+ attachment.file_path = @attachment_file_path
23
+ end
24
+ end
25
+
26
+ def set_attachment_section
27
+ if @attachment_section_id && @attachment_section_id != attachment.section
28
+ attachment.section_id = @attachment_section_id
29
+ end
30
+ end
31
+
17
32
  end
@@ -20,48 +20,27 @@ class Attachment < ActiveRecord::Base
20
20
  before_validation :extract_file_type_from_temp_file
21
21
  before_validation :extract_file_size_from_temp_file
22
22
  before_validation :set_file_location
23
- before_save :process_section
24
23
 
25
24
  after_save :write_temp_file_to_storage_location
26
25
  after_save :clear_ivars
27
26
 
28
27
  #----- Associations ----------------------------------------------------------
29
28
 
29
+ include Addressable
30
+ include Addressable::DeprecatedPageAccessors
30
31
  has_one :section_node, :as => :node
32
+ alias :node :section_node
31
33
 
32
34
  #----- Validations -----------------------------------------------------------
33
35
 
34
- validates_presence_of :temp_file,
35
- :message => "You must upload a file", :on => :create
36
+ validates_presence_of :temp_file, :message => "You must upload a file", :on => :create
36
37
  validates_presence_of :file_path
37
38
  validates_uniqueness_of :file_path
38
- validates_presence_of :section_id
39
-
40
- #----- Virtual Attributes ----------------------------------------------------
41
-
42
- def section_id
43
- @section_id ||= section_node ? section_node.section_id : nil
44
- end
45
-
46
- def section_id=(section_id)
47
- if @section_id != section_id
48
- dirty!
49
- @section_id = section_id
50
- end
51
- end
52
-
53
- def section
54
- @section ||= section_node ? section_node.section : nil
55
- end
56
39
 
57
40
  def section=(section)
58
- if @section != section
59
- dirty!
60
- @section_id = section ? section.id : nil
61
- @section = section
62
- end
41
+ dirty! if self.section != section
42
+ super(section)
63
43
  end
64
-
65
44
  #----- Callbacks Methods -----------------------------------------------------
66
45
 
67
46
  def make_dirty_if_temp_file
@@ -103,14 +82,17 @@ class Attachment < ActiveRecord::Base
103
82
  end
104
83
  end
105
84
 
106
- def process_section
107
- #logger.info "processing section, section_id => #{section_id}, section_node => #{section_node.inspect}"
108
- if section_node && !section_node.new_record? && section_node.section_id != section_id
109
- section_node.move_to_end(Section.find(section_id))
110
- else
111
- build_section_node(:node => self, :section_id => section_id)
112
- end
113
- end
85
+ #def process_section
86
+ # if section_node
87
+ # section_node.move_to_end(parent)
88
+ # #end
89
+ # #logger.info "processing section, section_id => #{section_id}, section_node => #{section_node.inspect}"
90
+ # #if section_node && !section_node.new_record? && section_node.section_id != section_id
91
+ # # section_node.move_to_end(Section.find(section_id))
92
+ # else
93
+ # build_section_node(:node => self, :parent => parent)
94
+ # end
95
+ #end
114
96
 
115
97
  def write_temp_file_to_storage_location
116
98
  unless temp_file.blank?
@@ -1,18 +1,6 @@
1
1
  class FileBlock < AbstractFileBlock
2
2
 
3
3
  acts_as_content_block :belongs_to_attachment => true, :taggable => true
4
-
5
- def set_attachment_file_path
6
- if @attachment_file_path && @attachment_file_path != attachment.file_path
7
- attachment.file_path = @attachment_file_path
8
- end
9
- end
10
-
11
- def set_attachment_section
12
- if @attachment_section_id && @attachment_section_id != attachment.section_id
13
- attachment.section_id = @attachment_section_id
14
- end
15
- end
16
4
 
17
5
  def self.display_name
18
6
  "File"
@@ -3,18 +3,6 @@ class ImageBlock < AbstractFileBlock
3
3
  acts_as_content_block :versioned => { :version_foreign_key => :file_block_id },
4
4
  :belongs_to_attachment => true, :taggable => true
5
5
 
6
- def set_attachment_file_path
7
- if @attachment_file_path && @attachment_file_path != attachment.file_path
8
- attachment.file_path = @attachment_file_path
9
- end
10
- end
11
-
12
- def set_attachment_section
13
- if @attachment_section_id && @attachment_section_id != attachment.section_id
14
- attachment.section_id = @attachment_section_id
15
- end
16
- end
17
-
18
6
  def self.display_name
19
7
  "Image"
20
8
  end
data/app/models/link.rb CHANGED
@@ -3,29 +3,12 @@ class Link < ActiveRecord::Base
3
3
 
4
4
  named_scope :named, lambda{|name| {:conditions => ['links.name = ?', name]}}
5
5
 
6
- has_one :section_node, :as => :node, :dependent => :destroy
7
-
6
+ has_one :section_node, :as => :node, :dependent => :destroy, :inverse_of => :node
7
+
8
8
  validates_presence_of :name
9
9
 
10
- def section_id
11
- section ? section.id : nil
12
- end
13
-
14
- def section
15
- section_node ? section_node.section : nil
16
- end
17
-
18
- def section_id=(sec_id)
19
- self.section = Section.find(sec_id)
20
- end
21
-
22
- def section=(sec)
23
- if section_node
24
- section_node.move_to_end(sec)
25
- else
26
- build_section_node(:node => self, :section => sec)
27
- end
28
- end
10
+ include Addressable
11
+ include Addressable::DeprecatedPageAccessors
29
12
 
30
13
  #needed by menu_helper
31
14
  def path
data/app/models/page.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  class Page < ActiveRecord::Base
2
-
2
+
3
3
  is_archivable
4
4
  flush_cache_on_change
5
5
  is_hideable
@@ -42,8 +42,12 @@ class Page < ActiveRecord::Base
42
42
  end
43
43
  }
44
44
 
45
- has_one :section_node, :as => :node, :dependent => :destroy
46
-
45
+ has_one :section_node, :as => :node, :dependent => :destroy, :inverse_of => :node
46
+
47
+
48
+ include Addressable
49
+ include Addressable::DeprecatedPageAccessors
50
+
47
51
  has_many :tasks
48
52
 
49
53
  before_validation :append_leading_slash_to_path
@@ -168,26 +172,6 @@ class Page < ActiveRecord::Base
168
172
  "?"
169
173
  end
170
174
 
171
- def section_id
172
- section ? section.id : nil
173
- end
174
-
175
- def section
176
- section_node ? section_node.section : nil
177
- end
178
-
179
- def section_id=(sec_id)
180
- self.section = Section.find(sec_id)
181
- end
182
-
183
- def section=(sec)
184
- if section_node
185
- section_node.move_to_end(sec)
186
- else
187
- build_section_node(:node => self, :section => sec)
188
- end
189
- end
190
-
191
175
  def public?
192
176
  section ? section.public? : false
193
177
  end
@@ -223,16 +207,25 @@ class Page < ActiveRecord::Base
223
207
  template_file_name && PageTemplate.display_name(template_file_name)
224
208
  end
225
209
 
226
- def ancestors
227
- section_node.ancestors
228
- end
229
-
210
+ # Determines if a page is a descendant of a given Section.
211
+ #
212
+ # @param [String | Section] section_or_section_name
230
213
  def in_section?(section_or_section_name)
231
- sec = section_or_section_name.is_a?(String) ?
232
- Section.first(:conditions => {:name => section_or_section_name}) :
233
- section_or_section_name
234
- fn = lambda{|s| s ? (s == sec || fn.call(s.parent)) : false}
235
- fn.call(section)
214
+ found = false
215
+ ancestors.each do |a|
216
+ if section_or_section_name.is_a?(String)
217
+ if a.name == section_or_section_name
218
+ found = true
219
+ break
220
+ end
221
+ else
222
+ if a == section_or_section_name
223
+ found = true
224
+ break
225
+ end
226
+ end
227
+ end
228
+ found
236
229
  end
237
230
 
238
231
  #Returns true if the block attached to each connector in the given container are published
@@ -256,12 +249,16 @@ class Page < ActiveRecord::Base
256
249
  (a[1..a.size].map{|a| a.name} + [name]).join(" / ")
257
250
  end
258
251
 
259
- # This will return the "top level section" for a page, which is the section directly
252
+ # This will return the "top level section" for this page, which is the section directly
260
253
  # below the root (a.k.a My Site) that this page is in. If this page is in root,
261
254
  # then this will return root.
255
+ #
256
+ # @return [Section] The first non-root ancestor if available, root otherwise.
262
257
  def top_level_section
258
+ # Cache the results of this since many projects will call it repeatly on current_page in menus.
259
+ return @top_level_section if @top_level_section
263
260
  a = ancestors
264
- (a.size > 0 && ancestors[1]) ? ancestors[1] : Section.root.first
261
+ @top_level_section = (a.size > 0 && a[1]) ? a[1] : Section.root.first
265
262
  end
266
263
 
267
264
  def current_task
@@ -3,14 +3,15 @@ class Section < ActiveRecord::Base
3
3
  flush_cache_on_change
4
4
 
5
5
  #The node that links this section to its parent
6
- has_one :node, :class_name => "SectionNode", :as => :node, :dependent => :destroy
6
+ has_one :section_node, :class_name => "SectionNode", :as => :node, :inverse_of => :node
7
7
 
8
- #The nodes that link this section to its children
9
- has_many :child_nodes, :class_name => "SectionNode"
10
- has_many :child_sections, :class_name => "SectionNode", :conditions => ["node_type = ?", "Section"], :order => 'section_nodes.position'
8
+ include Addressable
9
+ include Addressable::NodeAccessors
11
10
 
12
- has_many :pages, :through => :child_nodes, :source => :node, :source_type => 'Page', :order => 'section_nodes.position'
13
- has_many :sections, :through => :child_nodes, :source => :node, :source_type => 'Section', :order => 'section_nodes.position'
11
+ # Cannot use dependent => :destroy to do this. Ancestry's callbacks trigger before the before_destroy callback.
12
+ # So sections would always get deleted since deletable? would return true
13
+ after_destroy :destroy_node
14
+ before_destroy :deletable?
14
15
 
15
16
  has_many :group_sections
16
17
  has_many :groups, :through => :group_sections
@@ -21,59 +22,83 @@ class Section < ActiveRecord::Base
21
22
  named_scope :hidden, :conditions => {:hidden => true}
22
23
  named_scope :not_hidden, :conditions => {:hidden => false}
23
24
 
24
- named_scope :named, lambda{|name| {:conditions => ['sections.name = ?', name]}}
25
- named_scope :with_path, lambda{|path| {:conditions => ['sections.path = ?', path]}}
25
+ named_scope :named, lambda { |name| {:conditions => ['sections.name = ?', name]} }
26
+ named_scope :with_path, lambda { |path| {:conditions => ['sections.path = ?', path]} }
26
27
 
27
28
  validates_presence_of :name, :path
28
- #validates_presence_of :parent_id, :if => Proc.new {root.count > 0}, :message => "section is required"
29
29
 
30
30
  # Disabling '/' in section name for interoperability with FCKEditor file browser
31
31
  validates_format_of :name, :with => /\A[^\/]*\Z/, :message => "cannot contain '/'"
32
32
 
33
33
  validate :path_not_reserved
34
34
 
35
- before_destroy :deletable?
36
-
37
35
  attr_accessor :full_path
38
36
 
39
- def visible_child_nodes(options={})
40
- children = child_nodes.of_type(["Section", "Page", "Link"]).all(:order => 'section_nodes.position')
41
- visible_children = children.select{|sn| sn.visible?}
42
- options[:limit] ? visible_children[0...options[:limit]] : visible_children
37
+ delegate :ancestry_path, :to => :node
38
+
39
+ def ancestry
40
+ self.node.ancestry
43
41
  end
44
42
 
45
- def all_children_with_name
46
- child_sections.map do |s|
47
- if s.node
48
- s.node.full_path = root? ? s.node.name : "#{name} / #{s.node.name}"
49
- [s.node] << s.node.all_children_with_name
50
- end
51
- end.flatten.compact
43
+ def before_validation
44
+ unless node
45
+ self.node = build_section_node
46
+ end
52
47
  end
53
48
 
54
- def parent_id
55
- parent ? parent.id : nil
49
+ # Returns a list of all children which are sections.
50
+ # @return [Array<Section>]
51
+ def sections
52
+ child_nodes.of_type("Section").fetch_nodes.in_order.collect do |section_node|
53
+ section_node.node
54
+ end
56
55
  end
57
56
 
58
- def parent
59
- node ? node.section : nil
57
+ alias :child_sections :sections
58
+
59
+ # Since #sections isn't an association anymore, callers can use this rather than #sections.build
60
+ def build_section
61
+ Section.new(:parent=>self)
60
62
  end
61
63
 
62
- def parent_id=(sec_id)
63
- self.parent = Section.find(sec_id)
64
+ # Used by the sitemap to find children to iterate over.
65
+ def child_nodes
66
+ self.node.children
64
67
  end
65
68
 
66
- def parent=(sec)
67
- if node
68
- node.move_to_end(sec)
69
- else
70
- build_node(:node => self, :section => sec)
69
+ def pages
70
+ child_pages = self.node.children.collect do |section_node|
71
+ section_node.node if section_node.page?
71
72
  end
73
+ child_pages.compact
74
+ end
75
+
76
+ def self.sitemap
77
+ SectionNode.of_type(["Page", "Link", "Section"]).fetch_nodes.arrange(:order=>:position)
72
78
  end
73
79
 
74
- def ancestors(options={})
75
- ancs = node ? node.ancestors : []
76
- options[:include_self] ? ancs + [self] : ancs
80
+ def visible_child_nodes(options={})
81
+ children = child_nodes.of_type(["Section", "Page", "Link"]).fetch_nodes.in_order.all
82
+ visible_children = children.select { |sn| sn.visible? }
83
+ options[:limit] ? visible_children[0...options[:limit]] : visible_children
84
+ end
85
+
86
+
87
+ # Returns a complete list of all sections that are desecendants of this sections, in order, as a single flat list.
88
+ # Used by Section selectors where users have to pick a single section from a complete list of all sections.
89
+ def master_section_list
90
+ sections.map do |section|
91
+ section.full_path = root? ? section.name : "#{name} / #{section.name}"
92
+ [section] << section.master_section_list
93
+ end.flatten.compact
94
+ end
95
+
96
+ def parent_id
97
+ parent ? parent.id : nil
98
+ end
99
+
100
+ def parent_id=(sec_id)
101
+ self.parent = Section.find(sec_id)
77
102
  end
78
103
 
79
104
  def with_ancestors(options = {})
@@ -94,33 +119,46 @@ class Section < ActiveRecord::Base
94
119
  end
95
120
 
96
121
  def empty?
97
- child_nodes.reject{|n| n.orphaned?}.empty?
122
+ child_nodes.empty?
98
123
  end
99
124
 
125
+ # Callback to determine if this section can be deleted.
100
126
  def deletable?
101
127
  !root? && empty?
102
128
  end
103
129
 
130
+ # Callback to clean up related nodes
131
+ def destroy_node
132
+ node.destroy
133
+ end
134
+
104
135
  def editable_by_group?(group)
105
136
  group.editable_by_section(self)
106
137
  end
107
138
 
108
139
  def status
109
- public? ? :unlocked : :locked
140
+ @status ||= public? ? :unlocked : :locked
110
141
  end
111
142
 
143
+ # Used by the file browser to look up a section by the combined names as a path.
144
+ # i.e. /A/B/
145
+ # @return [Section] nil if not found
112
146
  def self.find_by_name_path(name_path)
113
- section = Section.root.first
114
- children = name_path.split("/")[1..-1] || []
115
- children.each do |name|
116
- section = section.sections.first(:conditions => {:name => name})
147
+ current_section = Section.root.first
148
+ path_names = name_path.split("/")[1..-1] || []
149
+
150
+ # This implementation is very slow as it has to loop over the entire tree in memory to match each name element.
151
+ path_names.each do |name|
152
+ current_section.sections.each do |s|
153
+ current_section = s if s.name == name
154
+ end
117
155
  end
118
- section
156
+ current_section
119
157
  end
120
158
 
121
159
  #The first page that is a decendent of this section
122
160
  def first_page_or_link
123
- section_node = child_nodes.of_type(['Link', 'Page']).first(:order => "section_nodes.position")
161
+ section_node = child_nodes.of_type(['Link', 'Page']).fetch_nodes.in_order.first
124
162
  return section_node.node if section_node
125
163
  sections.each do |s|
126
164
  node = s.first_page_or_link