browsercms 3.1.4 → 3.1.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 (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