alchemy_cms 5.2.0 → 6.0.0.b3

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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -14
  3. data/.gitignore +0 -1
  4. data/.hound.yml +1 -1
  5. data/.rubocop.yml +46 -4
  6. data/CHANGELOG.md +114 -5
  7. data/Gemfile +8 -1
  8. data/README.md +5 -2
  9. data/alchemy_cms.gemspec +78 -65
  10. data/app/assets/javascripts/alchemy/admin.js +0 -2
  11. data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +0 -27
  12. data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +2 -1
  13. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +1 -1
  14. data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +0 -25
  15. data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +1 -1
  16. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +2 -0
  17. data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +1 -1
  18. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +3 -1
  19. data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +1 -1
  20. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +40 -27
  21. data/app/assets/javascripts/alchemy/templates/node_folder.hbs +1 -1
  22. data/app/assets/stylesheets/alchemy/_extends.scss +15 -2
  23. data/app/assets/stylesheets/alchemy/admin.scss +1 -1
  24. data/app/assets/stylesheets/alchemy/archive.scss +20 -5
  25. data/app/assets/stylesheets/alchemy/buttons.scss +0 -4
  26. data/app/assets/stylesheets/alchemy/elements.scss +73 -61
  27. data/app/assets/stylesheets/alchemy/images.scss +8 -0
  28. data/app/assets/stylesheets/alchemy/node-select.scss +4 -3
  29. data/app/assets/stylesheets/alchemy/page-select.scss +1 -0
  30. data/app/controllers/alchemy/admin/attachments_controller.rb +8 -4
  31. data/app/controllers/alchemy/admin/base_controller.rb +5 -7
  32. data/app/controllers/alchemy/admin/elements_controller.rb +59 -34
  33. data/app/controllers/alchemy/admin/essence_audios_controller.rb +30 -0
  34. data/app/controllers/alchemy/admin/essence_files_controller.rb +0 -14
  35. data/app/controllers/alchemy/admin/essence_pictures_controller.rb +8 -79
  36. data/app/controllers/alchemy/admin/essence_videos_controller.rb +33 -0
  37. data/app/controllers/alchemy/admin/ingredients_controller.rb +30 -0
  38. data/app/controllers/alchemy/admin/layoutpages_controller.rb +0 -1
  39. data/app/controllers/alchemy/admin/pages_controller.rb +7 -22
  40. data/app/controllers/alchemy/admin/pictures_controller.rb +56 -17
  41. data/app/controllers/alchemy/admin/resources_controller.rb +84 -10
  42. data/app/controllers/alchemy/api/elements_controller.rb +13 -4
  43. data/app/controllers/alchemy/api/pages_controller.rb +4 -3
  44. data/app/controllers/concerns/alchemy/admin/archive_overlay.rb +13 -3
  45. data/app/controllers/concerns/alchemy/admin/crop_action.rb +26 -0
  46. data/app/decorators/alchemy/element_editor.rb +26 -1
  47. data/app/decorators/alchemy/ingredient_editor.rb +158 -0
  48. data/app/helpers/alchemy/admin/elements_helper.rb +1 -0
  49. data/app/helpers/alchemy/admin/essences_helper.rb +1 -1
  50. data/app/helpers/alchemy/admin/ingredients_helper.rb +42 -0
  51. data/app/helpers/alchemy/elements_block_helper.rb +23 -6
  52. data/app/helpers/alchemy/elements_helper.rb +12 -5
  53. data/app/helpers/alchemy/pages_helper.rb +3 -11
  54. data/app/jobs/alchemy/base_job.rb +11 -0
  55. data/app/jobs/alchemy/publish_page_job.rb +11 -0
  56. data/app/models/alchemy/attachment.rb +24 -7
  57. data/app/models/alchemy/content.rb +1 -6
  58. data/app/models/alchemy/content/factory.rb +23 -27
  59. data/app/models/alchemy/element.rb +39 -72
  60. data/app/models/alchemy/element/definitions.rb +29 -27
  61. data/app/models/alchemy/element/element_contents.rb +131 -122
  62. data/app/models/alchemy/element/element_essences.rb +111 -98
  63. data/app/models/alchemy/element/element_ingredients.rb +184 -0
  64. data/app/models/alchemy/element/presenters.rb +104 -85
  65. data/app/models/alchemy/elements_repository.rb +126 -0
  66. data/app/models/alchemy/essence_audio.rb +12 -0
  67. data/app/models/alchemy/essence_headline.rb +40 -0
  68. data/app/models/alchemy/essence_picture.rb +4 -116
  69. data/app/models/alchemy/essence_richtext.rb +12 -0
  70. data/app/models/alchemy/essence_video.rb +12 -0
  71. data/app/models/alchemy/image_cropper_settings.rb +87 -0
  72. data/app/models/alchemy/ingredient.rb +183 -0
  73. data/app/models/alchemy/ingredient_validator.rb +97 -0
  74. data/app/models/alchemy/ingredients/audio.rb +29 -0
  75. data/app/models/alchemy/ingredients/boolean.rb +21 -0
  76. data/app/models/alchemy/ingredients/datetime.rb +20 -0
  77. data/app/models/alchemy/ingredients/file.rb +30 -0
  78. data/app/models/alchemy/ingredients/headline.rb +42 -0
  79. data/app/models/alchemy/ingredients/html.rb +19 -0
  80. data/app/models/alchemy/ingredients/link.rb +16 -0
  81. data/app/models/alchemy/ingredients/node.rb +23 -0
  82. data/app/models/alchemy/ingredients/page.rb +23 -0
  83. data/app/models/alchemy/ingredients/picture.rb +41 -0
  84. data/app/models/alchemy/ingredients/richtext.rb +57 -0
  85. data/app/models/alchemy/ingredients/select.rb +10 -0
  86. data/app/models/alchemy/ingredients/text.rb +17 -0
  87. data/app/models/alchemy/ingredients/video.rb +33 -0
  88. data/app/models/alchemy/language.rb +0 -11
  89. data/app/models/alchemy/page.rb +76 -33
  90. data/app/models/alchemy/page/fixed_attributes.rb +53 -51
  91. data/app/models/alchemy/page/page_elements.rb +186 -205
  92. data/app/models/alchemy/page/page_naming.rb +66 -64
  93. data/app/models/alchemy/page/page_natures.rb +139 -142
  94. data/app/models/alchemy/page/page_scopes.rb +117 -102
  95. data/app/models/alchemy/page/publisher.rb +50 -0
  96. data/app/models/alchemy/page/url_path.rb +1 -1
  97. data/app/models/alchemy/page_version.rb +58 -0
  98. data/app/models/alchemy/picture.rb +18 -40
  99. data/app/models/alchemy/picture/calculations.rb +2 -8
  100. data/app/models/alchemy/picture/preprocessor.rb +2 -0
  101. data/app/models/alchemy/picture/transformations.rb +24 -96
  102. data/app/models/concerns/alchemy/picture_thumbnails.rb +181 -0
  103. data/app/models/concerns/alchemy/touch_elements.rb +2 -2
  104. data/app/presenters/alchemy/picture_view.rb +88 -0
  105. data/app/serializers/alchemy/element_serializer.rb +5 -0
  106. data/app/serializers/alchemy/page_tree_serializer.rb +3 -2
  107. data/app/services/alchemy/delete_elements.rb +44 -0
  108. data/app/services/alchemy/duplicate_element.rb +56 -0
  109. data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +2 -3
  110. data/app/views/alchemy/admin/attachments/_file_to_assign.html.erb +3 -3
  111. data/app/views/alchemy/admin/attachments/assign.js.erb +11 -0
  112. data/app/views/alchemy/admin/attachments/index.html.erb +2 -3
  113. data/app/views/alchemy/admin/crop.html.erb +36 -0
  114. data/app/views/alchemy/admin/elements/_element.html.erb +14 -10
  115. data/app/views/alchemy/admin/elements/{_element_footer.html.erb → _footer.html.erb} +0 -0
  116. data/app/views/alchemy/admin/elements/{_new_element_form.html.erb → _form.html.erb} +1 -1
  117. data/app/views/alchemy/admin/elements/{_element_header.html.erb → _header.html.erb} +1 -1
  118. data/app/views/alchemy/admin/elements/{_element_toolbar.html.erb → _toolbar.html.erb} +5 -6
  119. data/app/views/alchemy/admin/elements/create.js.erb +1 -1
  120. data/app/views/alchemy/admin/elements/{trash.js.erb → destroy.js.erb} +2 -6
  121. data/app/views/alchemy/admin/elements/fold.js.erb +2 -2
  122. data/app/views/alchemy/admin/elements/new.html.erb +3 -3
  123. data/app/views/alchemy/admin/elements/order.js.erb +0 -17
  124. data/app/views/alchemy/admin/elements/update.js.erb +3 -2
  125. data/app/views/alchemy/admin/essence_audios/edit.html.erb +7 -0
  126. data/app/views/alchemy/admin/essence_pictures/update.js.erb +0 -1
  127. data/app/views/alchemy/admin/essence_videos/edit.html.erb +11 -0
  128. data/app/views/alchemy/admin/ingredients/_audio_fields.html.erb +4 -0
  129. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +18 -0
  130. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +25 -0
  131. data/app/views/alchemy/admin/ingredients/_video_fields.html.erb +8 -0
  132. data/app/views/alchemy/admin/ingredients/edit.html.erb +4 -0
  133. data/app/views/alchemy/admin/layoutpages/edit.html.erb +0 -5
  134. data/app/views/alchemy/admin/nodes/_node.html.erb +2 -2
  135. data/app/views/alchemy/admin/pages/_anchor_link.html.erb +1 -1
  136. data/app/views/alchemy/admin/pages/_external_link.html.erb +1 -1
  137. data/app/views/alchemy/admin/pages/_file_link.html.erb +1 -1
  138. data/app/views/alchemy/admin/pages/_form.html.erb +0 -6
  139. data/app/views/alchemy/admin/pages/_internal_link.html.erb +1 -1
  140. data/app/views/alchemy/admin/pages/_tinymce_custom_config.html.erb +5 -2
  141. data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -1
  142. data/app/views/alchemy/admin/pages/edit.html.erb +36 -24
  143. data/app/views/alchemy/admin/pages/index.html.erb +2 -9
  144. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +2 -4
  145. data/app/views/alchemy/admin/partials/_routes.html.erb +7 -11
  146. data/app/views/alchemy/admin/partials/_search_form.html.erb +9 -0
  147. data/app/views/alchemy/admin/pictures/_archive.html.erb +1 -1
  148. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +1 -1
  149. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +5 -7
  150. data/app/views/alchemy/admin/pictures/_infos.html.erb +0 -1
  151. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +4 -4
  152. data/app/views/alchemy/admin/pictures/assign.js.erb +10 -0
  153. data/app/views/alchemy/admin/pictures/index.html.erb +8 -3
  154. data/app/views/alchemy/admin/resources/_filter.html.erb +12 -0
  155. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +14 -17
  156. data/app/views/alchemy/admin/resources/_form.html.erb +3 -0
  157. data/app/views/alchemy/admin/resources/_table_header.html.erb +15 -0
  158. data/app/views/alchemy/admin/resources/index.html.erb +3 -11
  159. data/app/views/alchemy/essences/_essence_audio_editor.html.erb +4 -0
  160. data/app/views/alchemy/essences/_essence_audio_view.html.erb +15 -0
  161. data/app/views/alchemy/essences/_essence_file_editor.html.erb +15 -6
  162. data/app/views/alchemy/essences/_essence_headline_editor.html.erb +36 -0
  163. data/app/views/alchemy/essences/_essence_headline_view.html.erb +10 -0
  164. data/app/views/alchemy/essences/_essence_link_editor.html.erb +8 -4
  165. data/app/views/alchemy/essences/_essence_picture_editor.html.erb +27 -12
  166. data/app/views/alchemy/essences/_essence_picture_view.html.erb +3 -3
  167. data/app/views/alchemy/essences/_essence_text_editor.html.erb +12 -4
  168. data/app/views/alchemy/essences/_essence_video_editor.html.erb +4 -0
  169. data/app/views/alchemy/essences/_essence_video_view.html.erb +18 -0
  170. data/app/views/alchemy/essences/shared/_essence_picture_tools.html.erb +21 -16
  171. data/app/views/alchemy/essences/shared/_linkable_essence_tools.html.erb +2 -2
  172. data/app/views/alchemy/ingredients/_audio_editor.html.erb +5 -0
  173. data/app/views/alchemy/ingredients/_audio_view.html.erb +14 -0
  174. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +11 -0
  175. data/app/views/alchemy/ingredients/_boolean_view.html.erb +1 -0
  176. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +17 -0
  177. data/app/views/alchemy/ingredients/_datetime_view.html.erb +9 -0
  178. data/app/views/alchemy/ingredients/_file_editor.html.erb +52 -0
  179. data/app/views/alchemy/ingredients/_file_view.html.erb +17 -0
  180. data/app/views/alchemy/ingredients/_headline_editor.html.erb +30 -0
  181. data/app/views/alchemy/ingredients/_headline_view.html.erb +9 -0
  182. data/app/views/alchemy/ingredients/_html_editor.html.erb +8 -0
  183. data/app/views/alchemy/ingredients/_html_view.html.erb +1 -0
  184. data/app/views/alchemy/ingredients/_link_editor.html.erb +24 -0
  185. data/app/views/alchemy/ingredients/_link_view.html.erb +9 -0
  186. data/app/views/alchemy/ingredients/_node_editor.html.erb +26 -0
  187. data/app/views/alchemy/ingredients/_node_view.html.erb +1 -0
  188. data/app/views/alchemy/ingredients/_page_editor.html.erb +25 -0
  189. data/app/views/alchemy/ingredients/_page_view.html.erb +4 -0
  190. data/app/views/alchemy/ingredients/_picture_editor.html.erb +60 -0
  191. data/app/views/alchemy/ingredients/_picture_view.html.erb +5 -0
  192. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +12 -0
  193. data/app/views/alchemy/ingredients/_richtext_view.html.erb +3 -0
  194. data/app/views/alchemy/ingredients/_select_editor.html.erb +30 -0
  195. data/app/views/alchemy/ingredients/_select_view.html.erb +1 -0
  196. data/app/views/alchemy/ingredients/_text_editor.html.erb +20 -0
  197. data/app/views/alchemy/ingredients/_text_view.html.erb +16 -0
  198. data/app/views/alchemy/ingredients/_video_editor.html.erb +5 -0
  199. data/app/views/alchemy/ingredients/_video_view.html.erb +17 -0
  200. data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +20 -0
  201. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +57 -0
  202. data/config/brakeman.ignore +66 -159
  203. data/config/initializers/dragonfly.rb +10 -0
  204. data/config/locales/alchemy.en.yml +108 -64
  205. data/config/routes.rb +17 -22
  206. data/db/migrate/20201207131309_create_page_versions.rb +19 -0
  207. data/db/migrate/20201207135820_add_page_version_id_to_alchemy_elements.rb +76 -0
  208. data/db/migrate/20210205143548_rename_public_on_and_public_until_on_alchemy_pages.rb +10 -0
  209. data/db/migrate/20210326105046_add_sanitized_body_to_alchemy_essence_richtexts.rb +7 -0
  210. data/db/migrate/20210406093436_add_alchemy_essence_headlines.rb +12 -0
  211. data/db/migrate/20210506135919_create_essence_audios.rb +19 -0
  212. data/db/migrate/20210506140258_create_essence_videos.rb +23 -0
  213. data/db/migrate/20210508091432_create_alchemy_ingredients.rb +22 -0
  214. data/lib/alchemy/admin/preview_url.rb +2 -0
  215. data/lib/alchemy/deprecation.rb +1 -1
  216. data/lib/alchemy/dragonfly/processors/auto_orient.rb +18 -0
  217. data/lib/alchemy/dragonfly/processors/crop_resize.rb +35 -0
  218. data/lib/alchemy/elements_finder.rb +14 -60
  219. data/lib/alchemy/essence.rb +1 -2
  220. data/lib/alchemy/forms/builder.rb +21 -1
  221. data/lib/alchemy/hints.rb +8 -4
  222. data/lib/alchemy/page_layout.rb +0 -13
  223. data/lib/alchemy/permissions.rb +30 -29
  224. data/lib/alchemy/resource.rb +13 -3
  225. data/lib/alchemy/resource_filter.rb +40 -0
  226. data/lib/alchemy/resources_helper.rb +1 -16
  227. data/lib/alchemy/tasks/tidy.rb +29 -0
  228. data/lib/alchemy/test_support.rb +2 -11
  229. data/lib/alchemy/test_support/essence_shared_examples.rb +0 -1
  230. data/lib/alchemy/test_support/factories/element_factory.rb +8 -8
  231. data/lib/alchemy/test_support/factories/essence_audio_factory.rb +7 -0
  232. data/lib/alchemy/test_support/factories/essence_video_factory.rb +7 -0
  233. data/lib/alchemy/test_support/factories/ingredient_factory.rb +25 -0
  234. data/lib/alchemy/test_support/factories/page_factory.rb +20 -1
  235. data/lib/alchemy/test_support/factories/page_version_factory.rb +23 -0
  236. data/lib/alchemy/test_support/having_crop_action_examples.rb +170 -0
  237. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +646 -0
  238. data/lib/alchemy/test_support/shared_ingredient_editor_examples.rb +21 -0
  239. data/lib/alchemy/test_support/shared_ingredient_examples.rb +75 -0
  240. data/lib/alchemy/tinymce.rb +17 -0
  241. data/lib/alchemy/upgrader/six_point_zero.rb +21 -0
  242. data/lib/alchemy/upgrader/tasks/add_page_versions.rb +33 -0
  243. data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +62 -0
  244. data/lib/alchemy/version.rb +1 -1
  245. data/lib/alchemy_cms.rb +1 -0
  246. data/lib/generators/alchemy/elements/elements_generator.rb +1 -0
  247. data/lib/generators/alchemy/elements/templates/view.html.erb +9 -0
  248. data/lib/generators/alchemy/elements/templates/view.html.haml +9 -0
  249. data/lib/generators/alchemy/elements/templates/view.html.slim +9 -0
  250. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +38 -0
  251. data/lib/generators/alchemy/ingredient/templates/editor.html.erb +14 -0
  252. data/lib/generators/alchemy/ingredient/templates/model.rb.tt +13 -0
  253. data/lib/generators/alchemy/ingredient/templates/view.html.erb +1 -0
  254. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +1 -1
  255. data/lib/generators/alchemy/menus/templates/node.html.erb +1 -1
  256. data/lib/generators/alchemy/menus/templates/node.html.haml +1 -1
  257. data/lib/generators/alchemy/menus/templates/node.html.slim +1 -1
  258. data/lib/generators/alchemy/menus/templates/wrapper.html.erb +1 -1
  259. data/lib/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
  260. data/lib/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
  261. data/lib/tasks/alchemy/thumbnails.rake +4 -2
  262. data/lib/tasks/alchemy/tidy.rake +12 -0
  263. data/lib/tasks/alchemy/upgrade.rake +26 -0
  264. data/package.json +3 -2
  265. data/package/admin.js +11 -1
  266. data/package/src/__tests__/i18n.spec.js +23 -0
  267. data/package/src/file_editors.js +28 -0
  268. data/package/src/i18n.js +1 -3
  269. data/package/src/image_cropper.js +103 -0
  270. data/package/src/image_loader.js +58 -0
  271. data/package/src/node_tree.js +5 -5
  272. data/package/src/picture_editors.js +169 -0
  273. data/package/src/utils/__tests__/ajax.spec.js +20 -12
  274. data/package/src/utils/ajax.js +8 -3
  275. data/vendor/assets/javascripts/jquery_plugins/jquery.Jcrop.min.js +3 -18
  276. data/vendor/assets/stylesheets/jquery.Jcrop.min.scss +2 -28
  277. metadata +292 -55
  278. data/app/assets/javascripts/alchemy/alchemy.image_cropper.js.coffee +0 -44
  279. data/app/assets/javascripts/alchemy/alchemy.trash_window.js.coffee +0 -30
  280. data/app/assets/stylesheets/alchemy/trash.scss +0 -8
  281. data/app/controllers/alchemy/admin/trash_controller.rb +0 -44
  282. data/app/views/alchemy/admin/attachments/_filter_bar.html.erb +0 -29
  283. data/app/views/alchemy/admin/essence_files/assign.js.erb +0 -3
  284. data/app/views/alchemy/admin/essence_pictures/assign.js.erb +0 -4
  285. data/app/views/alchemy/admin/essence_pictures/crop.html.erb +0 -48
  286. data/app/views/alchemy/admin/pictures/_filter_bar.html.erb +0 -30
  287. data/app/views/alchemy/admin/trash/clear.js.erb +0 -4
  288. data/app/views/alchemy/admin/trash/index.html.erb +0 -31
  289. data/lib/alchemy/test_support/factories.rb +0 -16
@@ -3,110 +3,125 @@
3
3
  module Alchemy
4
4
  # ActiveRecord scopes for Alchemy::Page
5
5
  #
6
- module Page::PageScopes
7
- extend ActiveSupport::Concern
8
-
9
- included do
10
- # All language root pages
11
- #
12
- scope :language_roots, -> { where(language_root: true) }
13
-
14
- # All layout pages
15
- #
16
- scope :layoutpages, -> { where(layoutpage: true) }
17
-
18
- # All locked pages
19
- #
20
- scope :locked, -> { where.not(locked_at: nil).where.not(locked_by: nil) }
21
-
22
- # All pages locked by given user
23
- #
24
- scope :locked_by,
25
- ->(user) {
26
- if user.class.respond_to? :primary_key
27
- locked.where(locked_by: user.send(user.class.primary_key))
28
- end
29
- }
30
-
31
- # All not locked pages
32
- #
33
- scope :not_locked, -> { where(locked_at: nil, locked_by: nil) }
34
-
35
- # All not restricted pages
36
- #
37
- scope :not_restricted, -> { where(restricted: false) }
38
-
39
- # All restricted pages
40
- #
41
- scope :restricted, -> { where(restricted: true) }
42
-
43
- # All pages that are a published language root
44
- #
45
- scope :public_language_roots,
46
- -> {
47
- published.language_roots.where(
48
- language_code: Language.published.pluck(:language_code),
49
- )
50
- }
51
-
52
- # Last 5 pages that where recently edited by given user
53
- #
54
- scope :all_last_edited_from,
55
- ->(user) {
56
- where(updater_id: user.id).order("updated_at DESC").limit(5)
57
- }
58
-
59
- # Returns all pages that have the given +language_id+
60
- #
61
- scope :with_language, ->(language_id) { where(language_id: language_id) }
62
-
63
- # Returns all content pages.
64
- #
65
- scope :contentpages, -> { where(layoutpage: [false, nil]) }
66
-
67
- # Returns all public contentpages that are not locked.
68
- #
69
- # Used for flushing all pages caches at once.
70
- #
71
- scope :flushables, -> { not_locked.published.contentpages }
72
-
73
- # Returns all layoutpages that are not locked.
74
- #
75
- # Used for flushing all pages caches at once.
76
- #
77
- scope :flushable_layoutpages, -> { not_locked.layoutpages }
78
-
79
- # All searchable pages
80
- #
81
- scope :searchables, -> { not_restricted.published.contentpages }
82
-
83
- # All pages from +Alchemy::Site.current+
84
- #
85
- scope :from_current_site,
86
- -> {
87
- where(Language.table_name => { site_id: Site.current || Site.default }).joins(:language)
88
- }
89
-
90
- # All pages for xml sitemap
91
- #
92
- scope :sitemap, -> { from_current_site.published.contentpages.where(sitemap: true) }
93
- end
94
-
95
- module ClassMethods
96
- # All public pages
97
- #
98
- def published
99
- where("#{table_name}.public_on <= :time AND " \
100
- "(#{table_name}.public_until IS NULL " \
101
- "OR #{table_name}.public_until >= :time)", time: Time.current)
6
+ class Page < BaseRecord
7
+ module PageScopes
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ # All pages of given page layout
12
+ #
13
+ scope :by_page_layout, ->(page_layout) { where(page_layout: page_layout) }
14
+
15
+ # All language root pages
16
+ #
17
+ scope :language_roots, -> { where(language_root: true) }
18
+
19
+ # All layout pages
20
+ #
21
+ scope :layoutpages, -> { where(layoutpage: true) }
22
+
23
+ # All locked pages
24
+ #
25
+ scope :locked, -> { where.not(locked_at: nil).where.not(locked_by: nil) }
26
+
27
+ # All pages locked by given user
28
+ #
29
+ scope :locked_by,
30
+ ->(user) {
31
+ if user.class.respond_to? :primary_key
32
+ locked.where(locked_by: user.send(user.class.primary_key))
33
+ end
34
+ }
35
+
36
+ # All not locked pages
37
+ #
38
+ scope :not_locked, -> { where(locked_at: nil, locked_by: nil) }
39
+
40
+ # All not restricted pages
41
+ #
42
+ scope :not_restricted, -> { where(restricted: false) }
43
+
44
+ # All restricted pages
45
+ #
46
+ scope :restricted, -> { where(restricted: true) }
47
+
48
+ # All public pages
49
+ #
50
+ scope :published,
51
+ -> {
52
+ joins(:language, :versions).
53
+ merge(Language.published).
54
+ merge(PageVersion.public_on(Time.current))
55
+ }
56
+
57
+ # All pages that are a published language root
58
+ #
59
+ scope :public_language_roots,
60
+ -> {
61
+ published.language_roots.where(
62
+ language_code: Language.published.pluck(:language_code),
63
+ )
64
+ }
65
+
66
+ # Last 5 pages that where recently edited by given user
67
+ #
68
+ scope :all_last_edited_from,
69
+ ->(user) {
70
+ where(updater_id: user.id).order("updated_at DESC").limit(5)
71
+ }
72
+
73
+ # Returns all pages that have the given +language_id+
74
+ #
75
+ scope :with_language, ->(language_id) { where(language_id: language_id) }
76
+
77
+ # Returns all content pages.
78
+ #
79
+ scope :contentpages, -> { where(layoutpage: [false, nil]) }
80
+
81
+ # Returns all public contentpages that are not locked.
82
+ #
83
+ # Used for flushing all pages caches at once.
84
+ #
85
+ scope :flushables, -> { not_locked.published.contentpages }
86
+
87
+ # Returns all layoutpages that are not locked.
88
+ #
89
+ # Used for flushing all pages caches at once.
90
+ #
91
+ scope :flushable_layoutpages, -> { not_locked.layoutpages }
92
+
93
+ # All searchable pages
94
+ #
95
+ scope :searchables, -> { not_restricted.published.contentpages }
96
+
97
+ # All pages from +Alchemy::Site.current+
98
+ #
99
+ scope :from_current_site,
100
+ -> {
101
+ where(Language.table_name => { site_id: Site.current || Site.default }).joins(:language)
102
+ }
103
+
104
+ # All pages for xml sitemap
105
+ #
106
+ scope :sitemap, -> { from_current_site.published.contentpages.where(sitemap: true) }
102
107
  end
103
108
 
104
- # All not public pages
105
- #
106
- def not_public
107
- where("#{table_name}.public_on IS NULL OR " \
108
- "#{table_name}.public_on >= :time OR " \
109
- "#{table_name}.public_until <= :time", time: Time.current)
109
+ module ClassMethods
110
+ # All pages that do not have any public version
111
+ #
112
+ def not_public(time = Time.current)
113
+ where <<~SQL
114
+ alchemy_pages.id NOT IN (
115
+ SELECT alchemy_page_versions.page_id
116
+ FROM alchemy_page_versions
117
+ WHERE alchemy_page_versions.public_on <= '#{connection.quoted_date(time)}'
118
+ AND (
119
+ alchemy_page_versions.public_until IS NULL
120
+ OR alchemy_page_versions.public_until >= '#{connection.quoted_date(time)}'
121
+ )
122
+ )
123
+ SQL
124
+ end
110
125
  end
111
126
  end
112
127
  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
@@ -29,7 +29,7 @@ module Alchemy
29
29
  def call
30
30
  if @page.language_root?
31
31
  language_root_path
32
- elsif @site.languages.select(&:public?).length > 1
32
+ elsif @site.languages.count(&:public?) > 1
33
33
  page_path_with_language_prefix
34
34
  else
35
35
  page_path_with_leading_slash
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class PageVersion < BaseRecord
5
+ belongs_to :page, class_name: "Alchemy::Page", inverse_of: :versions
6
+
7
+ has_many :elements, -> { order(:position) },
8
+ class_name: "Alchemy::Element",
9
+ inverse_of: :page_version
10
+
11
+ scope :drafts, -> { where(public_on: nil).order(updated_at: :desc) }
12
+ scope :published, -> { where.not(public_on: nil).order(public_on: :desc) }
13
+
14
+ def self.public_on(time = Time.current)
15
+ where("#{table_name}.public_on <= :time AND " \
16
+ "(#{table_name}.public_until IS NULL " \
17
+ "OR #{table_name}.public_until >= :time)", time: time)
18
+ end
19
+
20
+ before_destroy :delete_elements
21
+
22
+ # Determines if this version is public
23
+ #
24
+ # Takes the two timestamps +public_on+ and +public_until+
25
+ # and returns true if the time given (+Time.current+ per default)
26
+ # is in this timespan.
27
+ #
28
+ # @param time [DateTime] (Time.current)
29
+ # @returns Boolean
30
+ def public?(time = Time.current)
31
+ already_public_for?(time) && still_public_for?(time)
32
+ end
33
+
34
+ # Determines if this version is already public for given time
35
+ # @param time [DateTime] (Time.current)
36
+ # @returns Boolean
37
+ def already_public_for?(time = Time.current)
38
+ !public_on.nil? && public_on <= time
39
+ end
40
+
41
+ # Determines if this version is still public for given time
42
+ # @param time [DateTime] (Time.current)
43
+ # @returns Boolean
44
+ def still_public_for?(time = Time.current)
45
+ public_until.nil? || public_until >= time
46
+ end
47
+
48
+ def element_repository
49
+ ElementsRepository.new(elements.includes({ contents: :essence }, :tags))
50
+ end
51
+
52
+ private
53
+
54
+ def delete_elements
55
+ DeleteElements.new(elements).call
56
+ end
57
+ end
58
+ end
@@ -86,12 +86,14 @@ module Alchemy
86
86
  dragonfly_accessor :image_file, app: :alchemy_pictures do
87
87
  # Preprocess after uploading the picture
88
88
  after_assign do |image|
89
- self.class.preprocessor_class.new(image).call
89
+ if has_convertible_format?
90
+ self.class.preprocessor_class.new(image).call
91
+ end
90
92
  end
91
93
  end
92
94
 
93
95
  # Create important thumbnails upfront
94
- after_create -> { PictureThumb.generate_thumbs!(self) }
96
+ after_create -> { PictureThumb.generate_thumbs!(self) if has_convertible_format? }
95
97
 
96
98
  # We need to define this method here to have it available in the validations below.
97
99
  class << self
@@ -114,6 +116,7 @@ module Alchemy
114
116
  scope :recent, -> { where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) }
115
117
  scope :deletable, -> { where("#{table_name}.id NOT IN (SELECT picture_id FROM #{EssencePicture.table_name})") }
116
118
  scope :without_tag, -> { left_outer_joins(:taggings).where(gutentag_taggings: { id: nil }) }
119
+ scope :by_file_format, ->(format) { where(image_file_format: format) }
117
120
 
118
121
  # Class methods
119
122
 
@@ -132,6 +135,19 @@ module Alchemy
132
135
  @_url_class = klass
133
136
  end
134
137
 
138
+ def alchemy_resource_filters
139
+ [
140
+ {
141
+ name: :by_file_format,
142
+ values: distinct.pluck(:image_file_format),
143
+ },
144
+ {
145
+ name: :misc,
146
+ values: %w(recent last_upload without_tag),
147
+ },
148
+ ]
149
+ end
150
+
135
151
  def searchable_alchemy_resource_attributes
136
152
  %w(name image_file_name)
137
153
  end
@@ -142,34 +158,6 @@ module Alchemy
142
158
 
143
159
  Picture.where(upload_hash: last_picture.upload_hash)
144
160
  end
145
-
146
- def search_by(params, query, per_page = nil)
147
- pictures = query.result
148
-
149
- if params[:tagged_with].present?
150
- pictures = pictures.tagged_with(params[:tagged_with])
151
- end
152
-
153
- if params[:filter].present?
154
- pictures = pictures.filtered_by(params[:filter])
155
- end
156
-
157
- if per_page
158
- pictures = pictures.page(params[:page] || 1).per(per_page)
159
- end
160
-
161
- pictures.order(:name)
162
- end
163
-
164
- def filtered_by(filter = "")
165
- case filter
166
- when "recent" then recent
167
- when "last_upload" then last_upload
168
- when "without_tag" then without_tag
169
- else
170
- all
171
- end
172
- end
173
161
  end
174
162
 
175
163
  # Instance methods
@@ -201,16 +189,6 @@ module Alchemy
201
189
  nil
202
190
  end
203
191
 
204
- def previous(params = {})
205
- query = Picture.ransack(params[:q])
206
- Picture.search_by(params, query).where("name < ?", name).last
207
- end
208
-
209
- def next(params = {})
210
- query = Picture.ransack(params[:q])
211
- Picture.search_by(params, query).where("name > ?", name).first
212
- end
213
-
214
192
  # Updates name and tag_list attributes.
215
193
  #
216
194
  # Used by +Admin::PicturesController#update_multiple+
@@ -27,15 +27,9 @@ module Alchemy
27
27
  # Given a string with an x, this function returns a Hash with point
28
28
  # :width and :height.
29
29
  #
30
- def sizes_from_string(string = "0x0")
31
- string = "0x0" if string.nil? || string.empty?
30
+ def sizes_from_string(string)
31
+ width, height = string.to_s.split("x", 2).map(&:to_i)
32
32
 
33
- raise ArgumentError unless string =~ /(\d*x\d*)/
34
-
35
- width, height = string.scan(/(\d*)x(\d*)/)[0].map(&:to_i)
36
-
37
- width = 0 if width.nil?
38
- height = 0 if height.nil?
39
33
  {
40
34
  width: width,
41
35
  height: height,