alchemy_cms 6.1.10 → 7.0.0.pre.a

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.
Files changed (249) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +0 -3
  3. data/.gitignore +1 -6
  4. data/CHANGELOG.md +19 -44
  5. data/Gemfile +1 -1
  6. data/Rakefile +14 -9
  7. data/alchemy_cms.gemspec +2 -3
  8. data/app/assets/javascripts/alchemy/alchemy.dirty.js.coffee +1 -1
  9. data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +18 -32
  10. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +2 -2
  11. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +2 -2
  12. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +27 -29
  13. data/app/assets/stylesheets/alchemy/elements.scss +16 -35
  14. data/app/assets/stylesheets/alchemy/forms.scss +0 -4
  15. data/app/assets/stylesheets/alchemy/node-select.scss +2 -2
  16. data/app/controllers/alchemy/admin/attachments_controller.rb +0 -1
  17. data/app/controllers/alchemy/admin/elements_controller.rb +7 -32
  18. data/app/controllers/alchemy/admin/pages_controller.rb +1 -1
  19. data/app/controllers/alchemy/admin/pictures_controller.rb +1 -1
  20. data/app/controllers/alchemy/admin/resources_controller.rb +1 -18
  21. data/app/controllers/alchemy/api/elements_controller.rb +0 -2
  22. data/app/controllers/alchemy/api/pages_controller.rb +8 -4
  23. data/app/controllers/alchemy/messages_controller.rb +9 -9
  24. data/app/controllers/alchemy/pages_controller.rb +23 -18
  25. data/app/decorators/alchemy/element_editor.rb +10 -30
  26. data/app/helpers/alchemy/admin/elements_helper.rb +0 -2
  27. data/app/helpers/alchemy/elements_block_helper.rb +5 -42
  28. data/app/helpers/alchemy/elements_helper.rb +3 -11
  29. data/app/helpers/alchemy/pages_helper.rb +0 -4
  30. data/app/models/alchemy/attachment.rb +6 -3
  31. data/app/models/alchemy/base_record.rb +2 -0
  32. data/app/models/alchemy/eager_loading.rb +0 -1
  33. data/app/models/alchemy/element/element_ingredients.rb +1 -8
  34. data/app/models/alchemy/element/presenters.rb +9 -25
  35. data/app/models/alchemy/element.rb +2 -18
  36. data/app/models/alchemy/ingredient.rb +17 -6
  37. data/app/models/alchemy/ingredients/audio.rb +2 -0
  38. data/app/models/alchemy/ingredients/datetime.rb +3 -1
  39. data/app/models/alchemy/ingredients/file.rb +7 -0
  40. data/app/models/alchemy/ingredients/headline.rb +6 -0
  41. data/app/models/alchemy/ingredients/link.rb +2 -0
  42. data/app/models/alchemy/ingredients/node.rb +2 -0
  43. data/app/models/alchemy/ingredients/page.rb +2 -0
  44. data/app/models/alchemy/ingredients/picture.rb +11 -0
  45. data/app/models/alchemy/ingredients/richtext.rb +6 -0
  46. data/app/models/alchemy/ingredients/select.rb +1 -0
  47. data/app/models/alchemy/ingredients/text.rb +8 -0
  48. data/app/models/alchemy/ingredients/video.rb +2 -0
  49. data/app/models/alchemy/node.rb +9 -6
  50. data/app/models/alchemy/page/page_elements.rb +5 -26
  51. data/app/models/alchemy/page/page_layouts.rb +0 -14
  52. data/app/models/alchemy/page/page_natures.rb +0 -10
  53. data/app/models/alchemy/page.rb +0 -10
  54. data/app/models/alchemy/picture/transformations.rb +0 -30
  55. data/app/models/alchemy/picture/url.rb +1 -1
  56. data/app/models/alchemy/picture.rb +14 -13
  57. data/app/models/alchemy/picture_thumb/create.rb +7 -18
  58. data/app/models/alchemy/picture_thumb/file_store.rb +33 -0
  59. data/app/models/alchemy/picture_thumb.rb +10 -10
  60. data/app/models/concerns/alchemy/picture_thumbnails.rb +2 -6
  61. data/app/serializers/alchemy/element_serializer.rb +1 -6
  62. data/app/services/alchemy/delete_elements.rb +1 -7
  63. data/app/services/alchemy/duplicate_element.rb +1 -6
  64. data/app/views/alchemy/admin/elements/_element.html.erb +5 -22
  65. data/app/views/alchemy/admin/elements/create.js.erb +1 -1
  66. data/app/views/alchemy/admin/elements/fold.js.erb +2 -2
  67. data/app/views/alchemy/admin/elements/order.js.erb +1 -1
  68. data/app/views/alchemy/admin/elements/update.js.erb +1 -2
  69. data/app/views/alchemy/admin/pages/_external_link.html.erb +2 -2
  70. data/app/views/alchemy/admin/pages/_file_link.html.erb +2 -2
  71. data/app/views/alchemy/admin/pages/_internal_link.html.erb +2 -2
  72. data/app/views/alchemy/admin/pages/_table.html.erb +0 -6
  73. data/app/views/alchemy/admin/pages/_tinymce_custom_config.html.erb +3 -6
  74. data/app/views/alchemy/admin/pages/edit.html.erb +1 -1
  75. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +1 -3
  76. data/app/views/alchemy/admin/pictures/_infos.html.erb +4 -6
  77. data/app/views/alchemy/admin/resources/_per_page_select.html.erb +1 -1
  78. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +1 -1
  79. data/app/views/alchemy/ingredients/_headline_editor.html.erb +1 -1
  80. data/app/views/alchemy/ingredients/_html_editor.html.erb +1 -1
  81. data/app/views/alchemy/ingredients/_node_editor.html.erb +1 -1
  82. data/app/views/alchemy/ingredients/_picture_editor.html.erb +4 -4
  83. data/app/views/alchemy/ingredients/_select_editor.html.erb +2 -2
  84. data/app/views/alchemy/ingredients/_text_editor.html.erb +1 -1
  85. data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +3 -3
  86. data/app/views/alchemy/pages/_meta_data.html.erb +0 -1
  87. data/app/views/layouts/alchemy/admin.html.erb +5 -1
  88. data/config/alchemy/config.yml +6 -6
  89. data/config/brakeman.ignore +56 -57
  90. data/config/locales/alchemy.en.yml +99 -113
  91. data/config/routes.rb +1 -16
  92. data/db/migrate/20230121212637_alchemy_six_point_one.rb +248 -0
  93. data/lib/alchemy/cache_digests/template_tracker.rb +6 -7
  94. data/lib/alchemy/config.rb +2 -2
  95. data/lib/alchemy/deprecation.rb +1 -1
  96. data/lib/alchemy/errors.rb +0 -11
  97. data/lib/alchemy/hints.rb +10 -10
  98. data/lib/alchemy/permissions.rb +4 -17
  99. data/lib/alchemy/routing_constraints.rb +3 -3
  100. data/lib/alchemy/searchable_resource.rb +38 -0
  101. data/lib/alchemy/seeder.rb +2 -8
  102. data/lib/alchemy/tasks/tidy.rb +0 -38
  103. data/lib/alchemy/test_support/capybara_helpers.rb +69 -0
  104. data/lib/alchemy/test_support/factories/element_factory.rb +0 -6
  105. data/lib/alchemy/test_support/factories/ingredient_factory.rb +1 -1
  106. data/lib/alchemy/test_support/factories/page_factory.rb +4 -2
  107. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +0 -20
  108. data/lib/alchemy/test_support/shared_dom_ids_examples.rb +1 -1
  109. data/lib/alchemy/test_support/shared_ingredient_examples.rb +1 -1
  110. data/lib/alchemy/tinymce.rb +1 -18
  111. data/lib/alchemy/upgrader/seven_point_zero.rb +45 -0
  112. data/lib/alchemy/upgrader/tasks/.keep +0 -0
  113. data/lib/alchemy/upgrader.rb +8 -3
  114. data/lib/alchemy/version.rb +1 -1
  115. data/lib/alchemy.rb +0 -19
  116. data/lib/alchemy_cms.rb +1 -2
  117. data/lib/generators/alchemy/elements/elements_generator.rb +0 -1
  118. data/lib/generators/alchemy/elements/templates/view.html.erb +1 -10
  119. data/lib/generators/alchemy/elements/templates/view.html.haml +1 -9
  120. data/lib/generators/alchemy/elements/templates/view.html.slim +1 -9
  121. data/lib/generators/alchemy/install/files/alchemy.en.yml +7 -8
  122. data/lib/generators/alchemy/install/files/application.html.erb +1 -1
  123. data/lib/generators/alchemy/install/install_generator.rb +18 -34
  124. data/lib/generators/alchemy/install/templates/elements.yml.tt +12 -12
  125. data/lib/non_stupid_digest_assets.rb +1 -1
  126. data/lib/tasks/alchemy/thumbnails.rake +2 -21
  127. data/lib/tasks/alchemy/tidy.rake +1 -12
  128. data/lib/tasks/alchemy/upgrade.rake +10 -47
  129. data/package/dist/admin.js +16 -0
  130. data/package/dist/admin.js.map +7 -0
  131. data/package.json +5 -3
  132. metadata +18 -147
  133. data/app/controllers/alchemy/admin/contents_controller.rb +0 -21
  134. data/app/controllers/alchemy/admin/essence_audios_controller.rb +0 -30
  135. data/app/controllers/alchemy/admin/essence_files_controller.rb +0 -31
  136. data/app/controllers/alchemy/admin/essence_pictures_controller.rb +0 -43
  137. data/app/controllers/alchemy/admin/essence_videos_controller.rb +0 -34
  138. data/app/controllers/alchemy/api/contents_controller.rb +0 -52
  139. data/app/decorators/alchemy/content_editor.rb +0 -119
  140. data/app/helpers/alchemy/admin/contents_helper.rb +0 -42
  141. data/app/helpers/alchemy/admin/essences_helper.rb +0 -31
  142. data/app/models/alchemy/content/factory.rb +0 -143
  143. data/app/models/alchemy/content.rb +0 -247
  144. data/app/models/alchemy/element/element_contents.rb +0 -200
  145. data/app/models/alchemy/element/element_essences.rb +0 -133
  146. data/app/models/alchemy/essence_audio.rb +0 -13
  147. data/app/models/alchemy/essence_boolean.rb +0 -20
  148. data/app/models/alchemy/essence_date.rb +0 -25
  149. data/app/models/alchemy/essence_file.rb +0 -49
  150. data/app/models/alchemy/essence_headline.rb +0 -41
  151. data/app/models/alchemy/essence_html.rb +0 -23
  152. data/app/models/alchemy/essence_link.rb +0 -21
  153. data/app/models/alchemy/essence_node.rb +0 -19
  154. data/app/models/alchemy/essence_page.rb +0 -17
  155. data/app/models/alchemy/essence_picture.rb +0 -67
  156. data/app/models/alchemy/essence_picture_view.rb +0 -90
  157. data/app/models/alchemy/essence_richtext.rb +0 -44
  158. data/app/models/alchemy/essence_select.rb +0 -19
  159. data/app/models/alchemy/essence_text.rb +0 -23
  160. data/app/models/alchemy/essence_video.rb +0 -13
  161. data/app/serializers/alchemy/content_serializer.rb +0 -17
  162. data/app/serializers/alchemy/essence_boolean_serializer.rb +0 -10
  163. data/app/serializers/alchemy/essence_date_serializer.rb +0 -10
  164. data/app/serializers/alchemy/essence_file_serializer.rb +0 -13
  165. data/app/serializers/alchemy/essence_html_serializer.rb +0 -10
  166. data/app/serializers/alchemy/essence_link_serializer.rb +0 -13
  167. data/app/serializers/alchemy/essence_picture_serializer.rb +0 -28
  168. data/app/serializers/alchemy/essence_richtext_serializer.rb +0 -11
  169. data/app/serializers/alchemy/essence_select_serializer.rb +0 -10
  170. data/app/serializers/alchemy/essence_text_serializer.rb +0 -22
  171. data/app/views/alchemy/admin/contents/create.js.erb +0 -21
  172. data/app/views/alchemy/admin/essence_audios/edit.html.erb +0 -7
  173. data/app/views/alchemy/admin/essence_files/edit.html.erb +0 -21
  174. data/app/views/alchemy/admin/essence_pictures/destroy.js.erb +0 -5
  175. data/app/views/alchemy/admin/essence_pictures/edit.html.erb +0 -30
  176. data/app/views/alchemy/admin/essence_pictures/save_link.js.erb +0 -3
  177. data/app/views/alchemy/admin/essence_pictures/update.js.erb +0 -8
  178. data/app/views/alchemy/admin/essence_videos/edit.html.erb +0 -12
  179. data/app/views/alchemy/essences/_essence_audio_editor.html.erb +0 -4
  180. data/app/views/alchemy/essences/_essence_audio_view.html.erb +0 -15
  181. data/app/views/alchemy/essences/_essence_boolean_editor.html.erb +0 -11
  182. data/app/views/alchemy/essences/_essence_boolean_view.html.erb +0 -2
  183. data/app/views/alchemy/essences/_essence_date_editor.html.erb +0 -16
  184. data/app/views/alchemy/essences/_essence_date_view.html.erb +0 -10
  185. data/app/views/alchemy/essences/_essence_file_editor.html.erb +0 -54
  186. data/app/views/alchemy/essences/_essence_file_view.html.erb +0 -18
  187. data/app/views/alchemy/essences/_essence_headline_editor.html.erb +0 -36
  188. data/app/views/alchemy/essences/_essence_headline_view.html.erb +0 -10
  189. data/app/views/alchemy/essences/_essence_html_editor.html.erb +0 -10
  190. data/app/views/alchemy/essences/_essence_html_view.html.erb +0 -2
  191. data/app/views/alchemy/essences/_essence_link_editor.html.erb +0 -30
  192. data/app/views/alchemy/essences/_essence_link_view.html.erb +0 -10
  193. data/app/views/alchemy/essences/_essence_node_editor.html.erb +0 -27
  194. data/app/views/alchemy/essences/_essence_node_view.html.erb +0 -1
  195. data/app/views/alchemy/essences/_essence_page_editor.html.erb +0 -26
  196. data/app/views/alchemy/essences/_essence_page_view.html.erb +0 -5
  197. data/app/views/alchemy/essences/_essence_picture_editor.html.erb +0 -59
  198. data/app/views/alchemy/essences/_essence_picture_view.html.erb +0 -6
  199. data/app/views/alchemy/essences/_essence_richtext_editor.html.erb +0 -14
  200. data/app/views/alchemy/essences/_essence_richtext_view.html.erb +0 -4
  201. data/app/views/alchemy/essences/_essence_select_editor.html.erb +0 -28
  202. data/app/views/alchemy/essences/_essence_select_view.html.erb +0 -2
  203. data/app/views/alchemy/essences/_essence_text_editor.html.erb +0 -29
  204. data/app/views/alchemy/essences/_essence_text_view.html.erb +0 -17
  205. data/app/views/alchemy/essences/_essence_video_editor.html.erb +0 -4
  206. data/app/views/alchemy/essences/_essence_video_view.html.erb +0 -19
  207. data/app/views/alchemy/essences/shared/_essence_picture_tools.html.erb +0 -59
  208. data/app/views/alchemy/essences/shared/_linkable_essence_tools.html.erb +0 -20
  209. data/app/views/alchemy/pages/show.rss.builder +0 -21
  210. data/db/migrate/20200226213334_alchemy_four_point_four.rb +0 -313
  211. data/db/migrate/20200423073425_create_alchemy_essence_nodes.rb +0 -11
  212. data/db/migrate/20200504210159_remove_site_id_from_nodes.rb +0 -28
  213. data/db/migrate/20200505215518_add_language_id_foreign_key_to_alchemy_pages.rb +0 -8
  214. data/db/migrate/20200511113603_add_menu_type_to_alchemy_nodes.rb +0 -27
  215. data/db/migrate/20200514091507_make_page_layoutpage_null_false.rb +0 -6
  216. data/db/migrate/20200519073500_remove_visible_from_alchemy_pages.rb +0 -24
  217. data/db/migrate/20200617110713_create_alchemy_picture_thumbs.rb +0 -22
  218. data/db/migrate/20200907111332_remove_tri_state_booleans.rb +0 -33
  219. data/db/migrate/20201207131309_create_page_versions.rb +0 -19
  220. data/db/migrate/20201207135820_add_page_version_id_to_alchemy_elements.rb +0 -76
  221. data/db/migrate/20210205143548_rename_public_on_and_public_until_on_alchemy_pages.rb +0 -10
  222. data/db/migrate/20210326105046_add_sanitized_body_to_alchemy_essence_richtexts.rb +0 -7
  223. data/db/migrate/20210406093436_add_alchemy_essence_headlines.rb +0 -12
  224. data/db/migrate/20210506135919_create_essence_audios.rb +0 -19
  225. data/db/migrate/20210506140258_create_essence_videos.rb +0 -23
  226. data/db/migrate/20210508091432_create_alchemy_ingredients.rb +0 -22
  227. data/db/migrate/20220514072456_restrict_on_delete_page_id_foreign_key_from_alchemy_nodes.rb +0 -13
  228. data/db/migrate/20220622130905_add_playsinline_to_alchemy_essence_videos.rb +0 -9
  229. data/lib/alchemy/essence.rb +0 -250
  230. data/lib/alchemy/tasks/usage.rb +0 -34
  231. data/lib/alchemy/test_support/essence_shared_examples.rb +0 -271
  232. data/lib/alchemy/test_support/factories/content_factory.rb +0 -20
  233. data/lib/alchemy/test_support/factories/essence_audio_factory.rb +0 -7
  234. data/lib/alchemy/test_support/factories/essence_file_factory.rb +0 -7
  235. data/lib/alchemy/test_support/factories/essence_page_factory.rb +0 -7
  236. data/lib/alchemy/test_support/factories/essence_picture_factory.rb +0 -11
  237. data/lib/alchemy/test_support/factories/essence_text_factory.rb +0 -7
  238. data/lib/alchemy/test_support/factories/essence_video_factory.rb +0 -7
  239. data/lib/alchemy/upgrader/five_point_zero.rb +0 -41
  240. data/lib/alchemy/upgrader/six_point_zero.rb +0 -21
  241. data/lib/alchemy/upgrader/tasks/add_page_versions.rb +0 -33
  242. data/lib/alchemy/upgrader/tasks/element_views_updater.rb +0 -34
  243. data/lib/alchemy/upgrader/tasks/harden_gutentag_migrations.rb +0 -29
  244. data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +0 -73
  245. data/lib/generators/alchemy/essence/essence_generator.rb +0 -49
  246. data/lib/generators/alchemy/essence/templates/editor.html.erb +0 -17
  247. data/lib/generators/alchemy/essence/templates/view.html.erb +0 -2
  248. data/lib/generators/alchemy/install/files/babel.config.js +0 -64
  249. data/lib/tasks/alchemy/usage.rake +0 -44
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class RestrictOnDeletePageIdForeignKeyFromAlchemyNodes < ActiveRecord::Migration[6.0]
4
- def up
5
- remove_foreign_key :alchemy_nodes, :alchemy_pages
6
- add_foreign_key :alchemy_nodes, :alchemy_pages, column: :page_id, on_delete: :restrict
7
- end
8
-
9
- def down
10
- remove_foreign_key :alchemy_nodes, :alchemy_pages
11
- add_foreign_key :alchemy_nodes, :alchemy_pages, column: :page_id, on_delete: :cascade
12
- end
13
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class AddPlaysinlineToAlchemyEssenceVideos < ActiveRecord::Migration[6.0]
4
- def change
5
- return if column_exists?(:alchemy_essence_videos, :playsinline)
6
-
7
- add_column :alchemy_essence_videos, :playsinline, :boolean, default: false, null: false
8
- end
9
- end
@@ -1,250 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_record"
4
-
5
- module Alchemy #:nodoc:
6
- module Essence #:nodoc:
7
- def self.included(base)
8
- base.extend(ClassMethods)
9
- end
10
-
11
- # Delivers various methods we need for Essences in Alchemy.
12
- #
13
- # To turn a model into an essence call acts_as_essence inside your model and you will get:
14
- # * validations
15
- # * several getters (ie: page, element, content, ingredient, preview_text)
16
- #
17
- module ClassMethods
18
- # Turn any active record model into an essence by calling this class method
19
- #
20
- # @option options [String || Symbol] ingredient_column ('body')
21
- # specifies the column name you use for storing the content in the database (default: +body+)
22
- # @option options [String || Symbol] validate_column (ingredient_column)
23
- # The column the the validations run against.
24
- # @option options [String || Symbol] preview_text_column (ingredient_column)
25
- # Specify the column for the preview_text method.
26
- # @deprecated
27
- def acts_as_essence(options = {})
28
- if !DEPRECATED_ESSENCE_CLASSES.include?(name)
29
- Alchemy::Deprecation.warn "Please create a custom Alchemy::Ingredient for #{name} instead"
30
- end
31
-
32
- register_as_essence_association!
33
-
34
- configuration = {
35
- ingredient_column: "body",
36
- }.update(options)
37
-
38
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
39
- attr_writer :validation_errors
40
- include Alchemy::Essence::InstanceMethods
41
-
42
- validate :validate_ingredient, on: :update, if: -> { validations.any? }
43
-
44
- has_one :content, as: :essence, class_name: "Alchemy::Content", inverse_of: :essence
45
- has_one :element, through: :content, class_name: "Alchemy::Element"
46
- has_one :page, through: :element, class_name: "Alchemy::Page"
47
-
48
- scope :available, -> { joins(:element).merge(Alchemy::Element.published) }
49
- scope :from_element, ->(name) { joins(:element).where(Element.table_name => { name: name }) }
50
-
51
- delegate :restricted?, to: :page, allow_nil: true
52
- delegate :public?, to: :element, allow_nil: true
53
-
54
- after_update :touch_element
55
-
56
- def acts_as_essence_class
57
- #{name}
58
- end
59
-
60
- def ingredient_column
61
- '#{configuration[:ingredient_column]}'
62
- end
63
-
64
- def validation_column
65
- '#{configuration[:validate_column] || configuration[:ingredient_column]}'
66
- end
67
-
68
- def preview_text_column
69
- '#{configuration[:preview_text_column] || configuration[:ingredient_column]}'
70
- end
71
- RUBY
72
-
73
- if configuration[:belongs_to]
74
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
75
- belongs_to :ingredient_association, **#{configuration[:belongs_to]}
76
-
77
- alias_method :#{configuration[:ingredient_column]}, :ingredient_association
78
- alias_method :#{configuration[:ingredient_column]}=, :ingredient_association=
79
- RUBY
80
- end
81
- end
82
-
83
- private
84
-
85
- # Register the current class as has_many association on +Alchemy::Page+ and +Alchemy::Element+ models
86
- def register_as_essence_association!
87
- klass_name = model_name.to_s
88
- arguments = [:has_many, klass_name.demodulize.tableize.to_sym]
89
- kwargs = { through: :contents, source: :essence, source_type: klass_name }
90
- %w(Page Element).each { |k| "Alchemy::#{k}".constantize.send(*arguments, **kwargs) }
91
- end
92
- end
93
-
94
- module InstanceMethods
95
- # Essence Validations:
96
- #
97
- # Essence validations can be set inside the config/elements.yml file.
98
- #
99
- # Supported validations are:
100
- #
101
- # * presence
102
- # * uniqueness
103
- # * format
104
- #
105
- # format needs to come with a regex or a predefined matcher string as its value.
106
- # There are already predefined format matchers listed in the config/alchemy/config.yml file.
107
- # It is also possible to add own format matchers there.
108
- #
109
- # Example of format matchers in config/alchemy/config.yml:
110
- #
111
- # format_matchers:
112
- # email: !ruby/regexp '/\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/'
113
- # url: !ruby/regexp '/\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix'
114
- # ssl: !ruby/regexp '/https:\/\/[\S]+/'
115
- #
116
- # Example of an element definition with essence validations:
117
- #
118
- # - name: person
119
- # contents:
120
- # - name: name
121
- # type: EssenceText
122
- # validate: [presence]
123
- # - name: email
124
- # type: EssenceText
125
- # validate: [format: 'email']
126
- # - name: homepage
127
- # type: EssenceText
128
- # validate: [format: !ruby/regexp '^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$']
129
- #
130
- # Example of an element definition with chained validations.
131
- #
132
- # - name: person
133
- # contents:
134
- # - name: name
135
- # type: EssenceText
136
- # validate: [presence, uniqueness, format: 'name']
137
- #
138
- def validate_ingredient
139
- validations.each do |validation|
140
- if validation.respond_to?(:keys)
141
- validation.map do |key, value|
142
- send("validate_#{key}", value)
143
- end
144
- else
145
- send("validate_#{validation}")
146
- end
147
- end
148
- end
149
-
150
- def validations
151
- @validations ||= definition.present? ? definition["validate"] || [] : []
152
- end
153
-
154
- def validation_errors
155
- @validation_errors ||= []
156
- end
157
-
158
- def validate_presence(validate = true)
159
- if validate && ingredient.blank?
160
- errors.add(ingredient_column, :blank)
161
- validation_errors << :blank
162
- end
163
- end
164
-
165
- def validate_uniqueness(validate = true)
166
- return if !validate || !public?
167
-
168
- if duplicates.any?
169
- errors.add(ingredient_column, :taken)
170
- validation_errors << :taken
171
- end
172
- end
173
-
174
- def validate_format(format)
175
- matcher = Config.get("format_matchers")[format] || format
176
- if ingredient.to_s.match(Regexp.new(matcher)).nil?
177
- errors.add(ingredient_column, :invalid)
178
- validation_errors << :invalid
179
- end
180
- end
181
-
182
- def duplicates
183
- acts_as_essence_class
184
- .available
185
- .from_element(element.name)
186
- .where(ingredient_column.to_s => ingredient)
187
- .where.not(id: id)
188
- end
189
-
190
- # Returns the value stored from the database column that is configured as ingredient column.
191
- def ingredient
192
- if respond_to?(ingredient_column)
193
- send(ingredient_column)
194
- end
195
- end
196
-
197
- # Sets the value stored in the database column that is configured as ingredient column.
198
- def ingredient=(value)
199
- if respond_to?(ingredient_setter_method)
200
- send(ingredient_setter_method, value)
201
- end
202
- end
203
-
204
- # Returns the setter method for ingredient column
205
- def ingredient_setter_method
206
- ingredient_column.to_s + "="
207
- end
208
-
209
- # Essence definition from config/elements.yml
210
- def definition
211
- return {} if element.nil? || element.content_definitions.nil?
212
-
213
- element.content_definitions.detect { |c| c["name"] == content.name } || {}
214
- end
215
-
216
- # Touches element. Called after save.
217
- def touch_element
218
- element&.touch
219
- end
220
-
221
- # Returns the first x (default 30) characters of ingredient for the Element#preview_text method.
222
- #
223
- def preview_text(maxlength = 30)
224
- send(preview_text_column).to_s[0..maxlength - 1]
225
- end
226
-
227
- def open_link_in_new_window?
228
- respond_to?(:link_target) && link_target == "blank"
229
- end
230
-
231
- def partial_name
232
- self.class.name.split("::").last.underscore
233
- end
234
-
235
- def acts_as_essence?
236
- acts_as_essence_class.present?
237
- end
238
-
239
- def to_partial_path
240
- "alchemy/essences/#{partial_name}_view"
241
- end
242
-
243
- def has_tinymce?
244
- false
245
- end
246
- end
247
- end
248
- end
249
-
250
- ActiveRecord::Base.include(Alchemy::Essence)
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Alchemy
4
- module Tasks
5
- module Usage
6
- extend self
7
-
8
- def elements_count_by_name
9
- res = Alchemy::Element.all
10
- .select("name, COUNT(*) AS count")
11
- .group(:name)
12
- .order("count DESC, name ASC")
13
- .map { |e| {"name" => e.name, "count" => e.count} }
14
- Alchemy::Element.definitions.map do |definition|
15
- count = res.find { |r| r["name"] == definition["name"] }&.fetch("count") || 0
16
- definition["count"] = count
17
- definition
18
- end.sort_by { |r| -1 * r["count"] }
19
- end
20
-
21
- def pages_count_by_type
22
- res = Alchemy::Page.all
23
- .select("page_layout, COUNT(*) AS count")
24
- .group(:page_layout)
25
- .order("count DESC, page_layout ASC")
26
- .map { |p| {"page_layout" => p.page_layout, "count" => p.count} }
27
- Alchemy::PageLayout.all.reject { |page_layout| res.map { |p| p["page_layout"] }.include? page_layout["name"] }.sort_by { |d| d["name"] }.each do |page_layout|
28
- res << {"page_layout" => page_layout["name"], "count" => 0}
29
- end
30
- res
31
- end
32
- end
33
- end
34
- end
@@ -1,271 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "shoulda-matchers"
4
-
5
- RSpec.shared_examples_for "an essence" do
6
- let(:element) { Alchemy::Element.new }
7
- let(:content) { Alchemy::Content.new(name: "foo") }
8
- let(:content_definition) { { "name" => "foo" } }
9
-
10
- it "touches the element after save" do
11
- element = FactoryBot.create(:alchemy_element)
12
- content = FactoryBot.create(:alchemy_content, element: element, essence: essence, essence_type: essence.class.name)
13
-
14
- element.update_column(:updated_at, 3.days.ago)
15
- content.essence.update(essence.ingredient_column.to_sym => ingredient_value)
16
-
17
- element.reload
18
- expect(element.updated_at).to be_within(3.seconds).of(Time.current)
19
- end
20
-
21
- it "should have correct partial path" do
22
- underscored_essence = essence.class.name.demodulize.underscore
23
- expect(essence.to_partial_path).to eq("alchemy/essences/#{underscored_essence}_view")
24
- end
25
-
26
- describe "#definition" do
27
- subject { essence.definition }
28
-
29
- context "without element" do
30
- it { is_expected.to eq({}) }
31
- end
32
-
33
- context "with element" do
34
- before do
35
- expect(essence).to receive(:element).at_least(:once).and_return(element)
36
- end
37
-
38
- context "but without content definitions" do
39
- it { is_expected.to eq({}) }
40
- end
41
-
42
- context "and content definitions" do
43
- before do
44
- allow(essence).to receive(:content).and_return(content)
45
- end
46
-
47
- context "containing the content name" do
48
- before do
49
- expect(element).to receive(:content_definitions).at_least(:once).and_return([content_definition])
50
- end
51
-
52
- it "returns the content definition" do
53
- is_expected.to eq(content_definition)
54
- end
55
- end
56
-
57
- context "not containing the content name" do
58
- before do
59
- expect(element).to receive(:content_definitions).at_least(:once).and_return([])
60
- end
61
-
62
- it { is_expected.to eq({}) }
63
- end
64
- end
65
- end
66
- end
67
-
68
- describe "#ingredient=" do
69
- it "should set the value to ingredient column" do
70
- essence.ingredient = ingredient_value
71
- expect(essence.ingredient).to eq ingredient_value
72
- end
73
- end
74
-
75
- describe "validations" do
76
- context "without essence definition in elements.yml" do
77
- it "should return an empty array" do
78
- allow(essence).to receive(:definition).and_return nil
79
- expect(essence.validations).to eq([])
80
- end
81
- end
82
-
83
- context "without validations defined in essence definition in elements.yml" do
84
- it "should return an empty array" do
85
- allow(essence).to receive(:definition).and_return({ name: "test", type: "EssenceText" })
86
- expect(essence.validations).to eq([])
87
- end
88
- end
89
-
90
- describe "presence" do
91
- context "with string given as validation type" do
92
- before do
93
- allow(essence).to receive(:definition).and_return({ "validate" => ["presence"] })
94
- end
95
-
96
- context "when the ingredient column is empty" do
97
- before do
98
- essence.update(essence.ingredient_column.to_sym => nil)
99
- end
100
-
101
- it "should not be valid" do
102
- expect(essence).to_not be_valid
103
- end
104
- end
105
-
106
- context "when the ingredient column is not nil" do
107
- before do
108
- essence.update(essence.ingredient_column.to_sym => ingredient_value)
109
- end
110
-
111
- it "should be valid" do
112
- expect(essence).to be_valid
113
- end
114
- end
115
- end
116
-
117
- context "with hash given as validation type" do
118
- context "where the value is true" do
119
- before do
120
- allow(essence).to receive(:definition).and_return({ "validate" => [{ "presence" => true }] })
121
- end
122
-
123
- context "when the ingredient column is empty" do
124
- before do
125
- essence.update(essence.ingredient_column.to_sym => nil)
126
- end
127
-
128
- it "should not be valid" do
129
- expect(essence).to_not be_valid
130
- end
131
- end
132
-
133
- context "when the ingredient column is not nil" do
134
- before do
135
- essence.update(essence.ingredient_column.to_sym => ingredient_value)
136
- end
137
-
138
- it "should be valid" do
139
- expect(essence).to be_valid
140
- end
141
- end
142
- end
143
-
144
- context "where the value is false" do
145
- before do
146
- allow(essence).to receive(:definition).and_return({ "validate" => [{ "presence" => false }] })
147
- end
148
-
149
- it "should be valid" do
150
- expect(essence).to be_valid
151
- end
152
- end
153
- end
154
- end
155
-
156
- describe "uniqueness" do
157
- before do
158
- allow(essence).to receive(:element).and_return(FactoryBot.create(:alchemy_element))
159
- essence.update(essence.ingredient_column.to_sym => ingredient_value)
160
- end
161
-
162
- context "with string given as validation type" do
163
- before do
164
- expect(essence).to receive(:definition).at_least(:once).and_return({ "validate" => ["uniqueness"] })
165
- end
166
-
167
- context "when a duplicate exists" do
168
- before do
169
- allow(essence).to receive(:duplicates).and_return([essence.dup])
170
- end
171
-
172
- it "should not be valid" do
173
- expect(essence).to_not be_valid
174
- end
175
-
176
- context "when validated essence is not public" do
177
- before do
178
- expect(essence).to receive(:public?).and_return(false)
179
- end
180
-
181
- it "should be valid" do
182
- expect(essence).to be_valid
183
- end
184
- end
185
- end
186
-
187
- context "when no duplicate exists" do
188
- before do
189
- expect(essence).to receive(:duplicates).and_return([])
190
- end
191
-
192
- it "should be valid" do
193
- expect(essence).to be_valid
194
- end
195
- end
196
- end
197
-
198
- context "with hash given as validation type" do
199
- context "where the value is true" do
200
- before do
201
- expect(essence).to receive(:definition).at_least(:once).and_return({ "validate" => [{ "uniqueness" => true }] })
202
- end
203
-
204
- context "when a duplicate exists" do
205
- before do
206
- allow(essence).to receive(:duplicates).and_return([essence.dup])
207
- end
208
-
209
- it "should not be valid" do
210
- expect(essence).to_not be_valid
211
- end
212
-
213
- context "when validated essence is not public" do
214
- before do
215
- expect(essence).to receive(:public?).and_return(false)
216
- end
217
-
218
- it "should be valid" do
219
- expect(essence).to be_valid
220
- end
221
- end
222
- end
223
-
224
- context "when no duplicate exists" do
225
- before do
226
- expect(essence).to receive(:duplicates).and_return([])
227
- end
228
-
229
- it "should be valid" do
230
- expect(essence).to be_valid
231
- end
232
- end
233
- end
234
-
235
- context "where the value is false" do
236
- before do
237
- allow(essence).to receive(:definition).and_return({ "validate" => [{ "uniqueness" => false }] })
238
- end
239
-
240
- it "should be valid" do
241
- expect(essence).to be_valid
242
- end
243
- end
244
- end
245
- end
246
-
247
- describe "#acts_as_essence?" do
248
- it "should return true" do
249
- expect(essence.acts_as_essence?).to be_truthy
250
- end
251
- end
252
- end
253
-
254
- context "delegations" do
255
- it { should delegate_method(:restricted?).to(:page) }
256
- it { should delegate_method(:public?).to(:element) }
257
- end
258
-
259
- describe "essence relations" do
260
- let(:page) { FactoryBot.create(:alchemy_page, :restricted) }
261
- let(:element) { FactoryBot.create(:alchemy_element) }
262
-
263
- it "registers itself on page as essence relation" do
264
- expect(page.respond_to?(essence.class.model_name.route_key)).to be(true)
265
- end
266
-
267
- it "registers itself on element as essence relation" do
268
- expect(element.respond_to?(essence.class.model_name.route_key)).to be(true)
269
- end
270
- end
271
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- FactoryBot.define do
4
- factory :alchemy_content, class: "Alchemy::Content" do
5
- name { "text" }
6
- essence_type { "Alchemy::EssenceText" }
7
- association :essence, factory: :alchemy_essence_text
8
- association :element, factory: :alchemy_element
9
-
10
- trait :essence_file do
11
- essence_type { "Alchemy::EssenceFile" }
12
- association :essence, factory: :alchemy_essence_file
13
- end
14
-
15
- trait :essence_picture do
16
- essence_type { "Alchemy::EssencePicture" }
17
- association :essence, factory: :alchemy_essence_picture
18
- end
19
- end
20
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- FactoryBot.define do
4
- factory :alchemy_essence_audio, class: "Alchemy::EssenceAudio" do
5
- attachment factory: :alchemy_attachment
6
- end
7
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- FactoryBot.define do
4
- factory :alchemy_essence_file, class: "Alchemy::EssenceFile" do
5
- attachment factory: :alchemy_attachment
6
- end
7
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- FactoryBot.define do
4
- factory :alchemy_essence_page, class: "Alchemy::EssencePage" do
5
- page factory: :alchemy_page
6
- end
7
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- FactoryBot.define do
4
- factory :alchemy_essence_picture, class: "Alchemy::EssencePicture" do
5
- picture factory: :alchemy_picture
6
-
7
- trait :with_content do
8
- association :content, factory: :alchemy_content
9
- end
10
- end
11
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- FactoryBot.define do
4
- factory :alchemy_essence_text, class: "Alchemy::EssenceText" do
5
- body { "This is a headline" }
6
- end
7
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- FactoryBot.define do
4
- factory :alchemy_essence_video, class: "Alchemy::EssenceVideo" do
5
- attachment factory: :alchemy_attachment
6
- end
7
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "tasks/harden_gutentag_migrations"
4
-
5
- module Alchemy
6
- class Upgrader::FivePointZero < Upgrader
7
- class << self
8
- def install_gutentag_migrations
9
- desc "Install Gutentag migrations"
10
- Rake::Task["gutentag:install:migrations"].invoke
11
- Alchemy::Upgrader::Tasks::HardenGutentagMigrations.new.patch_migrations
12
- Rake::Task["db:migrate"].invoke
13
- end
14
-
15
- def remove_layout_roots
16
- desc "Remove layout root pages"
17
- layout_roots = Alchemy::Page.where(layoutpage: true).where("name LIKE 'Layoutroot for%'")
18
- if layout_roots.size.positive?
19
- log "Removing #{layout_roots.size} layout root pages."
20
- layout_roots.delete_all
21
- Alchemy::Page.where(layoutpage: true).update_all(parent_id: nil)
22
- log "Done.", :success
23
- else
24
- log "No layout root pages found.", :skip
25
- end
26
- end
27
-
28
- def remove_root_page
29
- desc "Remove root page"
30
- root_page = Alchemy::Page.find_by(parent_id: nil, name: "Root")
31
- if root_page
32
- Alchemy::Page.where(parent_id: root_page.id).update_all(parent_id: nil)
33
- root_page.delete
34
- log "Done.", :success
35
- else
36
- log "Root page not found.", :skip
37
- end
38
- end
39
- end
40
- end
41
- end