pageflow 15.0.0.beta2 → 15.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pageflow might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +105 -2
- data/README.md +5 -1
- data/app/assets/javascripts/pageflow/dist/react-client.js +176 -56
- data/app/assets/javascripts/pageflow/dist/react-server.js +176 -56
- data/app/assets/javascripts/pageflow/editor/api/widget_type.js +0 -1
- data/app/assets/javascripts/pageflow/editor/views/widget_types/media_loading_spinner.js +18 -0
- data/app/assets/javascripts/pageflow/editor/views/widget_types/title_loading_spinner.js +3 -0
- data/app/assets/javascripts/pageflow/ui/views/tabs_view.js +2 -1
- data/app/assets/stylesheets/pageflow/themes/default/base.scss +1 -1
- data/app/assets/stylesheets/pageflow/themes/default/loading_spinner.scss +37 -1
- data/app/assets/stylesheets/pageflow/themes/default/loading_spinner/media.scss +56 -0
- data/app/assets/stylesheets/pageflow/themes/default/loading_spinner/title.scss +10 -50
- data/app/assets/stylesheets/pageflow/themes/default/logo/variant/watermark.scss +10 -1
- data/app/assets/stylesheets/pageflow/themes/default/player_controls/classic/info_box.scss +5 -0
- data/app/assets/stylesheets/pageflow/themes/default/player_controls/slim/info_box.scss +4 -0
- data/app/controllers/pageflow/editor/files_controller.rb +6 -3
- data/app/helpers/pageflow/meta_tags_helper.rb +3 -3
- data/app/helpers/pageflow/social_share_links_helper.rb +1 -1
- data/app/jobs/pageflow/entry_export_import/upload_and_publish_file_job.rb +41 -0
- data/app/models/concerns/pageflow/reusable_file.rb +6 -0
- data/app/models/concerns/pageflow/uploadable_file.rb +5 -0
- data/app/models/pageflow/draft_entry.rb +26 -2
- data/app/models/pageflow/membership.rb +3 -4
- data/app/models/pageflow/revision.rb +20 -4
- data/app/models/pageflow/widget.rb +12 -6
- data/config/locales/de.yml +24 -0
- data/config/locales/en.yml +24 -0
- data/db/migrate/20140418225525_setup_schema.rb +18 -18
- data/db/migrate/20190820152900_drop_accounts_themes.rb +8 -0
- data/lib/pageflow/built_in_page_type.rb +3 -0
- data/lib/pageflow/built_in_widget_type.rb +7 -0
- data/lib/pageflow/built_in_widget_types_plugin.rb +6 -0
- data/lib/pageflow/entry_export_import.rb +43 -0
- data/lib/pageflow/entry_export_import/attachment_files.rb +65 -0
- data/lib/pageflow/entry_export_import/entry_serialization.rb +68 -0
- data/lib/pageflow/entry_export_import/file_mappings.rb +32 -0
- data/lib/pageflow/entry_export_import/page_type_versions.rb +29 -0
- data/lib/pageflow/entry_export_import/revision_serialization.rb +58 -0
- data/lib/pageflow/entry_export_import/revision_serialization/import.rb +158 -0
- data/lib/pageflow/entry_export_import/zip_archive.rb +36 -0
- data/lib/pageflow/file_type.rb +17 -4
- data/lib/pageflow/page_type.rb +20 -0
- data/lib/pageflow/page_types.rb +8 -0
- data/lib/pageflow/react/page_type.rb +10 -0
- data/lib/pageflow/version.rb +1 -1
- data/lib/pageflow/widget_types.rb +9 -1
- data/lib/tasks/entry_export_import.rake +27 -0
- data/spec/factories/revisions.rb +6 -0
- data/spec/factories/test_multi_attachment_files.rb +16 -0
- data/spec/factories/test_revision_components.rb +7 -0
- metadata +46 -3
@@ -0,0 +1,32 @@
|
|
1
|
+
module Pageflow
|
2
|
+
module EntryExportImport
|
3
|
+
class FileMappings
|
4
|
+
def initialize
|
5
|
+
@file_mappings = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def find_or_store(exported_file_id, file_type)
|
9
|
+
imported_file_id = @file_mappings.dig(file_type.model.name, exported_file_id)
|
10
|
+
|
11
|
+
if imported_file_id.present?
|
12
|
+
imported_file_id
|
13
|
+
else
|
14
|
+
file = yield
|
15
|
+
|
16
|
+
@file_mappings[file_type.model.name] ||= {}
|
17
|
+
@file_mappings[file_type.model.name][exported_file_id] = file.id
|
18
|
+
|
19
|
+
file.id
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def imported_id_for(model_name, exported_id)
|
24
|
+
@file_mappings.fetch(model_name)[exported_id]
|
25
|
+
end
|
26
|
+
|
27
|
+
def exported_id_for(model_name, imported_id)
|
28
|
+
@file_mappings.fetch(model_name).invert[imported_id]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Pageflow
|
2
|
+
module EntryExportImport
|
3
|
+
# @api private
|
4
|
+
module PageTypeVersions
|
5
|
+
extend self
|
6
|
+
|
7
|
+
class IncompatibleVersionsError < StandardError; end
|
8
|
+
|
9
|
+
def dump
|
10
|
+
Pageflow.config.page_types.each_with_object({}) do |page_type, version_requirements|
|
11
|
+
version_requirements[page_type.name] = page_type.export_version
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def verify_compatibility!(data)
|
16
|
+
data.each do |page_type_name, export_version|
|
17
|
+
page_type = Pageflow.config.page_types.find_by_name!(page_type_name)
|
18
|
+
requirement = Gem::Requirement.new(page_type.import_version_requirement)
|
19
|
+
|
20
|
+
next if requirement.satisfied_by?(Gem::Version.new(export_version))
|
21
|
+
|
22
|
+
raise(IncompatibleVersionsError,
|
23
|
+
"Export version #{export_version} of page type #{page_type_name}. " \
|
24
|
+
"does not match #{requirement}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Pageflow
|
2
|
+
module EntryExportImport
|
3
|
+
# Turn revision into JSON compatible data structure.
|
4
|
+
module RevisionSerialization
|
5
|
+
extend self
|
6
|
+
|
7
|
+
SERIALIZE_OPTIONS = {
|
8
|
+
except: [:entry_id, :creator_id],
|
9
|
+
include: {
|
10
|
+
widgets: {
|
11
|
+
except: [:subject_id, :subject_type]
|
12
|
+
},
|
13
|
+
storylines: {
|
14
|
+
except: :revision_id,
|
15
|
+
include: {
|
16
|
+
chapters: {
|
17
|
+
except: :storyline_id,
|
18
|
+
include: {
|
19
|
+
pages: {
|
20
|
+
except: :chapter_id
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
25
|
+
},
|
26
|
+
file_usages: {
|
27
|
+
except: [:revision_id],
|
28
|
+
include: {
|
29
|
+
file: {
|
30
|
+
except: [:entry_id, :job_id]
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
def dump(revision)
|
38
|
+
revision
|
39
|
+
.as_json(SERIALIZE_OPTIONS)
|
40
|
+
.merge('components' => serialize_revision_components(revision))
|
41
|
+
end
|
42
|
+
|
43
|
+
def import(data, options)
|
44
|
+
Import.new(options).perform(data)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def serialize_revision_components(revision)
|
50
|
+
revision.find_revision_components.map do |revision_component|
|
51
|
+
revision_component
|
52
|
+
.attributes.except('revision_id')
|
53
|
+
.merge('class_name' => revision_component.class.name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module Pageflow
|
2
|
+
module EntryExportImport
|
3
|
+
module RevisionSerialization
|
4
|
+
# Turn revision into JSON compatible data structure.
|
5
|
+
class Import
|
6
|
+
DEFAULT_REMOVAL_COLUMNS = %w[id updated_at].freeze
|
7
|
+
COMMON_FILE_COLUMNS = %w[entry_id rights created_at uploader_id
|
8
|
+
confirmed_by_id parent_file_id parent_file_model_type].freeze
|
9
|
+
|
10
|
+
def initialize(entry:, creator:, file_mappings: FileMappings.new)
|
11
|
+
@entry = entry
|
12
|
+
@creator = creator
|
13
|
+
@file_mappings = file_mappings
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform(data)
|
17
|
+
Revision.transaction do
|
18
|
+
revision = create_revision(data)
|
19
|
+
|
20
|
+
create_storylines_chapters_and_pages(revision, data['storylines'])
|
21
|
+
create_widgets(revision, data['widgets'])
|
22
|
+
create_revision_components(revision, data['components'])
|
23
|
+
create_files(revision, data['file_usages'])
|
24
|
+
|
25
|
+
revision
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :entry, :creator, :file_mappings
|
32
|
+
|
33
|
+
def create_revision(data)
|
34
|
+
revision_attributes = data.except('storylines',
|
35
|
+
'file_usages',
|
36
|
+
'widgets',
|
37
|
+
'components',
|
38
|
+
*DEFAULT_REMOVAL_COLUMNS)
|
39
|
+
|
40
|
+
unless Pageflow.config_for(entry).themes.names.include?(revision_attributes['theme_name'])
|
41
|
+
revision_attributes['theme_name'] = 'default'
|
42
|
+
end
|
43
|
+
|
44
|
+
entry.revisions.create!(revision_attributes.merge(creator: creator)) do |revision|
|
45
|
+
revision.published_until = Time.now if revision.published?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_storylines_chapters_and_pages(revision, storylines_data)
|
50
|
+
storylines_data.each do |storyline_data|
|
51
|
+
chapters_data = storyline_data.delete('chapters')
|
52
|
+
storyline = revision.storylines.create!(storyline_data.except(*DEFAULT_REMOVAL_COLUMNS))
|
53
|
+
|
54
|
+
create_chapters_and_pages(storyline, chapters_data)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_chapters_and_pages(storyline, chapters_data)
|
59
|
+
chapters_data.each do |chapter_data|
|
60
|
+
pages_data = chapter_data.delete('pages')
|
61
|
+
chapter = storyline.chapters.create!(chapter_data.except(*DEFAULT_REMOVAL_COLUMNS))
|
62
|
+
|
63
|
+
create_pages(chapter, pages_data)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_pages(chapter, pages_data)
|
68
|
+
pages_data.each do |page_data|
|
69
|
+
chapter.pages.create!(page_data.except(*DEFAULT_REMOVAL_COLUMNS))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_widgets(revision, widgets_data)
|
74
|
+
widgets_data.each do |widget_data|
|
75
|
+
revision.widgets.create!(widget_data.except(*DEFAULT_REMOVAL_COLUMNS))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_revision_components(revision, revision_components_data)
|
80
|
+
revision_components_data.each do |component_data|
|
81
|
+
component_model = component_data.delete('class_name').constantize
|
82
|
+
component_data['revision_id'] = revision.id
|
83
|
+
component_model.create!(component_data.except(*DEFAULT_REMOVAL_COLUMNS))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_files(revision, file_usages_data)
|
88
|
+
Pageflow.config.file_types.each do |file_type|
|
89
|
+
filter_by_file_type(file_usages_data, file_type).each do |file_type_usage_data|
|
90
|
+
file_data = file_type_usage_data.delete('file')
|
91
|
+
|
92
|
+
file_type_usage_data['file_id'] =
|
93
|
+
file_mappings.find_or_store(file_data['id'], file_type) do
|
94
|
+
create_file(file_data, file_type)
|
95
|
+
end
|
96
|
+
|
97
|
+
revision.file_usages.create!(file_type_usage_data.except(*DEFAULT_REMOVAL_COLUMNS))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def filter_by_file_type(file_usages_data, file_type)
|
103
|
+
file_usages_data.select do |file_usage|
|
104
|
+
file_usage['file_type'].eql?(file_type.type_name)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def create_file(file_data, file_type)
|
109
|
+
rewrite_entry_reference(file_data)
|
110
|
+
rewrite_user_references(file_data)
|
111
|
+
rewrite_parent_file_reference(file_data)
|
112
|
+
custom_file_attribute_names = rewrite_custom_attributes(file_data, file_type)
|
113
|
+
|
114
|
+
file_type.model.create!(file_data.slice(*COMMON_FILE_COLUMNS,
|
115
|
+
*custom_file_attribute_names)) do |file|
|
116
|
+
assign_attachments_attributes(file, file_data)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def rewrite_entry_reference(file_data)
|
121
|
+
file_data['entry_id'] = entry.id
|
122
|
+
end
|
123
|
+
|
124
|
+
def rewrite_user_references(file_data)
|
125
|
+
file_data['uploader_id'] = creator.id if file_data['uploader_id'].present?
|
126
|
+
file_data['confirmed_by_id'] = creator.id if file_data['confirmed_by_id'].present?
|
127
|
+
end
|
128
|
+
|
129
|
+
def rewrite_parent_file_reference(file_data)
|
130
|
+
rewrite_foreign_key(file_data, 'parent_file_id', file_data['parent_file_model_type'])
|
131
|
+
end
|
132
|
+
|
133
|
+
def rewrite_custom_attributes(file_data, file_type)
|
134
|
+
file_type.custom_attributes.stringify_keys.map do |attribute_name, options|
|
135
|
+
rewrite_foreign_key(file_data, attribute_name, options[:model]) if options[:model]
|
136
|
+
attribute_name
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def rewrite_foreign_key(attributes, attribute_name, model_name)
|
141
|
+
exported_id = attributes[attribute_name]
|
142
|
+
return unless exported_id
|
143
|
+
|
144
|
+
attributes[attribute_name] = file_mappings.imported_id_for(model_name, exported_id)
|
145
|
+
end
|
146
|
+
|
147
|
+
def assign_attachments_attributes(file, file_data)
|
148
|
+
file.attachments_for_export.each do |attachment|
|
149
|
+
file.assign_attributes(file_data.slice("#{attachment.name}_file_name",
|
150
|
+
"#{attachment.name}_content_type",
|
151
|
+
"#{attachment.name}_file_size",
|
152
|
+
"#{attachment.name}_updated_at"))
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'zip'
|
2
|
+
|
3
|
+
module Pageflow
|
4
|
+
module EntryExportImport
|
5
|
+
# Read and write files from and to zip archives
|
6
|
+
class ZipArchive
|
7
|
+
def initialize(file_name)
|
8
|
+
@file = Zip::File.open(file_name, Zip::File::CREATE)
|
9
|
+
end
|
10
|
+
|
11
|
+
def include?(path)
|
12
|
+
@file.find_entry(path).present?
|
13
|
+
end
|
14
|
+
|
15
|
+
def glob(pattern)
|
16
|
+
@file.glob(pattern).map(&:name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def add(path, stream)
|
20
|
+
@file.get_output_stream(path) { |f| IO.copy_stream(stream, f) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def extract_to_tempfile(path)
|
24
|
+
Tempfile.open do |f|
|
25
|
+
IO.copy_stream(@file.get_input_stream(path), f)
|
26
|
+
f.rewind
|
27
|
+
yield f
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
@file.close
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/pageflow/file_type.rb
CHANGED
@@ -51,8 +51,7 @@ module Pageflow
|
|
51
51
|
# @return [#call]
|
52
52
|
attr_reader :url_templates
|
53
53
|
|
54
|
-
#
|
55
|
-
# @return {Array<Symbol>}
|
54
|
+
# @api private
|
56
55
|
attr_reader :custom_attributes
|
57
56
|
|
58
57
|
# Create file type to be returned in {PageType#file_types}.
|
@@ -93,8 +92,8 @@ module Pageflow
|
|
93
92
|
@top_level_type = options.fetch(:top_level_type, false)
|
94
93
|
@css_background_image_urls = options[:css_background_image_urls]
|
95
94
|
@css_background_image_class_prefix = options[:css_background_image_class_prefix]
|
96
|
-
@url_templates = options.fetch(:url_templates, ->
|
97
|
-
@custom_attributes = options
|
95
|
+
@url_templates = options.fetch(:url_templates, -> { {} })
|
96
|
+
@custom_attributes = convert_custom_attributes_option(options[:custom_attributes])
|
98
97
|
end
|
99
98
|
|
100
99
|
# ActiveRecord model that represents the files of this type.
|
@@ -140,5 +139,19 @@ module Pageflow
|
|
140
139
|
def i18n_key
|
141
140
|
model.model_name.i18n_key
|
142
141
|
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def convert_custom_attributes_option(custom_attributes)
|
146
|
+
if custom_attributes.is_a?(Array)
|
147
|
+
custom_attributes.each_with_object({}) do |attribute_name, result|
|
148
|
+
result[attribute_name] = {permitted_create_param: true}
|
149
|
+
end
|
150
|
+
elsif custom_attributes.is_a?(Hash)
|
151
|
+
custom_attributes
|
152
|
+
else
|
153
|
+
{}
|
154
|
+
end
|
155
|
+
end
|
143
156
|
end
|
144
157
|
end
|
data/lib/pageflow/page_type.rb
CHANGED
@@ -94,6 +94,26 @@ module Pageflow
|
|
94
94
|
[]
|
95
95
|
end
|
96
96
|
|
97
|
+
# Current plugin version
|
98
|
+
def export_version
|
99
|
+
"Pageflow::#{name.camelize}::VERSION".constantize
|
100
|
+
rescue NameError
|
101
|
+
begin
|
102
|
+
"#{name.camelize}::VERSION".constantize
|
103
|
+
rescue NameError
|
104
|
+
raise "PageType #{name} needs to define export_version."
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Gets included in JSON file during export of an entry.
|
109
|
+
# The host application needs to compare this version with the version (above)
|
110
|
+
# for each page type before importing.
|
111
|
+
# Defaults to the plugins version but can be overwritten during registry of the page type,
|
112
|
+
# using the same notation as version requirements in the Gemfile.
|
113
|
+
def import_version_requirement
|
114
|
+
export_version
|
115
|
+
end
|
116
|
+
|
97
117
|
# A list of hashes used to determine a thumbnail for a page. Each
|
98
118
|
# hash in the list must contain two keys: `attribute` and
|
99
119
|
# `file_collection`.
|
data/lib/pageflow/page_types.rb
CHANGED
@@ -14,6 +14,8 @@ module Pageflow
|
|
14
14
|
def register(page_type)
|
15
15
|
@page_types << page_type
|
16
16
|
@page_types_by_name[page_type.name] = page_type
|
17
|
+
|
18
|
+
ensure_export_version_implemented(page_type)
|
17
19
|
end
|
18
20
|
|
19
21
|
def find_by_name!(name)
|
@@ -29,5 +31,11 @@ module Pageflow
|
|
29
31
|
def each(&block)
|
30
32
|
@page_types.each(&block)
|
31
33
|
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def ensure_export_version_implemented(page_type)
|
38
|
+
page_type.export_version
|
39
|
+
end
|
32
40
|
end
|
33
41
|
end
|
@@ -8,6 +8,8 @@ module Pageflow
|
|
8
8
|
@thumbnail_candidates = options[:thumbnail_candidates]
|
9
9
|
@translation_key_prefix = options[:translation_key_prefix]
|
10
10
|
@file_types = options.fetch(:file_types, [])
|
11
|
+
@export_version = options[:export_version]
|
12
|
+
@import_version_requirement = options[:import_version_requirement]
|
11
13
|
end
|
12
14
|
|
13
15
|
def template_path
|
@@ -21,6 +23,14 @@ module Pageflow
|
|
21
23
|
def translation_key_prefix
|
22
24
|
@translation_key_prefix || super
|
23
25
|
end
|
26
|
+
|
27
|
+
def export_version
|
28
|
+
@export_version || super
|
29
|
+
end
|
30
|
+
|
31
|
+
def import_version_requirement
|
32
|
+
@import_version_requirement || super
|
33
|
+
end
|
24
34
|
end
|
25
35
|
end
|
26
36
|
end
|
data/lib/pageflow/version.rb
CHANGED
@@ -10,7 +10,6 @@ module Pageflow
|
|
10
10
|
|
11
11
|
def register(widget_type, options = {})
|
12
12
|
@widget_types[widget_type.name] = widget_type
|
13
|
-
|
14
13
|
if options[:default]
|
15
14
|
widget_type.roles.each do |role|
|
16
15
|
defaults_by_role[role] = widget_type
|
@@ -18,9 +17,14 @@ module Pageflow
|
|
18
17
|
end
|
19
18
|
end
|
20
19
|
|
20
|
+
def register_widget_defaults(widget_role, default_configurations)
|
21
|
+
@default_configurations[widget_role] = default_configurations
|
22
|
+
end
|
23
|
+
|
21
24
|
def clear
|
22
25
|
@widget_types = {}
|
23
26
|
@defaults_by_role = {}
|
27
|
+
@default_configurations = {}
|
24
28
|
end
|
25
29
|
|
26
30
|
def each(&block)
|
@@ -37,6 +41,10 @@ module Pageflow
|
|
37
41
|
@widget_types.fetch(name, &block)
|
38
42
|
end
|
39
43
|
|
44
|
+
def default_configuration(name)
|
45
|
+
@default_configurations[name]
|
46
|
+
end
|
47
|
+
|
40
48
|
def find_all_by_role(role)
|
41
49
|
select do |widget_type|
|
42
50
|
widget_type.roles.include?(role)
|