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.

Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +105 -2
  3. data/README.md +5 -1
  4. data/app/assets/javascripts/pageflow/dist/react-client.js +176 -56
  5. data/app/assets/javascripts/pageflow/dist/react-server.js +176 -56
  6. data/app/assets/javascripts/pageflow/editor/api/widget_type.js +0 -1
  7. data/app/assets/javascripts/pageflow/editor/views/widget_types/media_loading_spinner.js +18 -0
  8. data/app/assets/javascripts/pageflow/editor/views/widget_types/title_loading_spinner.js +3 -0
  9. data/app/assets/javascripts/pageflow/ui/views/tabs_view.js +2 -1
  10. data/app/assets/stylesheets/pageflow/themes/default/base.scss +1 -1
  11. data/app/assets/stylesheets/pageflow/themes/default/loading_spinner.scss +37 -1
  12. data/app/assets/stylesheets/pageflow/themes/default/loading_spinner/media.scss +56 -0
  13. data/app/assets/stylesheets/pageflow/themes/default/loading_spinner/title.scss +10 -50
  14. data/app/assets/stylesheets/pageflow/themes/default/logo/variant/watermark.scss +10 -1
  15. data/app/assets/stylesheets/pageflow/themes/default/player_controls/classic/info_box.scss +5 -0
  16. data/app/assets/stylesheets/pageflow/themes/default/player_controls/slim/info_box.scss +4 -0
  17. data/app/controllers/pageflow/editor/files_controller.rb +6 -3
  18. data/app/helpers/pageflow/meta_tags_helper.rb +3 -3
  19. data/app/helpers/pageflow/social_share_links_helper.rb +1 -1
  20. data/app/jobs/pageflow/entry_export_import/upload_and_publish_file_job.rb +41 -0
  21. data/app/models/concerns/pageflow/reusable_file.rb +6 -0
  22. data/app/models/concerns/pageflow/uploadable_file.rb +5 -0
  23. data/app/models/pageflow/draft_entry.rb +26 -2
  24. data/app/models/pageflow/membership.rb +3 -4
  25. data/app/models/pageflow/revision.rb +20 -4
  26. data/app/models/pageflow/widget.rb +12 -6
  27. data/config/locales/de.yml +24 -0
  28. data/config/locales/en.yml +24 -0
  29. data/db/migrate/20140418225525_setup_schema.rb +18 -18
  30. data/db/migrate/20190820152900_drop_accounts_themes.rb +8 -0
  31. data/lib/pageflow/built_in_page_type.rb +3 -0
  32. data/lib/pageflow/built_in_widget_type.rb +7 -0
  33. data/lib/pageflow/built_in_widget_types_plugin.rb +6 -0
  34. data/lib/pageflow/entry_export_import.rb +43 -0
  35. data/lib/pageflow/entry_export_import/attachment_files.rb +65 -0
  36. data/lib/pageflow/entry_export_import/entry_serialization.rb +68 -0
  37. data/lib/pageflow/entry_export_import/file_mappings.rb +32 -0
  38. data/lib/pageflow/entry_export_import/page_type_versions.rb +29 -0
  39. data/lib/pageflow/entry_export_import/revision_serialization.rb +58 -0
  40. data/lib/pageflow/entry_export_import/revision_serialization/import.rb +158 -0
  41. data/lib/pageflow/entry_export_import/zip_archive.rb +36 -0
  42. data/lib/pageflow/file_type.rb +17 -4
  43. data/lib/pageflow/page_type.rb +20 -0
  44. data/lib/pageflow/page_types.rb +8 -0
  45. data/lib/pageflow/react/page_type.rb +10 -0
  46. data/lib/pageflow/version.rb +1 -1
  47. data/lib/pageflow/widget_types.rb +9 -1
  48. data/lib/tasks/entry_export_import.rake +27 -0
  49. data/spec/factories/revisions.rb +6 -0
  50. data/spec/factories/test_multi_attachment_files.rb +16 -0
  51. data/spec/factories/test_revision_components.rb +7 -0
  52. 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
@@ -51,8 +51,7 @@ module Pageflow
51
51
  # @return [#call]
52
52
  attr_reader :url_templates
53
53
 
54
- # Attributes that are custom to this file type.
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.fetch(:custom_attributes, [])
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
@@ -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`.
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Pageflow
2
- VERSION = '15.0.0.beta2'.freeze
2
+ VERSION = '15.0.0.beta3'.freeze
3
3
  end
@@ -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)