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.
- 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
|
+
|