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,193 @@
1
+ require 'digest/sha1'
2
+ require 'fileutils'
3
+
4
+ class LegacyBcms::Attachment < ActiveRecord::Base
5
+
6
+ #----- Macros ----------------------------------------------------------------
7
+
8
+ is_archivable
9
+ is_publishable
10
+ uses_soft_delete
11
+ is_userstamped
12
+ is_versioned
13
+ attr_accessor :temp_file
14
+
15
+ #----- Callbacks -------------------------------------------------------------
16
+
17
+ before_validation :make_dirty_if_temp_file
18
+ before_validation :prepend_file_path_with_slash
19
+ before_validation :extract_file_extension_from_file_name
20
+ before_validation :extract_file_type_from_temp_file
21
+ before_validation :extract_file_size_from_temp_file
22
+ before_validation :set_file_location
23
+ before_save :process_section
24
+
25
+ after_save :write_temp_file_to_storage_location
26
+ after_save :clear_ivars
27
+
28
+ #----- Associations ----------------------------------------------------------
29
+
30
+ has_one :section_node, :as => :node
31
+
32
+ #----- Validations -----------------------------------------------------------
33
+
34
+ validates_presence_of :temp_file,
35
+ :message => "You must upload a file", :on => :create
36
+ validates_presence_of :file_path
37
+ 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
+
57
+ def section=(section)
58
+ if @section != section
59
+ dirty!
60
+ @section_id = section ? section.id : nil
61
+ @section = section
62
+ end
63
+ end
64
+
65
+ #----- Callbacks Methods -----------------------------------------------------
66
+
67
+ def make_dirty_if_temp_file
68
+ dirty! if temp_file
69
+ end
70
+
71
+ def prepend_file_path_with_slash
72
+ unless file_path.blank?
73
+ self.file_path = "/#{file_path}" unless file_path =~ /^\//
74
+ end
75
+ end
76
+
77
+ def extract_file_extension_from_file_name
78
+ if file_name && file_name['.']
79
+ self.file_extension = file_name.split('.').last.to_s.downcase
80
+ end
81
+ end
82
+
83
+ def extract_file_type_from_temp_file
84
+ unless temp_file.blank?
85
+ self.file_type = temp_file.content_type
86
+ end
87
+ end
88
+
89
+ def extract_file_size_from_temp_file
90
+ unless temp_file.blank?
91
+ self.file_size = temp_file.size
92
+ end
93
+ end
94
+
95
+ # The file will be stored on disk at
96
+ # Attachment.storage_location/year/month/day/sha1
97
+ # The sha1 is a 40 character hash based on the original_filename
98
+ # of the file uploaded and the current time
99
+ def set_file_location
100
+ unless temp_file.blank?
101
+ sha1 = Digest::SHA1.hexdigest("#{temp_file.original_filename}#{Time.now.to_f}")
102
+ self.file_location = "#{Time.now.strftime("%Y/%m/%d")}/#{sha1}"
103
+ end
104
+ end
105
+
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
114
+
115
+ def write_temp_file_to_storage_location
116
+ unless temp_file.blank?
117
+ FileUtils.mkdir_p File.dirname(full_file_location)
118
+ if temp_file.local_path
119
+ FileUtils.copy temp_file.local_path, full_file_location
120
+ else
121
+ open(full_file_location, 'w') {|f| f << temp_file.read }
122
+ end
123
+
124
+ if Cms.attachment_file_permission
125
+ FileUtils.chmod Cms.attachment_file_permission, full_file_location
126
+ end
127
+ end
128
+ end
129
+
130
+ def clear_ivars
131
+ @temp_file = nil
132
+ @section = nil
133
+ @section_id = nil
134
+ end
135
+
136
+ #----- Class Methods ---------------------------------------------------------
137
+
138
+ def self.storage_location
139
+ @storage_location ||= File.join(Rails.root, "/tmp/uploads")
140
+ end
141
+
142
+ def self.storage_location=(storage_location)
143
+ @storage_location = storage_location
144
+ end
145
+
146
+ def self.find_live_by_file_path(file_path)
147
+ Attachment.published.not_archived.first(:conditions => {:file_path => file_path})
148
+ end
149
+
150
+ #----- Instance Methods ------------------------------------------------------
151
+
152
+ def file_name
153
+ file_path ? file_path.split('/').last : nil
154
+ end
155
+
156
+ def name
157
+ file_name
158
+ end
159
+
160
+ def icon
161
+ {
162
+ :doc => %w[doc],
163
+ :gif => %w[gif jpg jpeg png tiff bmp],
164
+ :htm => %w[htm html],
165
+ :pdf => %w[pdf],
166
+ :ppt => %w[ppt],
167
+ :swf => %w[swf],
168
+ :txt => %w[txt],
169
+ :xls => %w[xls],
170
+ :xml => %w[xml],
171
+ :zip => %w[zip rar tar gz tgz]
172
+ }.each do |icon, extensions|
173
+ return icon if extensions.include?(file_extension.to_s)
174
+ end
175
+ :file
176
+ end
177
+
178
+ def public?
179
+ section ? section.public? : false
180
+ end
181
+
182
+ def full_file_location
183
+ File.join(Attachment.storage_location, file_location)
184
+ end
185
+
186
+ # Forces this record to be changed, even if nothing has changed
187
+ # This is necessary if just the section.id has changed, for example
188
+ def dirty!
189
+ # Seems like a hack, is there a better way?
190
+ self.updated_at = Time.now
191
+ end
192
+
193
+ end
@@ -0,0 +1,35 @@
1
+ class LegacyBcms::Link < ActiveRecord::Base
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
+
8
+ validates_presence_of :name
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
29
+
30
+ #needed by menu_helper
31
+ def path
32
+ url
33
+ end
34
+
35
+ end
@@ -0,0 +1,275 @@
1
+ class LegacyBcms::Page < ActiveRecord::Base
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
+
11
+ has_many :connectors, :order => "connectors.container, connectors.position"
12
+ has_many :page_routes
13
+
14
+ named_scope :named, lambda{|name| {:conditions => ['pages.name = ?', name]}}
15
+ named_scope :with_path, lambda{|path| {:conditions => ['pages.path = ?', path]}}
16
+
17
+ # This scope will accept a connectable object or a Hash. The Hash is expect to have
18
+ # a value for the key :connectable, which is the connectable object, and possibly
19
+ # a value for the key :version. The Hash contains a versioned connectable object,
20
+ # it will use the value in :version if present, otherwise it will use the version
21
+ # of the object. In either case of a connectable object or a Hash, if the object
22
+ # is not versioned, no version will be used
23
+ named_scope :connected_to, lambda { |b|
24
+ if b.is_a?(Hash)
25
+ obj = b[:connectable]
26
+ if obj.class.versioned?
27
+ ver = b[:version] ? b[:version] : obj.version
28
+ else
29
+ ver = nil
30
+ end
31
+ else
32
+ obj = b
33
+ ver = obj.class.versioned? ? obj.version : nil
34
+ end
35
+
36
+ if ver
37
+ { :include => :connectors,
38
+ :conditions => ['connectors.connectable_id = ? and connectors.connectable_type = ? and connectors.connectable_version = ?', obj.id, obj.class.base_class.name, ver] }
39
+ else
40
+ { :include => :connectors,
41
+ :conditions => ['connectors.connectable_id = ? and connectors.connectable_type = ?', obj.id, obj.class.base_class.name] }
42
+ end
43
+ }
44
+
45
+ has_one :section_node, :as => :node, :dependent => :destroy
46
+
47
+ has_many :tasks
48
+
49
+ before_validation :append_leading_slash_to_path
50
+ before_destroy :delete_connectors
51
+
52
+ validates_presence_of :name, :path
53
+ validates_uniqueness_of :path
54
+ validate :path_not_reserved
55
+
56
+ def after_build_new_version(new_version)
57
+ copy_connectors(
58
+ :from_version_number => @copy_connectors_from_version || (new_version.version - 1),
59
+ :to_version_number => new_version.version
60
+ )
61
+ @copy_connectors_from_version = nil
62
+ true
63
+ end
64
+
65
+ # Publish all
66
+ def after_publish
67
+ self.reload # Get's the correct version number loaded
68
+ self.connectors.for_page_version(self.version).all(:order => "position").each do |c|
69
+ if c.connectable_type.constantize.publishable? && con = c.connectable
70
+ con.publish
71
+ end
72
+ end
73
+ end
74
+
75
+ def copy_connectors(options={})
76
+ connectors.for_page_version(options[:from_version_number]).all(:order => "connectors.container, connectors.position").each do |c|
77
+ # The connector won't have a connectable if it has been deleted
78
+ # Also need to see if the draft has been deleted,
79
+ # in which case we are in the process of deleting it
80
+ if c.should_be_copied?
81
+ connectable = c.connectable_type.constantize.versioned? ? c.connectable.as_of_version(c.connectable_version) : c.connectable
82
+
83
+ #If we are copying connectors from a previous version, that means we are reverting this page,
84
+ #in which case we should create a new version of the block, and connect this page to that block
85
+ if @copy_connectors_from_version && connectable.class.versioned? && (connectable.version != connectable.draft.version)
86
+ connectable = connectable.class.find(connectable.id)
87
+ connectable.updated_by_page = self
88
+ connectable.revert_to(c.connectable_version)
89
+ end
90
+
91
+ new_connector = connectors.build(
92
+ :page_version => options[:to_version_number],
93
+ :connectable => connectable,
94
+ :connectable_version => connectable.class.versioned? ? connectable.version : nil,
95
+ :container => c.container,
96
+ :position => c.position
97
+ )
98
+ end
99
+ end
100
+ true
101
+ end
102
+
103
+ def create_connector(connectable, container)
104
+ transaction do
105
+ raise "Connectable is nil" unless connectable
106
+ raise "Container is required" if container.blank?
107
+ update_attributes(
108
+ :version_comment => "#{connectable} was added to the '#{container}' container",
109
+ :publish_on_save => (
110
+ published? &&
111
+ connectable.connected_page &&
112
+ (connectable.class.publishable? ? connectable.published? : true)))
113
+ connectors.create(
114
+ :page_version => draft.version,
115
+ :connectable => connectable,
116
+ :connectable_version => connectable.class.versioned? ? connectable.version : nil,
117
+ :container => container)
118
+ end
119
+ end
120
+
121
+ def move_connector(connector, direction)
122
+ transaction do
123
+ raise "Connector is nil" unless connector
124
+ raise "Direction is nil" unless direction
125
+ orientation = direction[/_/] ? "#{direction.sub('_', ' the ')} of" : "#{direction} within"
126
+ update_attributes(:version_comment => "#{connector.connectable} was moved #{orientation} the '#{connector.container}' container")
127
+ connectors.for_page_version(draft.version).like(connector).first.send("move_#{direction}")
128
+ end
129
+ end
130
+
131
+ %w(up down to_top to_bottom).each do |d|
132
+ define_method("move_connector_#{d}") do |connector|
133
+ move_connector(connector, d)
134
+ end
135
+ end
136
+
137
+ def remove_connector(connector)
138
+ transaction do
139
+ raise "Connector is nil" unless connector
140
+ update_attributes(:version_comment => "#{connector.connectable} was removed from the '#{connector.container}' container")
141
+
142
+ #The logic of this is to go ahead and let the container get copied forward, then delete the new connector
143
+ if new_connector = connectors.for_page_version(draft.version).like(connector).first
144
+ new_connector.destroy
145
+ else
146
+ raise "Error occurred while trying to remove connector #{connector.id}"
147
+ end
148
+ end
149
+ end
150
+
151
+ def delete_connectors
152
+ connectors.for_page_version(version).all.each{|c| c.destroy }
153
+ end
154
+
155
+ #This is done to let copy_connectors know which version to pull from
156
+ #copy_connectors will get called later as an after_update callback
157
+ def revert_to(version)
158
+ @copy_connectors_from_version = version
159
+ super(version)
160
+ end
161
+
162
+ def file_size
163
+ "?"
164
+ end
165
+
166
+ def section_id
167
+ section ? section.id : nil
168
+ end
169
+
170
+ def section
171
+ section_node ? section_node.section : nil
172
+ end
173
+
174
+ def section_id=(sec_id)
175
+ self.section = Section.find(sec_id)
176
+ end
177
+
178
+ def section=(sec)
179
+ if section_node
180
+ section_node.move_to_end(sec)
181
+ else
182
+ build_section_node(:node => self, :section => sec)
183
+ end
184
+ end
185
+
186
+ def public?
187
+ section ? section.public? : false
188
+ end
189
+
190
+ def page_title
191
+ title.blank? ? name : title
192
+ end
193
+
194
+ def append_leading_slash_to_path
195
+ if path.blank?
196
+ self.path = "/"
197
+ elsif path[0,1] != "/"
198
+ self.path = "/#{path}"
199
+ end
200
+ end
201
+
202
+ def path_not_reserved
203
+ if Cms.reserved_paths.include?(path)
204
+ errors.add(:path, "is invalid, '#{path}' a reserved path")
205
+ end
206
+ end
207
+
208
+ def layout
209
+ template_file_name && "templates/#{template_file_name.split('.').first}"
210
+ end
211
+
212
+ # This will be nil if it is a file system based template
213
+ def template
214
+ PageTemplate.find_by_file_name(template_file_name)
215
+ end
216
+
217
+ def template_name
218
+ template_file_name && PageTemplate.display_name(template_file_name)
219
+ end
220
+
221
+ def ancestors
222
+ section_node.ancestors
223
+ end
224
+
225
+ def in_section?(section_or_section_name)
226
+ sec = section_or_section_name.is_a?(String) ?
227
+ Section.first(:conditions => {:name => section_or_section_name}) :
228
+ section_or_section_name
229
+ fn = lambda{|s| s ? (s == sec || fn.call(s.parent)) : false}
230
+ fn.call(section)
231
+ end
232
+
233
+ #Returns true if the block attached to each connector in the given container are published
234
+ def container_published?(container)
235
+ connectors.for_page_version(draft.version).in_container(container.to_s).all? do |c|
236
+ c.connectable_type.constantize.publishable? ? c.connectable.live? : true
237
+ end
238
+ end
239
+
240
+ # Returns the number of connectables in the given container for this version of this page
241
+ def connectable_count_for_container(container)
242
+ connectors.for_page_version(version).in_container(container.to_s).count
243
+ end
244
+
245
+ def self.find_live_by_path(path)
246
+ published.not_archived.first(:conditions => {:path => path})
247
+ end
248
+
249
+ def name_with_section_path
250
+ a = ancestors
251
+ (a[1..a.size].map{|a| a.name} + [name]).join(" / ")
252
+ end
253
+
254
+ # This will return the "top level section" for a page, which is the section directly
255
+ # below the root (a.k.a My Site) that this page is in. If this page is in root,
256
+ # then this will return root.
257
+ def top_level_section
258
+ a = ancestors
259
+ (a.size > 0 && ancestors[1]) ? ancestors[1] : Section.root.first
260
+ end
261
+
262
+ def current_task
263
+ tasks.incomplete.first
264
+ end
265
+
266
+ def assigned_to
267
+ current_task ? current_task.assigned_to : nil
268
+ end
269
+
270
+ def assigned_to?(user)
271
+ assigned_to == user
272
+ end
273
+
274
+ end
275
+