browsercms 3.1.4 → 3.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/app/controllers/cms/content_block_controller.rb +2 -2
- data/app/controllers/cms/section_nodes_controller.rb +6 -1
- data/app/controllers/cms/sections_controller.rb +1 -1
- data/app/helpers/cms/application_helper.rb +1 -1
- data/app/helpers/cms/content_block_helper.rb +27 -0
- data/app/helpers/cms/section_nodes_helper.rb +43 -5
- data/app/models/abstract_file_block.rb +16 -1
- data/app/models/attachment.rb +17 -35
- data/app/models/file_block.rb +0 -12
- data/app/models/image_block.rb +0 -12
- data/app/models/link.rb +4 -21
- data/app/models/page.rb +31 -34
- data/app/models/section.rb +82 -44
- data/app/models/section_node.rb +39 -24
- data/app/models/user.rb +5 -0
- data/app/views/cms/blocks/index.html.erb +4 -4
- data/app/views/cms/file_blocks/_form.html.erb +1 -1
- data/app/views/cms/image_blocks/_form.html.erb +1 -1
- data/app/views/cms/section_nodes/_link.html.erb +6 -3
- data/app/views/cms/section_nodes/_node.html.erb +11 -1
- data/app/views/cms/section_nodes/_page.html.erb +13 -7
- data/app/views/cms/section_nodes/_section.html.erb +24 -8
- data/app/views/cms/section_nodes/index.html.erb +28 -16
- data/app/views/layouts/templates/default.html.erb +17 -0
- data/browsercms.gemspec +28 -1413
- data/db/migrate/20120117144039_browsercms315.rb +94 -0
- data/db/migrate/{20081114172307_load_seed_data.rb → 20121114172307_load_seeds.rb} +8 -1
- data/lib/acts_as_list.rb +1 -1
- data/lib/browsercms.rb +2 -0
- data/lib/cms/addressable.rb +83 -0
- data/lib/cms/behaviors/attaching.rb +44 -24
- data/lib/cms/behaviors/connecting.rb +2 -1
- data/lib/cms/behaviors/publishing.rb +12 -3
- data/lib/cms/behaviors/versioning.rb +83 -53
- data/lib/cms/content_rendering_support.rb +3 -3
- data/lib/cms/error_pages.rb +8 -0
- data/lib/cms/init.rb +5 -3
- data/lib/cms/version.rb +1 -1
- data/templates/blank.rb +2 -0
- data/templates/demo.rb +2 -0
- data/templates/module.rb +2 -0
- data/test/custom_assertions.rb +7 -1
- data/test/factories.rb +3 -1
- data/test/factories/sitemap_factories.rb +28 -0
- data/test/fixtures/connectors.yml +97 -0
- data/test/fixtures/content_type_groups.yml +13 -0
- data/test/fixtures/content_types.yml +50 -0
- data/test/fixtures/dynamic_view_versions.yml +26 -0
- data/test/fixtures/dynamic_views.yml +26 -0
- data/test/fixtures/group_permissions.yml +16 -0
- data/test/fixtures/group_sections.yml +31 -0
- data/test/fixtures/group_type_permissions.yml +11 -0
- data/test/fixtures/group_types.yml +25 -0
- data/test/fixtures/groups.yml +25 -0
- data/test/fixtures/html_block_versions.yml +67 -0
- data/test/fixtures/html_blocks.yml +63 -0
- data/test/fixtures/page_versions.yml +265 -0
- data/test/fixtures/pages.yml +85 -0
- data/test/fixtures/permissions.yml +28 -0
- data/test/fixtures/section_nodes.yml +46 -0
- data/test/fixtures/sections.yml +19 -0
- data/test/fixtures/sites.yml +9 -0
- data/test/fixtures/user_group_memberships.yml +11 -0
- data/test/fixtures/users.yml +15 -0
- data/test/functional/cms/content_controller_test.rb +6 -1
- data/test/functional/cms/file_blocks_controller_test.rb +1 -0
- data/test/functional/cms/html_blocks_controller_test.rb +1 -0
- data/test/functional/cms/image_blocks_controller_test.rb +39 -32
- data/test/functional/cms/section_nodes_controller_test.rb +48 -20
- data/test/functional/cms/sections_controller_test.rb +3 -1
- data/test/functional/tests/pretend_controller_test.rb +6 -3
- data/test/integration/cms/ckeditor_test.rb +5 -2
- data/test/integration/sitemap_performance_test.rb +26 -0
- data/test/selenium-core/Blank.html +7 -0
- data/test/selenium-core/InjectedRemoteRunner.html +8 -0
- data/test/selenium-core/RemoteRunner.html +110 -0
- data/test/selenium-core/SeleniumLog.html +109 -0
- data/test/selenium-core/TestPrompt.html +145 -0
- data/test/selenium-core/TestRunner-splash.html +55 -0
- data/test/selenium-core/TestRunner.hta +176 -0
- data/test/selenium-core/TestRunner.html +176 -0
- data/test/selenium-core/domviewer/butmin.gif +0 -0
- data/test/selenium-core/domviewer/butplus.gif +0 -0
- data/test/selenium-core/domviewer/domviewer.css +298 -0
- data/test/selenium-core/domviewer/domviewer.html +16 -0
- data/test/selenium-core/domviewer/selenium-domviewer.js +205 -0
- data/test/selenium-core/icons/all.png +0 -0
- data/test/selenium-core/icons/continue.png +0 -0
- data/test/selenium-core/icons/continue_disabled.png +0 -0
- data/test/selenium-core/icons/pause.png +0 -0
- data/test/selenium-core/icons/pause_disabled.png +0 -0
- data/test/selenium-core/icons/selected.png +0 -0
- data/test/selenium-core/icons/step.png +0 -0
- data/test/selenium-core/icons/step_disabled.png +0 -0
- data/test/selenium-core/iedoc-core.xml +1515 -0
- data/test/selenium-core/iedoc.xml +1469 -0
- data/test/selenium-core/lib/cssQuery/cssQuery-p.js +6 -0
- data/test/selenium-core/lib/cssQuery/src/cssQuery-level2.js +142 -0
- data/test/selenium-core/lib/cssQuery/src/cssQuery-level3.js +150 -0
- data/test/selenium-core/lib/cssQuery/src/cssQuery-standard.js +53 -0
- data/test/selenium-core/lib/cssQuery/src/cssQuery.js +356 -0
- data/test/selenium-core/lib/prototype.js +2006 -0
- data/test/selenium-core/lib/scriptaculous/builder.js +101 -0
- data/test/selenium-core/lib/scriptaculous/controls.js +815 -0
- data/test/selenium-core/lib/scriptaculous/dragdrop.js +915 -0
- data/test/selenium-core/lib/scriptaculous/effects.js +958 -0
- data/test/selenium-core/lib/scriptaculous/scriptaculous.js +47 -0
- data/test/selenium-core/lib/scriptaculous/slider.js +283 -0
- data/test/selenium-core/lib/scriptaculous/unittest.js +383 -0
- data/test/selenium-core/scripts/find_matching_child.js +69 -0
- data/test/selenium-core/scripts/htmlutils.js +894 -0
- data/test/selenium-core/scripts/injection.html +72 -0
- data/test/selenium-core/scripts/js2html.js +70 -0
- data/test/selenium-core/scripts/narcissus-defs.js +175 -0
- data/test/selenium-core/scripts/narcissus-exec.js +1054 -0
- data/test/selenium-core/scripts/narcissus-parse.js +1003 -0
- data/test/selenium-core/scripts/se2html.js +63 -0
- data/test/selenium-core/scripts/selenium-api.js +2409 -0
- data/test/selenium-core/scripts/selenium-browserbot.js +2203 -0
- data/test/selenium-core/scripts/selenium-browserdetect.js +150 -0
- data/test/selenium-core/scripts/selenium-commandhandlers.js +377 -0
- data/test/selenium-core/scripts/selenium-executionloop.js +175 -0
- data/test/selenium-core/scripts/selenium-logging.js +147 -0
- data/test/selenium-core/scripts/selenium-remoterunner.js +571 -0
- data/test/selenium-core/scripts/selenium-testrunner.js +1333 -0
- data/test/selenium-core/scripts/selenium-version.js +5 -0
- data/test/selenium-core/scripts/user-extensions.js +3 -0
- data/test/selenium-core/scripts/user-extensions.js.sample +75 -0
- data/test/selenium-core/scripts/xmlextras.js +153 -0
- data/test/selenium-core/selenium-logo.png +0 -0
- data/test/selenium-core/selenium-test.css +43 -0
- data/test/selenium-core/selenium.css +299 -0
- data/test/selenium-core/xpath/dom.js +428 -0
- data/test/selenium-core/xpath/misc.js +252 -0
- data/test/selenium-core/xpath/xpath.js +2223 -0
- data/test/selenium/_login_as_cmsadmin.rsel +4 -0
- data/test/selenium/dashboard.rsel +5 -0
- data/test/selenium/html_blocks.rsel +4 -0
- data/test/selenium/login/failed_login.rsel +8 -0
- data/test/selenium/login/successful_login.rsel +9 -0
- data/test/selenium/page_templates.rsel +12 -0
- data/test/selenium/pages/edit_properties.rsel +5 -0
- data/test/selenium/site/view_home_page.rsel +4 -0
- data/test/selenium/sitemap/move_page.rsel +9 -0
- data/test/selenium/sitemap/open_section.rsel +6 -0
- data/test/selenium/sitemap/select_page.rsel +12 -0
- data/test/selenium/sitemap/select_section.rsel +17 -0
- data/test/test_helper.rb +30 -12
- data/test/unit/behaviors/attaching_test.rb +4 -6
- data/test/unit/behaviors/connectable_test.rb +29 -0
- data/test/unit/behaviors/publishable_test.rb +40 -9
- data/test/unit/behaviors/versioning_test.rb +36 -0
- data/test/unit/helpers/menu_helper_test.rb +5 -2
- data/test/unit/helpers/page_helper_test.rb +2 -0
- data/test/unit/lib/cms/sitemap_test.rb +206 -0
- data/test/unit/models/attachment_test.rb +51 -31
- data/test/unit/models/file_block_test.rb +74 -55
- data/test/unit/models/link_test.rb +44 -0
- data/test/unit/models/page_test.rb +290 -224
- data/test/unit/models/sections_test.rb +144 -44
- data/test/unit/models/user_test.rb +28 -18
- metadata +581 -350
- data/app/views/cms/section_nodes/_section_node.html.erb +0 -10
- data/test/unit/models/section_node_test.rb +0 -92
@@ -0,0 +1,94 @@
|
|
1
|
+
class Browsercms315 < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
generate_ancestry_from_section_id
|
4
|
+
update_latest_version_cache
|
5
|
+
|
6
|
+
INDEXES.each do |index|
|
7
|
+
add_index *index
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.down
|
12
|
+
# This migration is not reversible since it removes the original section_id column.
|
13
|
+
end
|
14
|
+
|
15
|
+
# Add some very commonly used indexes to improve the site performance as the # of pages/content grows (i.e. several thousand pages)
|
16
|
+
INDEXES = [
|
17
|
+
[:pages, :deleted],
|
18
|
+
[:pages, :path],
|
19
|
+
[:pages, :version],
|
20
|
+
[:page_versions, :page_id],
|
21
|
+
[:groups, :code],
|
22
|
+
[:groups, :group_type_id],
|
23
|
+
[:group_types, :cms_access],
|
24
|
+
[:group_sections, :section_id],
|
25
|
+
[:group_sections, :group_id],
|
26
|
+
[:users, :expires_at],
|
27
|
+
[:user_group_memberships, :group_id],
|
28
|
+
[:user_group_memberships, :user_id],
|
29
|
+
[:group_permissions, :group_id],
|
30
|
+
[:group_permissions, :permission_id],
|
31
|
+
[:group_permissions, [:group_id, :permission_id]],
|
32
|
+
[:section_nodes, :node_type],
|
33
|
+
[:section_nodes, :ancestry],
|
34
|
+
[:connectors, :page_id],
|
35
|
+
[:connectors, :page_version],
|
36
|
+
[:html_blocks, :deleted],
|
37
|
+
[:html_block_versions, :html_block_id],
|
38
|
+
[:html_block_versions, :version],
|
39
|
+
[:portlet_attributes, :portlet_id],
|
40
|
+
[:portlets, :name],
|
41
|
+
[:sections, :path],
|
42
|
+
[:redirects, :from_path],
|
43
|
+
[:connectors, :connectable_version],
|
44
|
+
[:connectors, :connectable_type],
|
45
|
+
[:content_types, :content_type_group_id],
|
46
|
+
[:content_types, :name],
|
47
|
+
[:file_block_versions, :file_block_id],
|
48
|
+
[:file_block_versions, :version],
|
49
|
+
[:file_blocks, :deleted],
|
50
|
+
[:file_blocks, :type],
|
51
|
+
[:attachment_versions, :attachment_id],
|
52
|
+
[:tasks, :page_id],
|
53
|
+
[:tasks, :completed_at],
|
54
|
+
[:tasks, :assigned_to_id],
|
55
|
+
]
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# v3.1.5 uses Ancestry to manage the parent child relationship between sections and their children.
|
60
|
+
# This converts the data from the old section_id to use the ancestry column.
|
61
|
+
def self.generate_ancestry_from_section_id
|
62
|
+
add_column :section_nodes, :ancestry, :string
|
63
|
+
add_column :section_nodes, :temp_parent_id, :integer
|
64
|
+
|
65
|
+
SectionNode.reset_column_information
|
66
|
+
root_section = Section.root.first
|
67
|
+
SectionNode.create!(:node => root_section) if root_section
|
68
|
+
|
69
|
+
all_nodes_but_root = SectionNode.find(:all, :conditions=>["section_id IS NOT NULL"])
|
70
|
+
all_nodes_but_root.each do |sn|
|
71
|
+
parent_node = SectionNode.find(:first, :conditions => ["node_id = ? and node_type = 'Section'", sn.section_id])
|
72
|
+
sn.temp_parent_id = parent_node.id
|
73
|
+
sn.save!
|
74
|
+
end
|
75
|
+
rename_column :section_nodes, :temp_parent_id, :parent_id # Ancestry works off the 'parent_id' column.
|
76
|
+
|
77
|
+
SectionNode.build_ancestry_from_parent_ids!
|
78
|
+
remove_column :section_nodes, :section_id
|
79
|
+
remove_column :section_nodes, :parent_id
|
80
|
+
SectionNode.reset_column_information
|
81
|
+
end
|
82
|
+
|
83
|
+
# Adds a 'latest_version' pointer to pages and links. Greatly reduces the number of queries the sitemap requires to determine if pages are in draft/published mode
|
84
|
+
def self.update_latest_version_cache
|
85
|
+
add_column :pages, :latest_version, :integer
|
86
|
+
add_column :links, :latest_version, :integer
|
87
|
+
Page.all.each do |p|
|
88
|
+
p.update_latest_version
|
89
|
+
end
|
90
|
+
Link.all.each do |link|
|
91
|
+
link.update_latest_version
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -1,6 +1,13 @@
|
|
1
|
-
|
1
|
+
# This is a new seed data file (new timestamp and name) so migrations will work in order.
|
2
|
+
# It should be able to peacefully coexist next to the previous load_seed_data
|
3
|
+
class LoadSeeds < ActiveRecord::Migration
|
2
4
|
extend Cms::DataLoader
|
3
5
|
def self.up
|
6
|
+
if User.count > 0
|
7
|
+
puts "Database has already been seeded. So skip reloading."
|
8
|
+
return
|
9
|
+
end
|
10
|
+
|
4
11
|
if %w[development test dev local].include?(Rails.env)
|
5
12
|
pwd = "cmsadmin"
|
6
13
|
else
|
data/lib/acts_as_list.rb
CHANGED
@@ -7,7 +7,7 @@ module ActsAsList
|
|
7
7
|
# The class that has this specified needs to have a +position+ column defined as an integer on
|
8
8
|
# the mapped database table.
|
9
9
|
#
|
10
|
-
#
|
10
|
+
# To Do list example:
|
11
11
|
#
|
12
12
|
# class TodoList < ActiveRecord::Base
|
13
13
|
# has_many :todo_items, :order => "position"
|
data/lib/browsercms.rb
CHANGED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Represents any object which exists in a Sitemap.
|
2
|
+
#
|
3
|
+
# Can have parents (using SectionNodes) and children.
|
4
|
+
module Addressable
|
5
|
+
|
6
|
+
# Returns a list of all Addressable objects that are ancestors to this record.
|
7
|
+
# @param [Hash] options
|
8
|
+
# @option [Symbol] :include_self If this object should be included in the Array
|
9
|
+
# @return [Array<Addressable]
|
10
|
+
#
|
11
|
+
def ancestors(options={})
|
12
|
+
ancestor_nodes = node.ancestors
|
13
|
+
ancestors = ancestor_nodes.collect { |node| node.node }
|
14
|
+
ancestors << self if options[:include_self]
|
15
|
+
ancestors
|
16
|
+
end
|
17
|
+
|
18
|
+
def parent
|
19
|
+
@parent if @parent
|
20
|
+
node ? node.section : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def cache_parent(section)
|
24
|
+
@parent = section
|
25
|
+
end
|
26
|
+
|
27
|
+
def parent=(sec)
|
28
|
+
if node
|
29
|
+
node.move_to_end(sec)
|
30
|
+
else
|
31
|
+
build_section_node(:node => self, :section => sec)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Computes the name of the partial used to render this object in the sitemap.
|
36
|
+
def partial_for
|
37
|
+
self.class.name.underscore
|
38
|
+
end
|
39
|
+
|
40
|
+
# Pages/Links/Attachments use their parent to determine access
|
41
|
+
module LeafNode
|
42
|
+
def access_status
|
43
|
+
parent.status
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module NodeAccessors
|
48
|
+
def node
|
49
|
+
section_node
|
50
|
+
end
|
51
|
+
|
52
|
+
def node=(n)
|
53
|
+
self.section_node = n
|
54
|
+
end
|
55
|
+
end
|
56
|
+
# These exist for backwards compatibility to avoid having to change tests.
|
57
|
+
# I want to get rid of these in favor of parent and parent_id
|
58
|
+
module DeprecatedPageAccessors
|
59
|
+
include LeafNode
|
60
|
+
include NodeAccessors
|
61
|
+
|
62
|
+
def build_node(opts)
|
63
|
+
build_section_node(opts)
|
64
|
+
end
|
65
|
+
|
66
|
+
def section_id
|
67
|
+
section ? section.id : nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def section_id=(sec_id)
|
71
|
+
self.section = Section.find(sec_id)
|
72
|
+
end
|
73
|
+
|
74
|
+
def section
|
75
|
+
parent
|
76
|
+
end
|
77
|
+
|
78
|
+
def section=(sec)
|
79
|
+
self.parent = sec
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -1,34 +1,36 @@
|
|
1
1
|
module Cms
|
2
2
|
module Behaviors
|
3
3
|
module Attaching
|
4
|
-
SANITIZATION_REGEXES = [
|
5
|
-
|
4
|
+
SANITIZATION_REGEXES = [[/\s/, '_'], [/[&+()]/, '-'], [/[=?!'"{}\[\]#<>%]/, '']]
|
5
|
+
|
6
6
|
def self.included(model_class)
|
7
7
|
model_class.extend(MacroMethods)
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
|
+
module MacroMethods
|
10
11
|
def belongs_to_attachment?
|
11
12
|
!!@belongs_to_attachment
|
12
13
|
end
|
14
|
+
|
13
15
|
def belongs_to_attachment(options={})
|
14
16
|
@belongs_to_attachment = true
|
15
17
|
include InstanceMethods
|
16
|
-
before_validation :process_attachment
|
18
|
+
before_validation :process_attachment
|
17
19
|
before_save :update_attachment_if_changed
|
18
20
|
after_save :clear_attachment_ivars
|
19
|
-
belongs_to :attachment, :dependent => :destroy
|
20
|
-
|
21
|
+
belongs_to :attachment, :dependent => :destroy
|
22
|
+
|
21
23
|
validates_each :attachment_file do |record, attr, value|
|
22
24
|
if record.attachment && !record.attachment.valid?
|
23
25
|
record.attachment.errors.each do |err_field, err_value|
|
24
26
|
if err_field.to_sym == :file_path
|
25
27
|
record.errors.add(:attachment_file_path, err_value)
|
26
|
-
else
|
28
|
+
else
|
27
29
|
record.errors.add(:attachment_file, err_value)
|
28
30
|
end
|
29
|
-
end
|
31
|
+
end
|
30
32
|
end
|
31
|
-
end
|
33
|
+
end
|
32
34
|
end
|
33
35
|
end
|
34
36
|
module InstanceMethods
|
@@ -92,20 +94,30 @@ module Cms
|
|
92
94
|
unless attachment_section_id.blank?
|
93
95
|
errors.add(:attachment_file, "You must upload a file")
|
94
96
|
return false
|
95
|
-
end
|
97
|
+
end
|
96
98
|
else
|
97
|
-
build_attachment if attachment.nil?
|
98
|
-
attachment.temp_file = attachment_file
|
99
|
-
|
99
|
+
build_attachment if attachment.nil?
|
100
|
+
attachment.temp_file = attachment_file
|
101
|
+
handle_setting_attachment_path
|
100
102
|
if attachment.file_path.blank?
|
101
103
|
errors.add(:attachment_file_path, "File Name is required for attachment")
|
102
104
|
return false
|
103
105
|
end
|
104
|
-
|
105
|
-
|
106
|
+
handle_setting_attachment_section
|
107
|
+
unless attachment.section
|
106
108
|
errors.add(:attachment_file, "Section is required for attachment")
|
107
109
|
return false
|
108
110
|
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Define at :set_attachment_path if you would like to override the way file_path is set
|
116
|
+
def handle_setting_attachment_path
|
117
|
+
if self.respond_to? :set_attachment_path
|
118
|
+
set_attachment_path
|
119
|
+
else
|
120
|
+
use_default_attachment_path
|
109
121
|
end
|
110
122
|
end
|
111
123
|
|
@@ -113,18 +125,26 @@ module Cms
|
|
113
125
|
@attachment_file = nil
|
114
126
|
@attachment_file_path = nil
|
115
127
|
@attachment_section_id = nil
|
116
|
-
@attachment_section = nil
|
128
|
+
@attachment_section = nil
|
129
|
+
end
|
130
|
+
|
131
|
+
# Implement a :set_attachment_section method if you would like to override the way the section is set
|
132
|
+
def handle_setting_attachment_section
|
133
|
+
if self.respond_to? :set_attachment_section
|
134
|
+
set_attachment_section
|
135
|
+
else
|
136
|
+
use_default_attachment_section
|
137
|
+
end
|
117
138
|
end
|
118
139
|
|
119
|
-
#
|
120
|
-
def
|
140
|
+
# Default behavior for assigning a section, if a block does not define its own.
|
141
|
+
def use_default_attachment_section
|
121
142
|
if !attachment_file.blank?
|
122
143
|
attachment.section = Section.root.first
|
123
144
|
end
|
124
145
|
end
|
125
146
|
|
126
|
-
|
127
|
-
def set_attachment_file_path
|
147
|
+
def use_default_attachment_path
|
128
148
|
if !attachment_file.blank?
|
129
149
|
attachment.file_path = "/attachments/#{File.basename(attachment_file.original_filename).to_s.downcase}"
|
130
150
|
end
|
@@ -168,16 +188,16 @@ module Cms
|
|
168
188
|
(published? && live_version?) ? attachment_file_path : "/cms/attachments/#{attachment_id}?version=#{attachment_version}"
|
169
189
|
else
|
170
190
|
nil
|
171
|
-
end
|
191
|
+
end
|
172
192
|
end
|
173
|
-
|
193
|
+
|
174
194
|
# Forces this record to be changed, even if nothing has changed
|
175
195
|
# This is necessary if just the section.id has changed, for example
|
176
196
|
def dirty!
|
177
197
|
# Seems like a hack, is there a better way?
|
178
198
|
self.updated_at = Time.now
|
179
|
-
end
|
180
|
-
|
199
|
+
end
|
200
|
+
|
181
201
|
end
|
182
202
|
end
|
183
203
|
end
|
@@ -106,15 +106,24 @@ module Cms
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def status
|
109
|
-
|
110
|
-
|
109
|
+
return @status if @status
|
110
|
+
@status = live? ? :published : :draft
|
111
|
+
end
|
111
112
|
|
112
113
|
def status_name
|
113
114
|
status.to_s.titleize
|
114
115
|
end
|
115
116
|
|
116
117
|
def live?
|
117
|
-
self.class.versioned?
|
118
|
+
if self.class.versioned?
|
119
|
+
if (respond_to?(:latest_version) && self.latest_version)
|
120
|
+
version == latest_version && published?
|
121
|
+
else
|
122
|
+
live_version.version == draft.version && published?
|
123
|
+
end
|
124
|
+
else
|
125
|
+
true
|
126
|
+
end
|
118
127
|
end
|
119
128
|
|
120
129
|
end
|
@@ -4,10 +4,12 @@ module Cms
|
|
4
4
|
def self.included(model_class)
|
5
5
|
model_class.extend(MacroMethods)
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
|
+
module MacroMethods
|
8
9
|
def versioned?
|
9
10
|
!!@is_versioned
|
10
11
|
end
|
12
|
+
|
11
13
|
def is_versioned(options={})
|
12
14
|
@is_versioned = true
|
13
15
|
|
@@ -17,25 +19,29 @@ module Cms
|
|
17
19
|
extend ClassMethods
|
18
20
|
include InstanceMethods
|
19
21
|
|
20
|
-
has_many :versions, :class_name
|
22
|
+
has_many :versions, :class_name => version_class_name, :foreign_key => version_foreign_key
|
21
23
|
|
22
24
|
before_validation_on_create :initialize_version
|
23
25
|
|
24
26
|
attr_accessor :revert_to_version
|
25
27
|
|
26
28
|
#Define the version class
|
27
|
-
const_set("Version", Class.new(ActiveRecord::Base)).class_eval do
|
28
|
-
class << self;
|
29
|
+
const_set("Version", Class.new(ActiveRecord::Base)).class_eval do
|
30
|
+
class << self;
|
31
|
+
attr_accessor :versioned_class
|
32
|
+
end
|
29
33
|
|
30
34
|
def versioned_class
|
31
35
|
self.class.versioned_class
|
32
36
|
end
|
37
|
+
|
33
38
|
def versioned_object_id
|
34
39
|
send("#{versioned_class.name.underscore}_id")
|
35
40
|
end
|
41
|
+
|
36
42
|
def versioned_object
|
37
43
|
send(versioned_class.name.underscore.to_sym)
|
38
|
-
end
|
44
|
+
end
|
39
45
|
end unless self.const_defined?("Version")
|
40
46
|
|
41
47
|
version_class.versioned_class = self
|
@@ -46,33 +52,47 @@ module Cms
|
|
46
52
|
|
47
53
|
end
|
48
54
|
end
|
49
|
-
module ClassMethods
|
55
|
+
module ClassMethods
|
50
56
|
def version_class
|
51
57
|
const_get "Version"
|
52
58
|
end
|
53
59
|
|
54
60
|
def version_class_name
|
55
61
|
"#{name}::Version"
|
56
|
-
end
|
62
|
+
end
|
57
63
|
|
58
64
|
def version_foreign_key
|
59
65
|
@version_foreign_key
|
60
66
|
end
|
61
67
|
|
62
|
-
def version_table_name
|
68
|
+
def version_table_name
|
63
69
|
@version_table_name
|
64
70
|
end
|
65
71
|
|
66
72
|
def versioned_columns
|
67
|
-
@versioned_columns ||= (version_class.new.attributes.keys -
|
68
|
-
|
69
|
-
end
|
73
|
+
@versioned_columns ||= (version_class.new.attributes.keys -
|
74
|
+
(%w[id lock_version position version_comment created_at updated_at created_by_id updated_by_id type] + [version_foreign_key]))
|
75
|
+
end
|
70
76
|
end
|
71
77
|
module InstanceMethods
|
72
78
|
def initialize_version
|
73
79
|
self.version = 1
|
74
80
|
end
|
75
81
|
|
82
|
+
def after_save
|
83
|
+
update_latest_version
|
84
|
+
end
|
85
|
+
|
86
|
+
# Used in migrations and as a callback.
|
87
|
+
def update_latest_version
|
88
|
+
#Rails 3 could use update_column here instead
|
89
|
+
if respond_to? :latest_version
|
90
|
+
sql = "UPDATE #{self.class.table_name} SET latest_version = #{draft.version} where id = #{self.id}"
|
91
|
+
connection.execute sql
|
92
|
+
self.latest_version = draft.version # So we don't need to #reload this object. Probably marks it as dirty though, which could have weird side effects.
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
76
96
|
def build_new_version
|
77
97
|
# First get the values from the draft
|
78
98
|
attrs = draft_attributes
|
@@ -83,7 +103,7 @@ module Cms
|
|
83
103
|
end
|
84
104
|
|
85
105
|
attrs[:version_comment] = @version_comment || default_version_comment
|
86
|
-
@version_comment = nil
|
106
|
+
@version_comment = nil
|
87
107
|
new_version = versions.build(attrs)
|
88
108
|
new_version.version = new_record? ? 1 : (draft.version.to_i + 1)
|
89
109
|
after_build_new_version(new_version) if respond_to?(:after_build_new_version)
|
@@ -94,8 +114,8 @@ module Cms
|
|
94
114
|
# When there is no draft, we'll just copy the attibutes from this object
|
95
115
|
# Otherwise we need to use the draft
|
96
116
|
d = new_record? ? self : draft
|
97
|
-
self.class.versioned_columns.inject({}){|attrs, col| attrs[col] = d.send(col); attrs }
|
98
|
-
end
|
117
|
+
self.class.versioned_columns.inject({}) { |attrs, col| attrs[col] = d.send(col); attrs }
|
118
|
+
end
|
99
119
|
|
100
120
|
def default_version_comment
|
101
121
|
if new_record?
|
@@ -108,22 +128,22 @@ module Cms
|
|
108
128
|
def save(perform_validations=true)
|
109
129
|
transaction do
|
110
130
|
#logger.info "..... Calling valid?"
|
111
|
-
return false unless !perform_validations || valid?
|
112
|
-
|
131
|
+
return false unless !perform_validations || valid?
|
132
|
+
|
113
133
|
if different_from_last_draft?
|
114
134
|
#logger.info "..... Changes => #{changes.inspect}"
|
115
135
|
else
|
116
136
|
#logger.info "..... No Changes"
|
117
137
|
return true
|
118
138
|
end
|
119
|
-
|
139
|
+
|
120
140
|
#logger.info "..... Calling before_save"
|
121
141
|
return false if callback(:before_save) == false
|
122
142
|
|
123
143
|
if new_record?
|
124
144
|
#logger.info "..... Calling before_create"
|
125
145
|
return false if callback(:before_create) == false
|
126
|
-
else
|
146
|
+
else
|
127
147
|
#logger.info "..... Calling before_update"
|
128
148
|
return false if callback(:before_update) == false
|
129
149
|
end
|
@@ -140,12 +160,12 @@ module Cms
|
|
140
160
|
#logger.info "..... Calling after_save"
|
141
161
|
callback(:after_save)
|
142
162
|
end
|
143
|
-
|
163
|
+
|
144
164
|
if @publish_on_save
|
145
165
|
publish
|
146
166
|
@publish_on_save = nil
|
147
|
-
end
|
148
|
-
changed_attributes.clear
|
167
|
+
end
|
168
|
+
changed_attributes.clear
|
149
169
|
end
|
150
170
|
result
|
151
171
|
elsif new_version
|
@@ -156,11 +176,11 @@ module Cms
|
|
156
176
|
#logger.info "..... Calling after_update"
|
157
177
|
callback(:after_save)
|
158
178
|
end
|
159
|
-
|
179
|
+
|
160
180
|
if @publish_on_save
|
161
181
|
publish
|
162
182
|
@publish_on_save = nil
|
163
|
-
end
|
183
|
+
end
|
164
184
|
changed_attributes.clear
|
165
185
|
end
|
166
186
|
result
|
@@ -174,13 +194,13 @@ module Cms
|
|
174
194
|
end
|
175
195
|
|
176
196
|
def draft
|
177
|
-
versions.first(:order => "version desc")
|
197
|
+
versions.first(:order => "version desc")
|
178
198
|
end
|
179
|
-
|
199
|
+
|
180
200
|
def draft_version?
|
181
201
|
version == draft.version
|
182
202
|
end
|
183
|
-
|
203
|
+
|
184
204
|
def live_version
|
185
205
|
find_version(self.class.find(id).version)
|
186
206
|
end
|
@@ -192,38 +212,19 @@ module Cms
|
|
192
212
|
def current_version
|
193
213
|
find_version(self.version)
|
194
214
|
end
|
195
|
-
|
215
|
+
|
196
216
|
def find_version(number)
|
197
|
-
versions.first(:conditions => {
|
217
|
+
versions.first(:conditions => {:version => number})
|
198
218
|
end
|
199
219
|
|
200
220
|
def as_of_draft_version
|
201
|
-
|
221
|
+
build_object_from_version(draft)
|
202
222
|
end
|
203
223
|
|
204
224
|
def as_of_version(version)
|
205
225
|
v = find_version(version)
|
206
226
|
raise ActiveRecord::RecordNotFound.new("version #{version.inspect} does not exist for <#{self.class}:#{id}>") unless v
|
207
|
-
|
208
|
-
|
209
|
-
(self.class.versioned_columns + [:version, :created_at, :created_by_id, :updated_at, :updated_by_id]).each do |a|
|
210
|
-
obj.send("#{a}=", v.send(a))
|
211
|
-
end
|
212
|
-
obj.id = id
|
213
|
-
obj.lock_version = lock_version
|
214
|
-
|
215
|
-
# Need to do this so associations can be loaded
|
216
|
-
obj.instance_variable_set("@new_record", false)
|
217
|
-
|
218
|
-
# Callback to allow us to load other data when an older version is loaded
|
219
|
-
obj.after_as_of_version if obj.respond_to?(:after_as_of_version)
|
220
|
-
|
221
|
-
# Last but not least, clear the changed attributes
|
222
|
-
if changed_attrs = obj.send(:changed_attributes)
|
223
|
-
changed_attrs.clear
|
224
|
-
end
|
225
|
-
|
226
|
-
obj
|
227
|
+
build_object_from_version(v)
|
227
228
|
end
|
228
229
|
|
229
230
|
def revert
|
@@ -237,15 +238,15 @@ module Cms
|
|
237
238
|
raise "Could not find version #{version}" unless revert_to_version
|
238
239
|
(self.class.versioned_columns - ["version"]).each do |a|
|
239
240
|
send("#{a}=", revert_to_version.send(a))
|
240
|
-
end
|
241
|
+
end
|
241
242
|
self.version_comment = "Reverted to version #{version}"
|
242
|
-
self
|
243
|
-
end
|
243
|
+
self
|
244
|
+
end
|
244
245
|
|
245
246
|
def revert_to(version)
|
246
247
|
revert_to_without_save(version)
|
247
248
|
save
|
248
|
-
end
|
249
|
+
end
|
249
250
|
|
250
251
|
def version_comment
|
251
252
|
@version_comment
|
@@ -266,6 +267,35 @@ module Cms
|
|
266
267
|
return false
|
267
268
|
end
|
268
269
|
|
270
|
+
private
|
271
|
+
|
272
|
+
# Given a ::Version object of a given type, create an original object from its attributes.
|
273
|
+
#
|
274
|
+
# @param [Class#name::Version] version (i.e. HtmlBlock::Version)
|
275
|
+
# @return [Class#name] i.e. HtmlBlock
|
276
|
+
def build_object_from_version(version_of_object)
|
277
|
+
obj = self.class.new
|
278
|
+
|
279
|
+
(self.class.versioned_columns + [:version, :created_at, :created_by_id, :updated_at, :updated_by_id]).each do |a|
|
280
|
+
obj.send("#{a}=", version_of_object.send(a))
|
281
|
+
end
|
282
|
+
obj.id = id
|
283
|
+
obj.lock_version = lock_version
|
284
|
+
|
285
|
+
# Need to do this so associations can be loaded
|
286
|
+
obj.instance_variable_set("@new_record", false)
|
287
|
+
|
288
|
+
# Callback to allow us to load other data when an older version is loaded
|
289
|
+
obj.after_as_of_version if obj.respond_to?(:after_as_of_version)
|
290
|
+
|
291
|
+
# Last but not least, clear the changed attributes
|
292
|
+
if changed_attrs = obj.send(:changed_attributes)
|
293
|
+
changed_attrs.clear
|
294
|
+
end
|
295
|
+
|
296
|
+
obj
|
297
|
+
end
|
298
|
+
|
269
299
|
end
|
270
300
|
end
|
271
301
|
|