bcms_ancestry 1.0.0

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.
@@ -0,0 +1,155 @@
1
+ class LegacyBcms::Section < ActiveRecord::Base
2
+
3
+ flush_cache_on_change
4
+
5
+ #The node that links this section to its parent
6
+ has_one :node, :class_name => "LegacyBcms::SectionNode", :as => :node, :dependent => :destroy
7
+
8
+ #The nodes that link this section to its children
9
+ has_many :child_nodes, :class_name => "LegacyBcms::SectionNode"
10
+ has_many :child_sections, :class_name => "LegacyBcms::SectionNode", :conditions => ["node_type = ?", "LegacyBcms::Section"], :order => 'section_nodes.position'
11
+
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'
14
+
15
+ has_many :group_sections
16
+ has_many :groups, :through => :group_sections
17
+
18
+ named_scope :root, :conditions => ['root = ?', true]
19
+ named_scope :system, :conditions => {:name => 'system'}
20
+
21
+ named_scope :hidden, :conditions => {:hidden => true}
22
+ named_scope :not_hidden, :conditions => {:hidden => false}
23
+
24
+ named_scope :named, lambda{|name| {:conditions => ['sections.name = ?', name]}}
25
+ named_scope :with_path, lambda{|path| {:conditions => ['sections.path = ?', path]}}
26
+
27
+ validates_presence_of :name, :path
28
+ #validates_presence_of :parent_id, :if => Proc.new {root.count > 0}, :message => "section is required"
29
+
30
+ # Disabling '/' in section name for interoperability with FCKEditor file browser
31
+ validates_format_of :name, :with => /\A[^\/]*\Z/, :message => "cannot contain '/'"
32
+
33
+ validate :path_not_reserved
34
+
35
+ before_destroy :deletable?
36
+
37
+ attr_accessor :full_path
38
+
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
43
+ end
44
+
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
52
+ end
53
+
54
+ def parent_id
55
+ parent ? parent.id : nil
56
+ end
57
+
58
+ def parent
59
+ node ? node.section : nil
60
+ end
61
+
62
+ def parent_id=(sec_id)
63
+ self.parent = Section.find(sec_id)
64
+ end
65
+
66
+ def parent=(sec)
67
+ if node
68
+ node.move_to_end(sec)
69
+ else
70
+ build_node(:node => self, :section => sec)
71
+ end
72
+ end
73
+
74
+ def ancestors(options={})
75
+ ancs = node ? node.ancestors : []
76
+ options[:include_self] ? ancs + [self] : ancs
77
+ end
78
+
79
+ def with_ancestors(options = {})
80
+ options.merge! :include_self => true
81
+ self.ancestors(options)
82
+ end
83
+
84
+ def move_to(section)
85
+ if root?
86
+ false
87
+ else
88
+ node.move_to_end(section)
89
+ end
90
+ end
91
+
92
+ def public?
93
+ !!(groups.find_by_code('guest'))
94
+ end
95
+
96
+ def empty?
97
+ child_nodes.reject{|n| n.orphaned?}.empty?
98
+ end
99
+
100
+ def deletable?
101
+ !root? && empty?
102
+ end
103
+
104
+ def editable_by_group?(group)
105
+ group.editable_by_section(self)
106
+ end
107
+
108
+ def status
109
+ public? ? :unlocked : :locked
110
+ end
111
+
112
+ 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})
117
+ end
118
+ section
119
+ end
120
+
121
+ #The first page that is a decendent of this section
122
+ def first_page_or_link
123
+ section_node = child_nodes.of_type(['Link', 'Page']).first(:order => "section_nodes.position")
124
+ return section_node.node if section_node
125
+ sections.each do |s|
126
+ node = s.first_page_or_link
127
+ return node if node
128
+ end
129
+ nil
130
+ end
131
+
132
+ def actual_path
133
+ if root?
134
+ "/"
135
+ else
136
+ p = first_page_or_link
137
+ p ? p.path : "#"
138
+ end
139
+ end
140
+
141
+ def path_not_reserved
142
+ if Cms.reserved_paths.include?(path)
143
+ errors.add(:path, "is invalid, '#{path}' a reserved path")
144
+ end
145
+ end
146
+
147
+ ##
148
+ # Set which groups are allowed to access this section.
149
+ # @params [Symbol] code Set of groups to allow (Options :all, :none) Defaults to :none
150
+ def allow_groups=(code=:none)
151
+ if code == :all
152
+ self.groups = Group.all
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,93 @@
1
+ class LegacyBcms::SectionNode < ActiveRecord::Base
2
+ belongs_to :section
3
+ belongs_to :node, :polymorphic => :true
4
+
5
+ acts_as_list :scope => :section
6
+
7
+ named_scope :of_type, lambda{|types| {:conditions => ["section_nodes.node_type IN (?)", types]}}
8
+
9
+ def visible?
10
+ return false unless node
11
+ return false if(node.respond_to?(:hidden?) && node.hidden?)
12
+ return false if(node.respond_to?(:archived?) && node.archived?)
13
+ return false if(node.respond_to?(:published?) && !node.published?)
14
+ true
15
+ end
16
+
17
+ def orphaned?
18
+ !node || (node.class.uses_soft_delete? && node.deleted?)
19
+ end
20
+
21
+ #Is this node a section
22
+ def section?
23
+ node_type == 'Section'
24
+ end
25
+
26
+ #Is this node a page
27
+ def page?
28
+ node_type == 'Page'
29
+ end
30
+
31
+ def move_to(sec, pos)
32
+ #logger.info "Moving Section Node ##{id} to Section ##{sec.id} Position #{pos}"
33
+ transaction do
34
+ if section != sec
35
+ remove_from_list
36
+ self.section = sec
37
+ save
38
+ end
39
+
40
+ if pos < 0
41
+ pos = 0
42
+ else
43
+ #This helps prevent the position from getting out of whack
44
+ #If you pass in a really high number for position,
45
+ #this just corrects it to the right number
46
+ node_count = SectionNode.count(:conditions => {:section_id => section_id})
47
+ pos = node_count if pos > node_count
48
+ end
49
+
50
+ insert_at_position(pos)
51
+ end
52
+ end
53
+
54
+ def move_before(section_node)
55
+ if section == section_node.section && position < section_node.position
56
+ pos = section_node.position - 1
57
+ else
58
+ pos = section_node.position
59
+ end
60
+ move_to(section_node.section, pos)
61
+ end
62
+
63
+ def move_after(section_node)
64
+ if section == section_node.section && position < section_node.position
65
+ pos = section_node.position
66
+ else
67
+ pos = section_node.position + 1
68
+ end
69
+ move_to(section_node.section, pos)
70
+ end
71
+
72
+ def move_to_beginning(sec)
73
+ move_to(sec, 0)
74
+ end
75
+
76
+ def move_to_end(sec)
77
+ #1.0/0 == Infinity
78
+ move_to(sec, 1.0/0)
79
+ end
80
+
81
+ def ancestors()
82
+ ancestors = []
83
+ fn = lambda do |sn|
84
+ ancestors << sn.section
85
+ if sn.section && !sn.section.root?
86
+ fn.call(sn.section.node)
87
+ end
88
+ end
89
+ fn.call(self)
90
+ ancestors.reverse
91
+ end
92
+
93
+ end
@@ -0,0 +1,32 @@
1
+ class Link < SectionNode
2
+ #acts_as_content_block :connectable => false
3
+
4
+ named_scope :named, lambda{|name| {:conditions => ['links.name = ?', name]}}
5
+
6
+ # has_one :section_node, :as => :node, :dependent => :destroy
7
+ is_versioned :version_foreign_key => :ancestry_section_node_id
8
+
9
+ validates_presence_of :name
10
+
11
+ def section_id
12
+ self.parent_id
13
+ end
14
+
15
+ def section
16
+ parent
17
+ end
18
+
19
+ def section_id=(sec_id)
20
+ parent_id = sec_id
21
+ end
22
+
23
+ def section=(sec)
24
+ self.parent = sec
25
+ end
26
+
27
+ #needed by menu_helper
28
+ def path
29
+ url
30
+ end
31
+
32
+ end
@@ -0,0 +1,307 @@
1
+ class Page < SectionNode
2
+
3
+ is_archivable
4
+ flush_cache_on_change
5
+ is_hideable
6
+ is_publishable
7
+ uses_soft_delete
8
+ is_userstamped
9
+ #is_versioned
10
+ #is_versioned :version_foreign_key => :ancestry_section_node_id
11
+
12
+ has_many :connectors, :order => "connectors.container, connectors.position"
13
+ has_many :page_routes
14
+
15
+ named_scope :named, lambda{|name| {:conditions => ['pages.name = ?', name]}}
16
+ named_scope :with_path, lambda{|path| {:conditions => ['pages.path = ?', path]}}
17
+
18
+ is_versioned :version_foreign_key => :ancestry_section_node_id
19
+
20
+ # This scope will accept a connectable object or a Hash. The Hash is expect to have
21
+ # a value for the key :connectable, which is the connectable object, and possibly
22
+ # a value for the key :version. The Hash contains a versioned connectable object,
23
+ # it will use the value in :version if present, otherwise it will use the version
24
+ # of the object. In either case of a connectable object or a Hash, if the object
25
+ # is not versioned, no version will be used
26
+ named_scope :connected_to, lambda { |b|
27
+ if b.is_a?(Hash)
28
+ obj = b[:connectable]
29
+ if obj.class.versioned?
30
+ ver = b[:version] ? b[:version] : obj.version
31
+ else
32
+ ver = nil
33
+ end
34
+ else
35
+ obj = b
36
+ ver = obj.class.versioned? ? obj.version : nil
37
+ end
38
+
39
+ if ver
40
+ { :include => :connectors,
41
+ :conditions => ['connectors.connectable_id = ? and connectors.connectable_type = ? and connectors.connectable_version = ?', obj.id, obj.class.base_class.name, ver] }
42
+ else
43
+ { :include => :connectors,
44
+ :conditions => ['connectors.connectable_id = ? and connectors.connectable_type = ?', obj.id, obj.class.base_class.name] }
45
+ end
46
+ }
47
+
48
+ #has_one :section_node, :as => :node, :dependent => :destroy
49
+
50
+ has_many :tasks
51
+
52
+ before_validation :append_leading_slash_to_path
53
+ before_destroy :delete_connectors
54
+
55
+ validates_presence_of :name, :path
56
+ validates_uniqueness_of :path
57
+ validate :path_not_reserved
58
+
59
+ def after_build_new_version(new_version)
60
+ copy_connectors(
61
+ :from_version_number => @copy_connectors_from_version || (new_version.version - 1),
62
+ :to_version_number => new_version.version
63
+ )
64
+ @copy_connectors_from_version = nil
65
+ true
66
+ end
67
+
68
+ # Publish all
69
+ def after_publish
70
+ self.reload # Get's the correct version number loaded
71
+ self.connectors.for_page_version(self.version).all(:order => "position").each do |c|
72
+ if c.connectable_type.constantize.publishable? && con = c.connectable
73
+ con.publish
74
+ end
75
+ end
76
+ end
77
+
78
+ def copy_connectors(options={})
79
+ connectors.for_page_version(options[:from_version_number]).all(:order => "connectors.container, connectors.position").each do |c|
80
+ # The connector won't have a connectable if it has been deleted
81
+ # Also need to see if the draft has been deleted,
82
+ # in which case we are in the process of deleting it
83
+ if c.should_be_copied?
84
+ connectable = c.connectable_type.constantize.versioned? ? c.connectable.as_of_version(c.connectable_version) : c.connectable
85
+
86
+ #If we are copying connectors from a previous version, that means we are reverting this page,
87
+ #in which case we should create a new version of the block, and connect this page to that block
88
+ if @copy_connectors_from_version && connectable.class.versioned? && (connectable.version != connectable.draft.version)
89
+ connectable = connectable.class.find(connectable.id)
90
+ connectable.updated_by_page = self
91
+ connectable.revert_to(c.connectable_version)
92
+ end
93
+
94
+ new_connector = connectors.build(
95
+ :page_version => options[:to_version_number],
96
+ :connectable => connectable,
97
+ :connectable_version => connectable.class.versioned? ? connectable.version : nil,
98
+ :container => c.container,
99
+ :position => c.position
100
+ )
101
+ end
102
+ end
103
+ true
104
+ end
105
+
106
+ def create_connector(connectable, container)
107
+ transaction do
108
+ raise "Connectable is nil" unless connectable
109
+ raise "Container is required" if container.blank?
110
+ update_attributes(
111
+ :version_comment => "#{connectable} was added to the '#{container}' container",
112
+ :publish_on_save => (
113
+ published? &&
114
+ connectable.connected_page &&
115
+ (connectable.class.publishable? ? connectable.published? : true)))
116
+ connectors.create(
117
+ :page_version => draft.version,
118
+ :connectable => connectable,
119
+ :connectable_version => connectable.class.versioned? ? connectable.version : nil,
120
+ :container => container)
121
+ end
122
+ end
123
+
124
+ def move_connector(connector, direction)
125
+ transaction do
126
+ raise "Connector is nil" unless connector
127
+ raise "Direction is nil" unless direction
128
+ orientation = direction[/_/] ? "#{direction.sub('_', ' the ')} of" : "#{direction} within"
129
+ update_attributes(:version_comment => "#{connector.connectable} was moved #{orientation} the '#{connector.container}' container")
130
+ connectors.for_page_version(draft.version).like(connector).first.send("move_#{direction}")
131
+ end
132
+ end
133
+
134
+ %w(up down to_top to_bottom).each do |d|
135
+ define_method("move_connector_#{d}") do |connector|
136
+ move_connector(connector, d)
137
+ end
138
+ end
139
+
140
+ def remove_connector(connector)
141
+ transaction do
142
+ raise "Connector is nil" unless connector
143
+ update_attributes(:version_comment => "#{connector.connectable} was removed from the '#{connector.container}' container")
144
+
145
+ #The logic of this is to go ahead and let the container get copied forward, then delete the new connector
146
+ if new_connector = connectors.for_page_version(draft.version).like(connector).first
147
+ new_connector.destroy
148
+ else
149
+ raise "Error occurred while trying to remove connector #{connector.id}"
150
+ end
151
+ end
152
+ end
153
+
154
+ def delete_connectors
155
+ connectors.for_page_version(version).all.each{|c| c.destroy }
156
+ end
157
+
158
+ #This is done to let copy_connectors know which version to pull from
159
+ #copy_connectors will get called later as an after_update callback
160
+ def revert_to(version)
161
+ @copy_connectors_from_version = version
162
+ super(version)
163
+ end
164
+
165
+ def file_size
166
+ "?"
167
+ end
168
+
169
+ def section_id
170
+ section ? section.id : nil
171
+ end
172
+
173
+ def section
174
+ section_node ? section_node.section : nil
175
+ end
176
+
177
+ def section_id=(sec_id)
178
+ self.section = Section.find(sec_id)
179
+ end
180
+
181
+ def section=(sec)
182
+ if section_node
183
+ section_node.move_to_end(sec)
184
+ else
185
+ build_section_node(:node => self, :section => sec)
186
+ end
187
+ end
188
+
189
+ # moved to section
190
+ # def public?
191
+ # section ? section.public? : false
192
+ # end
193
+
194
+ def page_title
195
+ title.blank? ? name : title
196
+ end
197
+
198
+ def append_leading_slash_to_path
199
+ if path.blank?
200
+ self.path = "/"
201
+ elsif path[0,1] != "/"
202
+ self.path = "/#{path}"
203
+ end
204
+ end
205
+
206
+ def path_not_reserved
207
+ if Cms.reserved_paths.include?(path)
208
+ errors.add(:path, "is invalid, '#{path}' a reserved path")
209
+ end
210
+ end
211
+
212
+ def layout
213
+ template_file_name && "templates/#{template_file_name.split('.').first}"
214
+ end
215
+
216
+ # This will be nil if it is a file system based template
217
+ def template
218
+ PageTemplate.find_by_file_name(template_file_name)
219
+ end
220
+
221
+ def template_name
222
+ template_file_name && PageTemplate.display_name(template_file_name)
223
+ end
224
+
225
+ def ancestors
226
+ section_node.ancestors
227
+ end
228
+
229
+ def in_section?(section_or_section_name)
230
+ sec = section_or_section_name.is_a?(String) ?
231
+ Section.first(:conditions => {:name => section_or_section_name}) :
232
+ section_or_section_name
233
+ fn = lambda{|s| s ? (s == sec || fn.call(s.parent)) : false}
234
+ fn.call(section)
235
+ end
236
+
237
+ #Returns true if the block attached to each connector in the given container are published
238
+ def container_published?(container)
239
+ connectors.for_page_version(draft.version).in_container(container.to_s).all? do |c|
240
+ c.connectable_type.constantize.publishable? ? c.connectable.live? : true
241
+ end
242
+ end
243
+
244
+ # Returns the number of connectables in the given container for this version of this page
245
+ def connectable_count_for_container(container)
246
+ connectors.for_page_version(version).in_container(container.to_s).count
247
+ end
248
+
249
+ def self.find_live_by_path(path)
250
+ published.not_archived.first(:conditions => {:path => path})
251
+ end
252
+
253
+ def name_with_section_path
254
+ a = ancestors
255
+ (a[1..a.size].map{|a| a.name} + [name]).join(" / ")
256
+ end
257
+
258
+ # This will return the "top level section" for a page, which is the section directly
259
+ # below the root (a.k.a My Site) that this page is in. If this page is in root,
260
+ # then this will return root.
261
+ def top_level_section
262
+ a = ancestors
263
+ (a.size > 0 && ancestors[1]) ? ancestors[1] : Section.root.first
264
+ end
265
+
266
+ def current_task
267
+ tasks.incomplete.first
268
+ end
269
+
270
+ def assigned_to
271
+ current_task ? current_task.assigned_to : nil
272
+ end
273
+
274
+ def assigned_to?(user)
275
+ assigned_to == user
276
+ end
277
+
278
+
279
+
280
+ ######### proxy ###########
281
+
282
+
283
+ def section_id
284
+ self.parent_id
285
+ end
286
+
287
+ def section
288
+ self.parent
289
+ end
290
+
291
+ def section_id=(sec_id)
292
+ self.parent_id = sec_id
293
+ end
294
+
295
+ def section=(sec)
296
+ self.parent = sec
297
+ end
298
+
299
+ def status
300
+ "asd" #public? ? :unlocked : :locked
301
+ end
302
+
303
+ def self.versioned_columns
304
+ @versioned_columns ||= (self.new.attributes.keys -
305
+ (%w[ancestry lock_version position version_comment created_at updated_at created_by_id updated_by_id type] + [version_foreign_key]))
306
+ end
307
+ end