alchemy_cms 5.2.4 → 6.0.0.b1
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +6 -14
- data/.gitignore +0 -1
- data/.hound.yml +1 -1
- data/.rubocop.yml +46 -4
- data/CHANGELOG.md +80 -25
- data/Gemfile +4 -2
- data/README.md +5 -2
- data/alchemy_cms.gemspec +78 -65
- data/app/assets/javascripts/alchemy/admin.js +0 -2
- data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +0 -27
- data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +2 -1
- data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +0 -25
- data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +2 -0
- data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +1 -1
- data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +3 -1
- data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +40 -27
- data/app/assets/javascripts/alchemy/templates/node_folder.hbs +1 -1
- data/app/assets/stylesheets/alchemy/admin.scss +1 -1
- data/app/assets/stylesheets/alchemy/archive.scss +4 -4
- data/app/assets/stylesheets/alchemy/buttons.scss +0 -4
- data/app/assets/stylesheets/alchemy/elements.scss +73 -61
- data/app/assets/stylesheets/alchemy/images.scss +8 -0
- data/app/assets/stylesheets/alchemy/node-select.scss +4 -3
- data/app/assets/stylesheets/alchemy/page-select.scss +1 -0
- data/app/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss +6 -6
- data/app/controllers/alchemy/admin/attachments_controller.rb +6 -2
- data/app/controllers/alchemy/admin/base_controller.rb +5 -7
- data/app/controllers/alchemy/admin/elements_controller.rb +58 -34
- data/app/controllers/alchemy/admin/essence_audios_controller.rb +30 -0
- data/app/controllers/alchemy/admin/essence_files_controller.rb +0 -14
- data/app/controllers/alchemy/admin/essence_pictures_controller.rb +8 -79
- data/app/controllers/alchemy/admin/essence_videos_controller.rb +33 -0
- data/app/controllers/alchemy/admin/ingredients_controller.rb +30 -0
- data/app/controllers/alchemy/admin/layoutpages_controller.rb +0 -1
- data/app/controllers/alchemy/admin/pages_controller.rb +6 -13
- data/app/controllers/alchemy/admin/pictures_controller.rb +35 -9
- data/app/controllers/alchemy/api/elements_controller.rb +10 -5
- data/app/controllers/alchemy/api/pages_controller.rb +2 -4
- data/app/controllers/concerns/alchemy/admin/archive_overlay.rb +13 -3
- data/app/controllers/concerns/alchemy/admin/crop_action.rb +26 -0
- data/app/decorators/alchemy/element_editor.rb +23 -1
- data/app/decorators/alchemy/ingredient_editor.rb +154 -0
- data/app/helpers/alchemy/admin/elements_helper.rb +1 -0
- data/app/helpers/alchemy/admin/essences_helper.rb +1 -1
- data/app/helpers/alchemy/admin/ingredients_helper.rb +42 -0
- data/app/helpers/alchemy/elements_block_helper.rb +22 -7
- data/app/helpers/alchemy/elements_helper.rb +12 -5
- data/app/helpers/alchemy/pages_helper.rb +3 -11
- data/app/jobs/alchemy/base_job.rb +11 -0
- data/app/jobs/alchemy/publish_page_job.rb +11 -0
- data/app/models/alchemy/attachment.rb +1 -1
- data/app/models/alchemy/content/factory.rb +23 -27
- data/app/models/alchemy/content.rb +1 -6
- data/app/models/alchemy/element/definitions.rb +29 -27
- data/app/models/alchemy/element/element_contents.rb +131 -122
- data/app/models/alchemy/element/element_essences.rb +100 -98
- data/app/models/alchemy/element/element_ingredients.rb +176 -0
- data/app/models/alchemy/element/presenters.rb +89 -87
- data/app/models/alchemy/element.rb +40 -73
- data/app/models/alchemy/elements_repository.rb +126 -0
- data/app/models/alchemy/essence_audio.rb +12 -0
- data/app/models/alchemy/essence_headline.rb +40 -0
- data/app/models/alchemy/essence_picture.rb +4 -116
- data/app/models/alchemy/essence_richtext.rb +12 -0
- data/app/models/alchemy/essence_video.rb +12 -0
- data/app/models/alchemy/image_cropper_settings.rb +87 -0
- data/app/models/alchemy/ingredient.rb +219 -0
- data/app/models/alchemy/ingredient_validator.rb +97 -0
- data/app/models/alchemy/ingredients/audio.rb +29 -0
- data/app/models/alchemy/ingredients/boolean.rb +21 -0
- data/app/models/alchemy/ingredients/datetime.rb +20 -0
- data/app/models/alchemy/ingredients/file.rb +30 -0
- data/app/models/alchemy/ingredients/headline.rb +42 -0
- data/app/models/alchemy/ingredients/html.rb +19 -0
- data/app/models/alchemy/ingredients/link.rb +16 -0
- data/app/models/alchemy/ingredients/node.rb +23 -0
- data/app/models/alchemy/ingredients/page.rb +23 -0
- data/app/models/alchemy/ingredients/picture.rb +41 -0
- data/app/models/alchemy/ingredients/richtext.rb +57 -0
- data/app/models/alchemy/ingredients/select.rb +10 -0
- data/app/models/alchemy/ingredients/text.rb +17 -0
- data/app/models/alchemy/ingredients/video.rb +33 -0
- data/app/models/alchemy/language.rb +0 -11
- data/app/models/alchemy/node.rb +1 -1
- data/app/models/alchemy/page/fixed_attributes.rb +53 -51
- data/app/models/alchemy/page/page_elements.rb +186 -205
- data/app/models/alchemy/page/page_naming.rb +66 -64
- data/app/models/alchemy/page/page_natures.rb +139 -142
- data/app/models/alchemy/page/page_scopes.rb +113 -102
- data/app/models/alchemy/page/publisher.rb +50 -0
- data/app/models/alchemy/page/url_path.rb +1 -1
- data/app/models/alchemy/page.rb +67 -33
- data/app/models/alchemy/page_version.rb +58 -0
- data/app/models/alchemy/picture/calculations.rb +2 -8
- data/app/models/alchemy/picture/preprocessor.rb +2 -0
- data/app/models/alchemy/picture/transformations.rb +24 -96
- data/app/models/alchemy/picture.rb +4 -2
- data/app/models/concerns/alchemy/picture_thumbnails.rb +181 -0
- data/app/models/concerns/alchemy/touch_elements.rb +2 -2
- data/app/presenters/alchemy/picture_view.rb +88 -0
- data/app/serializers/alchemy/element_serializer.rb +5 -0
- data/app/serializers/alchemy/page_tree_serializer.rb +3 -2
- data/app/services/alchemy/delete_elements.rb +44 -0
- data/app/services/alchemy/duplicate_element.rb +56 -0
- data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +1 -2
- data/app/views/alchemy/admin/attachments/_file_to_assign.html.erb +3 -3
- data/app/views/alchemy/admin/attachments/assign.js.erb +11 -0
- data/app/views/alchemy/admin/crop.html.erb +36 -0
- data/app/views/alchemy/admin/elements/_element.html.erb +14 -10
- data/app/views/alchemy/admin/elements/{_element_footer.html.erb → _footer.html.erb} +0 -0
- data/app/views/alchemy/admin/elements/{_new_element_form.html.erb → _form.html.erb} +1 -1
- data/app/views/alchemy/admin/elements/{_element_header.html.erb → _header.html.erb} +1 -1
- data/app/views/alchemy/admin/elements/{_element_toolbar.html.erb → _toolbar.html.erb} +5 -6
- data/app/views/alchemy/admin/elements/{trash.js.erb → destroy.js.erb} +1 -3
- data/app/views/alchemy/admin/elements/new.html.erb +3 -3
- data/app/views/alchemy/admin/elements/order.js.erb +0 -17
- data/app/views/alchemy/admin/elements/update.js.erb +3 -2
- data/app/views/alchemy/admin/essence_audios/edit.html.erb +7 -0
- data/app/views/alchemy/admin/essence_pictures/update.js.erb +0 -1
- data/app/views/alchemy/admin/essence_videos/edit.html.erb +11 -0
- data/app/views/alchemy/admin/ingredients/_audio_fields.html.erb +4 -0
- data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +18 -0
- data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +25 -0
- data/app/views/alchemy/admin/ingredients/_video_fields.html.erb +8 -0
- data/app/views/alchemy/admin/ingredients/edit.html.erb +4 -0
- data/app/views/alchemy/admin/layoutpages/edit.html.erb +0 -5
- data/app/views/alchemy/admin/nodes/_node.html.erb +2 -2
- data/app/views/alchemy/admin/pages/_anchor_link.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_external_link.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_file_link.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_form.html.erb +0 -6
- data/app/views/alchemy/admin/pages/_internal_link.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_tinymce_custom_config.html.erb +5 -2
- data/app/views/alchemy/admin/pages/edit.html.erb +36 -24
- data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +2 -4
- data/app/views/alchemy/admin/partials/_routes.html.erb +7 -11
- data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +4 -8
- data/app/views/alchemy/admin/pictures/_infos.html.erb +0 -1
- data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +4 -4
- data/app/views/alchemy/admin/pictures/assign.js.erb +10 -0
- data/app/views/alchemy/admin/resources/_form.html.erb +1 -0
- data/app/views/alchemy/essences/_essence_audio_editor.html.erb +4 -0
- data/app/views/alchemy/essences/_essence_audio_view.html.erb +15 -0
- data/app/views/alchemy/essences/_essence_file_editor.html.erb +15 -6
- data/app/views/alchemy/essences/_essence_headline_editor.html.erb +36 -0
- data/app/views/alchemy/essences/_essence_headline_view.html.erb +10 -0
- data/app/views/alchemy/essences/_essence_link_editor.html.erb +8 -4
- data/app/views/alchemy/essences/_essence_picture_editor.html.erb +27 -12
- data/app/views/alchemy/essences/_essence_text_editor.html.erb +12 -4
- data/app/views/alchemy/essences/_essence_video_editor.html.erb +4 -0
- data/app/views/alchemy/essences/_essence_video_view.html.erb +18 -0
- data/app/views/alchemy/essences/shared/_essence_picture_tools.html.erb +21 -16
- data/app/views/alchemy/essences/shared/_linkable_essence_tools.html.erb +2 -2
- data/app/views/alchemy/ingredients/_audio_editor.html.erb +5 -0
- data/app/views/alchemy/ingredients/_audio_view.html.erb +14 -0
- data/app/views/alchemy/ingredients/_boolean_editor.html.erb +11 -0
- data/app/views/alchemy/ingredients/_boolean_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_datetime_editor.html.erb +17 -0
- data/app/views/alchemy/ingredients/_datetime_view.html.erb +9 -0
- data/app/views/alchemy/ingredients/_file_editor.html.erb +50 -0
- data/app/views/alchemy/ingredients/_file_view.html.erb +17 -0
- data/app/views/alchemy/ingredients/_headline_editor.html.erb +30 -0
- data/app/views/alchemy/ingredients/_headline_view.html.erb +9 -0
- data/app/views/alchemy/ingredients/_html_editor.html.erb +8 -0
- data/app/views/alchemy/ingredients/_html_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_link_editor.html.erb +24 -0
- data/app/views/alchemy/ingredients/_link_view.html.erb +9 -0
- data/app/views/alchemy/ingredients/_node_editor.html.erb +25 -0
- data/app/views/alchemy/ingredients/_node_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_page_editor.html.erb +24 -0
- data/app/views/alchemy/ingredients/_page_view.html.erb +4 -0
- data/app/views/alchemy/ingredients/_picture_editor.html.erb +59 -0
- data/app/views/alchemy/ingredients/_picture_view.html.erb +5 -0
- data/app/views/alchemy/ingredients/_richtext_editor.html.erb +12 -0
- data/app/views/alchemy/ingredients/_richtext_view.html.erb +3 -0
- data/app/views/alchemy/ingredients/_select_editor.html.erb +29 -0
- data/app/views/alchemy/ingredients/_select_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_text_editor.html.erb +19 -0
- data/app/views/alchemy/ingredients/_text_view.html.erb +16 -0
- data/app/views/alchemy/ingredients/_video_editor.html.erb +5 -0
- data/app/views/alchemy/ingredients/_video_view.html.erb +17 -0
- data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +20 -0
- data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +57 -0
- data/config/brakeman.ignore +66 -159
- data/config/initializers/dragonfly.rb +10 -0
- data/config/locales/alchemy.en.yml +23 -15
- data/config/routes.rb +17 -22
- data/db/migrate/20201207131309_create_page_versions.rb +19 -0
- data/db/migrate/20201207135820_add_page_version_id_to_alchemy_elements.rb +76 -0
- data/db/migrate/20210205143548_rename_public_on_and_public_until_on_alchemy_pages.rb +10 -0
- data/db/migrate/20210326105046_add_sanitized_body_to_alchemy_essence_richtexts.rb +7 -0
- data/db/migrate/20210406093436_add_alchemy_essence_headlines.rb +12 -0
- data/db/migrate/20210506135919_create_essence_audios.rb +19 -0
- data/db/migrate/20210506140258_create_essence_videos.rb +23 -0
- data/db/migrate/20210508091432_create_alchemy_ingredients.rb +22 -0
- data/lib/alchemy/admin/preview_url.rb +2 -0
- data/lib/alchemy/deprecation.rb +1 -1
- data/lib/alchemy/dragonfly/processors/auto_orient.rb +18 -0
- data/lib/alchemy/dragonfly/processors/crop_resize.rb +35 -0
- data/lib/alchemy/elements_finder.rb +14 -60
- data/lib/alchemy/engine.rb +1 -1
- data/lib/alchemy/essence.rb +1 -2
- data/lib/alchemy/hints.rb +8 -4
- data/lib/alchemy/page_layout.rb +0 -13
- data/lib/alchemy/permissions.rb +30 -29
- data/lib/alchemy/resource.rb +13 -3
- data/lib/alchemy/tasks/tidy.rb +29 -0
- data/lib/alchemy/test_support/essence_shared_examples.rb +0 -1
- data/lib/alchemy/test_support/factories/element_factory.rb +8 -8
- data/lib/alchemy/test_support/factories/essence_audio_factory.rb +7 -0
- data/lib/alchemy/test_support/factories/essence_video_factory.rb +7 -0
- data/lib/alchemy/test_support/factories/ingredient_factory.rb +25 -0
- data/lib/alchemy/test_support/factories/page_factory.rb +20 -1
- data/lib/alchemy/test_support/factories/page_version_factory.rb +23 -0
- data/lib/alchemy/test_support/having_crop_action_examples.rb +170 -0
- data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +646 -0
- data/lib/alchemy/test_support/shared_ingredient_editor_examples.rb +21 -0
- data/lib/alchemy/test_support/shared_ingredient_examples.rb +57 -0
- data/lib/alchemy/test_support.rb +2 -11
- data/lib/alchemy/tinymce.rb +17 -0
- data/lib/alchemy/upgrader/five_point_zero.rb +0 -32
- data/lib/alchemy/upgrader/six_point_zero.rb +21 -0
- data/lib/alchemy/upgrader/tasks/add_page_versions.rb +33 -0
- data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +51 -0
- data/lib/alchemy/version.rb +1 -1
- data/lib/generators/alchemy/elements/elements_generator.rb +1 -0
- data/lib/generators/alchemy/elements/templates/view.html.erb +9 -0
- data/lib/generators/alchemy/elements/templates/view.html.haml +9 -0
- data/lib/generators/alchemy/elements/templates/view.html.slim +9 -0
- data/lib/generators/alchemy/ingredient/ingredient_generator.rb +38 -0
- data/lib/generators/alchemy/ingredient/templates/editor.html.erb +14 -0
- data/lib/generators/alchemy/ingredient/templates/model.rb.tt +13 -0
- data/lib/generators/alchemy/ingredient/templates/view.html.erb +1 -0
- data/lib/generators/alchemy/install/install_generator.rb +1 -2
- data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +1 -1
- data/lib/generators/alchemy/menus/templates/node.html.erb +1 -1
- data/lib/generators/alchemy/menus/templates/node.html.haml +1 -1
- data/lib/generators/alchemy/menus/templates/node.html.slim +1 -1
- data/lib/generators/alchemy/menus/templates/wrapper.html.erb +1 -1
- data/lib/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
- data/lib/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
- data/lib/tasks/alchemy/tidy.rake +12 -0
- data/lib/tasks/alchemy/upgrade.rake +21 -15
- data/package/admin.js +9 -1
- data/package/src/file_editors.js +28 -0
- data/package/src/image_cropper.js +103 -0
- data/package/src/image_loader.js +58 -0
- data/package/src/node_tree.js +5 -5
- data/package/src/picture_editors.js +169 -0
- data/package/src/utils/__tests__/ajax.spec.js +20 -12
- data/package/src/utils/ajax.js +8 -3
- data/package.json +3 -2
- data/vendor/assets/javascripts/jquery_plugins/jquery.Jcrop.min.js +3 -18
- data/vendor/assets/stylesheets/jquery.Jcrop.min.scss +2 -28
- metadata +285 -56
- data/app/assets/javascripts/alchemy/alchemy.image_cropper.js.coffee +0 -44
- data/app/assets/javascripts/alchemy/alchemy.trash_window.js.coffee +0 -30
- data/app/assets/stylesheets/alchemy/trash.scss +0 -8
- data/app/controllers/alchemy/admin/trash_controller.rb +0 -44
- data/app/views/alchemy/admin/essence_files/assign.js.erb +0 -3
- data/app/views/alchemy/admin/essence_pictures/assign.js.erb +0 -4
- data/app/views/alchemy/admin/essence_pictures/crop.html.erb +0 -48
- data/app/views/alchemy/admin/trash/clear.js.erb +0 -4
- data/app/views/alchemy/admin/trash/index.html.erb +0 -31
- data/lib/alchemy/test_support/factories.rb +0 -20
|
@@ -1,171 +1,168 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Alchemy
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def taggable?
|
|
17
|
-
definition["taggable"] == true
|
|
18
|
-
end
|
|
4
|
+
class Page < BaseRecord
|
|
5
|
+
module PageNatures
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
# Determines if this page has a public version and this version is public.
|
|
9
|
+
#
|
|
10
|
+
# @see PageVersion#public?
|
|
11
|
+
# @returns Boolean
|
|
12
|
+
def public?
|
|
13
|
+
language.public? && !!public_version&.public?
|
|
14
|
+
end
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
def expiration_time
|
|
17
|
+
public_until ? public_until - Time.current : nil
|
|
18
|
+
end
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
def taggable?
|
|
21
|
+
definition["taggable"] == true
|
|
22
|
+
end
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
return unless Alchemy.user_class < ActiveRecord::Base
|
|
24
|
+
deprecate :taggable?, deprecator: Alchemy::Deprecation
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
def rootpage?
|
|
27
|
+
!new_record? && parent_id.blank?
|
|
28
|
+
end
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
end
|
|
30
|
+
def folded?(user_id)
|
|
31
|
+
return unless Alchemy.user_class < ActiveRecord::Base
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# # config/alchemy/page_layouts.yml
|
|
39
|
-
# - name: contact
|
|
40
|
-
# editable_by:
|
|
41
|
-
# - freelancer
|
|
42
|
-
# - admin
|
|
43
|
-
#
|
|
44
|
-
# @returns Array
|
|
45
|
-
#
|
|
46
|
-
def has_limited_editors?
|
|
47
|
-
definition["editable_by"].present?
|
|
48
|
-
end
|
|
33
|
+
folded_pages.where(user_id: user_id, folded: true).any?
|
|
34
|
+
end
|
|
49
35
|
|
|
50
|
-
|
|
51
|
-
|
|
36
|
+
def contains_feed?
|
|
37
|
+
definition["feed"]
|
|
38
|
+
end
|
|
52
39
|
|
|
53
|
-
|
|
54
|
-
|
|
40
|
+
# Returns an Array of Alchemy roles which are able to edit this template
|
|
41
|
+
#
|
|
42
|
+
# # config/alchemy/page_layouts.yml
|
|
43
|
+
# - name: contact
|
|
44
|
+
# editable_by:
|
|
45
|
+
# - freelancer
|
|
46
|
+
# - admin
|
|
47
|
+
#
|
|
48
|
+
# @returns Array
|
|
49
|
+
#
|
|
50
|
+
def has_limited_editors?
|
|
51
|
+
definition["editable_by"].present?
|
|
52
|
+
end
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
locked_by? && locked_at?
|
|
59
|
-
end
|
|
54
|
+
def editor_roles
|
|
55
|
+
return unless has_limited_editors?
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def status
|
|
64
|
-
{
|
|
65
|
-
public: public?,
|
|
66
|
-
locked: locked?,
|
|
67
|
-
restricted: restricted?,
|
|
68
|
-
}
|
|
69
|
-
end
|
|
57
|
+
definition["editable_by"]
|
|
58
|
+
end
|
|
70
59
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def status_title(status_type)
|
|
76
|
-
Alchemy.t(status[status_type].to_s, scope: "page_states.#{status_type}")
|
|
77
|
-
end
|
|
60
|
+
# True if page locked_at timestamp and locked_by id are set
|
|
61
|
+
def locked?
|
|
62
|
+
locked_by? && locked_at?
|
|
63
|
+
end
|
|
78
64
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
65
|
+
# Returns a Hash describing the status of the Page.
|
|
66
|
+
#
|
|
67
|
+
def status
|
|
68
|
+
{
|
|
69
|
+
public: public?,
|
|
70
|
+
locked: locked?,
|
|
71
|
+
restricted: restricted?,
|
|
72
|
+
}
|
|
85
73
|
end
|
|
86
|
-
definition
|
|
87
|
-
end
|
|
88
74
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
# Returns the translated status for given status type.
|
|
76
|
+
#
|
|
77
|
+
# @param [Symbol] status_type
|
|
78
|
+
#
|
|
79
|
+
def status_title(status_type)
|
|
80
|
+
Alchemy.t(status[status_type].to_s, scope: "page_states.#{status_type}")
|
|
81
|
+
end
|
|
95
82
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
83
|
+
# Returns the self#page_layout definition from config/alchemy/page_layouts.yml file.
|
|
84
|
+
def definition
|
|
85
|
+
definition = PageLayout.get(page_layout)
|
|
86
|
+
if definition.nil?
|
|
87
|
+
log_warning "Page definition for `#{page_layout}` not found. Please check `page_layouts.yml` file."
|
|
88
|
+
return {}
|
|
89
|
+
end
|
|
90
|
+
definition
|
|
91
|
+
end
|
|
101
92
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
#
|
|
108
|
-
def cache_key
|
|
109
|
-
if Page.current_preview == self
|
|
110
|
-
"alchemy/pages/#{id}-#{updated_at}"
|
|
111
|
-
else
|
|
112
|
-
"alchemy/pages/#{id}-#{published_at}"
|
|
93
|
+
# Returns translated name of the pages page_layout value.
|
|
94
|
+
# Page layout names are defined inside the config/alchemy/page_layouts.yml file.
|
|
95
|
+
# Translate the name in your config/locales language yml file.
|
|
96
|
+
def layout_display_name
|
|
97
|
+
Alchemy.t(page_layout, scope: "page_layout_names")
|
|
113
98
|
end
|
|
114
|
-
end
|
|
115
99
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def published_at
|
|
122
|
-
read_attribute(:published_at) || updated_at
|
|
123
|
-
end
|
|
100
|
+
# Returns the name for the layout partial
|
|
101
|
+
#
|
|
102
|
+
def layout_partial_name
|
|
103
|
+
page_layout.parameterize.underscore
|
|
104
|
+
end
|
|
124
105
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
#
|
|
139
|
-
# == Note:
|
|
140
|
-
#
|
|
141
|
-
# This only sets the cache control headers and skips rendering of the page body,
|
|
142
|
-
# if the cache is fresh.
|
|
143
|
-
# This does not disable the fragment caching in the views.
|
|
144
|
-
# So if you don't want a page and it's elements to be cached,
|
|
145
|
-
# then be sure to not use <% cache element %> in the views.
|
|
146
|
-
#
|
|
147
|
-
# @returns Boolean
|
|
148
|
-
#
|
|
149
|
-
def cache_page?
|
|
150
|
-
return false unless caching_enabled?
|
|
151
|
-
|
|
152
|
-
page_layout = PageLayout.get(self.page_layout)
|
|
153
|
-
page_layout["cache"] != false && page_layout["searchresults"] != true
|
|
154
|
-
end
|
|
106
|
+
# Returns the key that's taken for cache path.
|
|
107
|
+
#
|
|
108
|
+
# Uses the +published_at+ value that's updated when the user publishes the page.
|
|
109
|
+
#
|
|
110
|
+
# If the page is the current preview it uses the updated_at value as cache key.
|
|
111
|
+
#
|
|
112
|
+
def cache_key
|
|
113
|
+
if Page.current_preview == self
|
|
114
|
+
"alchemy/pages/#{id}-#{updated_at}"
|
|
115
|
+
else
|
|
116
|
+
"alchemy/pages/#{id}-#{published_at}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
155
119
|
|
|
156
|
-
|
|
120
|
+
# We use the published_at value for the cache_key.
|
|
121
|
+
#
|
|
122
|
+
# If no published_at value is set yet, i.e. because it was never published,
|
|
123
|
+
# we return the updated_at value.
|
|
124
|
+
#
|
|
125
|
+
def published_at
|
|
126
|
+
read_attribute(:published_at) || updated_at
|
|
127
|
+
end
|
|
157
128
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
129
|
+
# Returns true if the page cache control headers should be set.
|
|
130
|
+
#
|
|
131
|
+
# == Disable Alchemy's page caching globally
|
|
132
|
+
#
|
|
133
|
+
# # config/alchemy/config.yml
|
|
134
|
+
# ...
|
|
135
|
+
# cache_pages: false
|
|
136
|
+
#
|
|
137
|
+
# == Disable caching on page layout level
|
|
138
|
+
#
|
|
139
|
+
# # config/alchemy/page_layouts.yml
|
|
140
|
+
# - name: contact
|
|
141
|
+
# cache: false
|
|
142
|
+
#
|
|
143
|
+
# == Note:
|
|
144
|
+
#
|
|
145
|
+
# This only sets the cache control headers and skips rendering of the page body,
|
|
146
|
+
# if the cache is fresh.
|
|
147
|
+
# This does not disable the fragment caching in the views.
|
|
148
|
+
# So if you don't want a page and it's elements to be cached,
|
|
149
|
+
# then be sure to not use <% cache element %> in the views.
|
|
150
|
+
#
|
|
151
|
+
# @returns Boolean
|
|
152
|
+
#
|
|
153
|
+
def cache_page?
|
|
154
|
+
return false unless caching_enabled?
|
|
155
|
+
|
|
156
|
+
page_layout = PageLayout.get(self.page_layout)
|
|
157
|
+
page_layout["cache"] != false && page_layout["searchresults"] != true
|
|
158
|
+
end
|
|
162
159
|
|
|
163
|
-
|
|
164
|
-
!public_on.nil? && public_on <= time
|
|
165
|
-
end
|
|
160
|
+
private
|
|
166
161
|
|
|
167
|
-
|
|
168
|
-
|
|
162
|
+
def caching_enabled?
|
|
163
|
+
Alchemy::Config.get(:cache_pages) &&
|
|
164
|
+
Rails.application.config.action_controller.perform_caching
|
|
165
|
+
end
|
|
169
166
|
end
|
|
170
167
|
end
|
|
171
168
|
end
|
|
@@ -3,110 +3,121 @@
|
|
|
3
3
|
module Alchemy
|
|
4
4
|
# ActiveRecord scopes for Alchemy::Page
|
|
5
5
|
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
6
|
+
class Page < BaseRecord
|
|
7
|
+
module PageScopes
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
# All language root pages
|
|
12
|
+
#
|
|
13
|
+
scope :language_roots, -> { where(language_root: true) }
|
|
14
|
+
|
|
15
|
+
# All layout pages
|
|
16
|
+
#
|
|
17
|
+
scope :layoutpages, -> { where(layoutpage: true) }
|
|
18
|
+
|
|
19
|
+
# All locked pages
|
|
20
|
+
#
|
|
21
|
+
scope :locked, -> { where.not(locked_at: nil).where.not(locked_by: nil) }
|
|
22
|
+
|
|
23
|
+
# All pages locked by given user
|
|
24
|
+
#
|
|
25
|
+
scope :locked_by,
|
|
26
|
+
->(user) {
|
|
27
|
+
if user.class.respond_to? :primary_key
|
|
28
|
+
locked.where(locked_by: user.send(user.class.primary_key))
|
|
29
|
+
end
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# All not locked pages
|
|
33
|
+
#
|
|
34
|
+
scope :not_locked, -> { where(locked_at: nil, locked_by: nil) }
|
|
35
|
+
|
|
36
|
+
# All not restricted pages
|
|
37
|
+
#
|
|
38
|
+
scope :not_restricted, -> { where(restricted: false) }
|
|
39
|
+
|
|
40
|
+
# All restricted pages
|
|
41
|
+
#
|
|
42
|
+
scope :restricted, -> { where(restricted: true) }
|
|
43
|
+
|
|
44
|
+
# All public pages
|
|
45
|
+
#
|
|
46
|
+
scope :published,
|
|
47
|
+
-> {
|
|
48
|
+
joins(:language, :versions).
|
|
49
|
+
merge(Language.published).
|
|
50
|
+
merge(PageVersion.public_on(Time.current))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# All pages that are a published language root
|
|
54
|
+
#
|
|
55
|
+
scope :public_language_roots,
|
|
56
|
+
-> {
|
|
57
|
+
published.language_roots.where(
|
|
58
|
+
language_code: Language.published.pluck(:language_code),
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Last 5 pages that where recently edited by given user
|
|
63
|
+
#
|
|
64
|
+
scope :all_last_edited_from,
|
|
65
|
+
->(user) {
|
|
66
|
+
where(updater_id: user.id).order("updated_at DESC").limit(5)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Returns all pages that have the given +language_id+
|
|
70
|
+
#
|
|
71
|
+
scope :with_language, ->(language_id) { where(language_id: language_id) }
|
|
72
|
+
|
|
73
|
+
# Returns all content pages.
|
|
74
|
+
#
|
|
75
|
+
scope :contentpages, -> { where(layoutpage: [false, nil]) }
|
|
76
|
+
|
|
77
|
+
# Returns all public contentpages that are not locked.
|
|
78
|
+
#
|
|
79
|
+
# Used for flushing all pages caches at once.
|
|
80
|
+
#
|
|
81
|
+
scope :flushables, -> { not_locked.published.contentpages }
|
|
82
|
+
|
|
83
|
+
# Returns all layoutpages that are not locked.
|
|
84
|
+
#
|
|
85
|
+
# Used for flushing all pages caches at once.
|
|
86
|
+
#
|
|
87
|
+
scope :flushable_layoutpages, -> { not_locked.layoutpages }
|
|
88
|
+
|
|
89
|
+
# All searchable pages
|
|
90
|
+
#
|
|
91
|
+
scope :searchables, -> { not_restricted.published.contentpages }
|
|
92
|
+
|
|
93
|
+
# All pages from +Alchemy::Site.current+
|
|
94
|
+
#
|
|
95
|
+
scope :from_current_site,
|
|
96
|
+
-> {
|
|
97
|
+
where(Language.table_name => { site_id: Site.current || Site.default }).joins(:language)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# All pages for xml sitemap
|
|
101
|
+
#
|
|
102
|
+
scope :sitemap, -> { from_current_site.published.contentpages.where(sitemap: true) }
|
|
102
103
|
end
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
module ClassMethods
|
|
106
|
+
# All pages that do not have any public version
|
|
107
|
+
#
|
|
108
|
+
def not_public(time = Time.current)
|
|
109
|
+
where <<~SQL
|
|
110
|
+
alchemy_pages.id NOT IN (
|
|
111
|
+
SELECT alchemy_page_versions.page_id
|
|
112
|
+
FROM alchemy_page_versions
|
|
113
|
+
WHERE alchemy_page_versions.public_on <= '#{connection.quoted_date(time)}'
|
|
114
|
+
AND (
|
|
115
|
+
alchemy_page_versions.public_until IS NULL
|
|
116
|
+
OR alchemy_page_versions.public_until >= '#{connection.quoted_date(time)}'
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
SQL
|
|
120
|
+
end
|
|
110
121
|
end
|
|
111
122
|
end
|
|
112
123
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_dependency "alchemy/page"
|
|
4
|
+
|
|
5
|
+
module Alchemy
|
|
6
|
+
class Page < BaseRecord
|
|
7
|
+
# Handles publishing of pages
|
|
8
|
+
class Publisher
|
|
9
|
+
def initialize(page)
|
|
10
|
+
@page = page
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Copies all currently visible elements to the public version of page
|
|
14
|
+
#
|
|
15
|
+
# Creates a new published version if none exists yet.
|
|
16
|
+
#
|
|
17
|
+
# Sends a publish notification to all registered publish targets
|
|
18
|
+
#
|
|
19
|
+
def publish!(public_on:)
|
|
20
|
+
Page.transaction do
|
|
21
|
+
version = public_version(public_on)
|
|
22
|
+
DeleteElements.new(version.elements).call
|
|
23
|
+
|
|
24
|
+
repository = page.draft_version.element_repository
|
|
25
|
+
ActiveRecord::Base.no_touching do
|
|
26
|
+
Element.acts_as_list_no_update do
|
|
27
|
+
repository.visible.not_nested.each.with_index(1) do |element, position|
|
|
28
|
+
Alchemy::DuplicateElement.new(element, repository: repository).call(
|
|
29
|
+
page_version_id: version.id,
|
|
30
|
+
position: position
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Alchemy.publish_targets.each { |p| p.perform_later(page) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
attr_reader :page
|
|
43
|
+
|
|
44
|
+
# Load the pages public version or create one
|
|
45
|
+
def public_version(public_on)
|
|
46
|
+
page.public_version || page.versions.create!(public_on: public_on)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|