bcms_ancestry 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +243 -0
- data/app/controllers/application_controller.rb +12 -0
- data/app/controllers/cms/sections_controller.rb +130 -0
- data/app/helpers/application_helper.rb +3 -0
- data/app/helpers/cms/section_nodes_helper.rb +11 -0
- data/app/models/attachment.rb +177 -0
- data/app/models/legacy_bcms/attachment.rb +193 -0
- data/app/models/legacy_bcms/link.rb +35 -0
- data/app/models/legacy_bcms/page.rb +275 -0
- data/app/models/legacy_bcms/section.rb +155 -0
- data/app/models/legacy_bcms/section_node.rb +93 -0
- data/app/models/link.rb +32 -0
- data/app/models/page.rb +307 -0
- data/app/models/section.rb +177 -0
- data/app/models/section_node.rb +142 -0
- data/app/views/cms/section_nodes/_link.html.erb +11 -0
- data/app/views/cms/section_nodes/_node.html.erb +36 -0
- data/app/views/cms/section_nodes/_page.html.erb +14 -0
- data/app/views/cms/section_nodes/_section.html.erb +13 -0
- data/app/views/cms/section_nodes/_section_node.html.erb +27 -0
- data/app/views/cms/section_nodes/_sitemap.html.erb +99 -0
- data/app/views/cms/section_nodes/index.html.erb +21 -0
- data/db/migrate/20100629162323_create_section_nodes.rb +41 -0
- data/lib/bcms_ancestry.rb +1 -0
- data/lib/bcms_ancestry/routes.rb +7 -0
- data/public/bcms/ancestry/README +1 -0
- data/rails/init.rb +4 -0
- metadata +93 -0
@@ -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
|
+
|