alchemy_cms 2.6.3 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.simplecov +14 -0
- data/.travis.yml +1 -1
- data/Gemfile +7 -6
- data/README.md +15 -5
- data/alchemy_cms.gemspec +3 -2
- data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +9 -17
- data/app/assets/javascripts/alchemy/alchemy.dirty.js.coffee +70 -0
- data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +80 -0
- data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +43 -19
- data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +3 -1
- data/app/assets/javascripts/alchemy/alchemy.js +4 -2
- data/app/assets/javascripts/alchemy/alchemy.onload.js.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.spinner.js.coffee +14 -0
- data/app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee.erb +96 -0
- data/app/assets/javascripts/alchemy/alchemy.translations.js.coffee +22 -0
- data/app/assets/javascripts/alchemy/alchemy.windows.js.coffee +28 -17
- data/app/assets/stylesheets/alchemy/base.scss +6 -0
- data/app/assets/stylesheets/alchemy/elements.scss +2 -28
- data/app/assets/stylesheets/alchemy/errors.scss +1 -1
- data/app/assets/stylesheets/alchemy/menubar.css.scss +2 -0
- data/app/assets/stylesheets/alchemy/sitemap.scss +21 -34
- data/app/assets/stylesheets/alchemy/tables.scss +13 -3
- data/app/controllers/alchemy/admin/attachments_controller.rb +10 -5
- data/app/controllers/alchemy/admin/base_controller.rb +19 -0
- data/app/controllers/alchemy/admin/contents_controller.rb +1 -4
- data/app/controllers/alchemy/admin/dashboard_controller.rb +2 -1
- data/app/controllers/alchemy/admin/elements_controller.rb +1 -1
- data/app/controllers/alchemy/admin/essence_files_controller.rb +1 -1
- data/app/controllers/alchemy/admin/essence_pictures_controller.rb +70 -56
- data/app/controllers/alchemy/admin/pages_controller.rb +37 -114
- data/app/controllers/alchemy/admin/pictures_controller.rb +5 -12
- data/app/controllers/alchemy/admin/resources_controller.rb +3 -1
- data/app/controllers/alchemy/admin/trash_controller.rb +1 -1
- data/app/controllers/alchemy/attachments_controller.rb +1 -1
- data/app/controllers/alchemy/base_controller.rb +3 -15
- data/app/controllers/alchemy/messages_controller.rb +4 -10
- data/app/controllers/alchemy/pages_controller.rb +6 -6
- data/app/controllers/alchemy/passwords_controller.rb +1 -1
- data/app/controllers/alchemy/user_sessions_controller.rb +1 -1
- data/app/helpers/alchemy/admin/base_helper.rb +49 -230
- data/app/helpers/alchemy/admin/contents_helper.rb +5 -1
- data/app/helpers/alchemy/admin/elements_helper.rb +19 -47
- data/app/helpers/alchemy/admin/essences_helper.rb +59 -17
- data/app/helpers/alchemy/admin/navigation_helper.rb +204 -0
- data/app/helpers/alchemy/admin/pages_helper.rb +22 -79
- data/app/helpers/alchemy/admin/pictures_helper.rb +1 -1
- data/app/helpers/alchemy/admin/tags_helper.rb +42 -0
- data/app/helpers/alchemy/base_helper.rb +0 -11
- data/app/helpers/alchemy/elements_helper.rb +48 -25
- data/app/helpers/alchemy/essences_helper.rb +0 -20
- data/app/helpers/alchemy/pages_helper.rb +18 -14
- data/app/helpers/alchemy/url_helper.rb +1 -0
- data/app/mailers/alchemy/messages.rb +4 -6
- data/app/models/alchemy/attachment.rb +3 -0
- data/app/models/alchemy/cell.rb +33 -35
- data/app/models/alchemy/content.rb +20 -111
- data/app/models/alchemy/content/factory.rb +188 -0
- data/app/models/alchemy/element.rb +51 -200
- data/app/models/alchemy/element/definitions.rb +52 -0
- data/app/models/alchemy/element/presenters.rb +87 -0
- data/app/models/alchemy/essence_date.rb +1 -1
- data/app/models/alchemy/essence_file.rb +6 -7
- data/app/models/alchemy/essence_picture.rb +19 -4
- data/app/models/alchemy/message.rb +18 -14
- data/app/models/alchemy/page.rb +120 -214
- data/app/models/alchemy/page/elements.rb +145 -36
- data/app/models/alchemy/page/natures.rb +90 -0
- data/app/models/alchemy/page/scopes.rb +93 -0
- data/app/models/alchemy/page/users.rb +25 -0
- data/app/models/alchemy/picture.rb +15 -0
- data/app/models/alchemy/site.rb +15 -1
- data/app/models/alchemy/site/layout.rb +38 -0
- data/app/models/alchemy/user.rb +13 -3
- data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +7 -7
- data/app/views/alchemy/admin/attachments/_file_to_assign.html.erb +8 -8
- data/app/views/alchemy/admin/attachments/_tag_list.html.erb +1 -16
- data/app/views/alchemy/admin/attachments/destroy.js.erb +1 -4
- data/app/views/alchemy/admin/contents/create.js.erb +1 -1
- data/app/views/alchemy/admin/dashboard/index.html.erb +14 -13
- data/app/views/alchemy/admin/elements/_element_head.html.erb +7 -7
- data/app/views/alchemy/admin/elements/_refresh_editor.js.erb +10 -0
- data/app/views/alchemy/admin/elements/create.js.erb +44 -44
- data/app/views/alchemy/admin/elements/fold.js.erb +22 -26
- data/app/views/alchemy/admin/elements/trash.js.erb +1 -1
- data/app/views/alchemy/admin/elements/update.js.erb +22 -25
- data/app/views/alchemy/admin/essence_files/assign.js.erb +8 -3
- data/app/views/alchemy/admin/essence_pictures/crop.html.erb +14 -12
- data/app/views/alchemy/admin/essence_pictures/edit.html.erb +22 -39
- data/app/views/alchemy/admin/pages/_page.html.erb +73 -80
- data/app/views/alchemy/admin/pages/destroy.js.erb +2 -2
- data/app/views/alchemy/admin/pages/edit.html.erb +21 -18
- data/app/views/alchemy/admin/pages/fold.js.erb +1 -0
- data/app/views/alchemy/admin/pages/info.html.erb +32 -0
- data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +11 -13
- data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +20 -20
- data/app/views/alchemy/admin/partials/_sub_navigation.html.erb +8 -0
- data/app/views/alchemy/admin/partials/_toolbar_button.html.erb +25 -0
- data/app/views/alchemy/admin/partials/_upload_form.html.erb +15 -15
- data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +39 -39
- data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +10 -10
- data/app/views/alchemy/admin/pictures/_tag_list.html.erb +1 -16
- data/app/views/alchemy/admin/resources/destroy.js.erb +1 -1
- data/app/views/alchemy/base/500.html.erb +1 -1
- data/app/views/alchemy/base/permission_denied.js.erb +1 -1
- data/app/views/alchemy/base/redirect.js.erb +1 -1
- data/app/views/alchemy/essences/_essence_link_editor.html.erb +1 -1
- data/app/views/alchemy/essences/_essence_picture_editor.html.erb +1 -1
- data/app/views/alchemy/essences/_essence_richtext_editor.html.erb +1 -1
- data/app/views/alchemy/essences/_essence_text_editor.html.erb +1 -1
- data/app/views/alchemy/essences/{_essence_picture_tools.html.erb → shared/_essence_picture_tools.html.erb} +5 -5
- data/app/views/alchemy/essences/{_linkable_essence_tools.html.erb → shared/_linkable_essence_tools.html.erb} +0 -0
- data/app/views/alchemy/messages/contact_form_mail.de.text.erb +12 -0
- data/app/views/alchemy/messages/contact_form_mail.en.text.erb +12 -0
- data/app/views/alchemy/notifications/reset_password_instructions.de.text.erb +1 -1
- data/app/views/alchemy/notifications/reset_password_instructions.en.text.erb +2 -2
- data/app/views/alchemy/pages/sitemap.xml.erb +3 -5
- data/app/views/alchemy/user_sessions/leave.html.erb +1 -1
- data/app/views/layouts/alchemy/admin.html.erb +4 -2
- data/app/views/layouts/alchemy/sitemap.xml.erb +1 -1
- data/bin/alchemy +7 -13
- data/config/alchemy/config.yml +1 -0
- data/config/authorization_rules.rb +2 -3
- data/config/initializers/dragonfly.rb +2 -0
- data/config/locales/alchemy.de.yml +8 -9
- data/config/locales/alchemy.en.yml +7 -4
- data/config/routes.rb +3 -0
- data/db/migrate/{20130214233001_alchemy_two_point_five.rb → 20130827094554_alchemy_two_point_six.rb} +29 -6
- data/lib/alchemy/auth/engine.rb +9 -0
- data/lib/alchemy/capistrano.rb +37 -12
- data/lib/alchemy/config.rb +48 -35
- data/lib/alchemy/engine.rb +35 -6
- data/lib/alchemy/essence.rb +25 -29
- data/lib/alchemy/ferret/search.rb +86 -0
- data/lib/alchemy/{scoped_pagination_url_helper.rb → kaminari/scoped_pagination_url_helper.rb} +0 -0
- data/lib/alchemy/logger.rb +3 -4
- data/lib/alchemy/page_layout.rb +124 -55
- data/lib/alchemy/resource.rb +0 -10
- data/lib/alchemy/resources_helper.rb +0 -5
- data/lib/alchemy/seeder.rb +1 -32
- data/lib/alchemy/shell.rb +6 -1
- data/lib/alchemy/tinymce.rb +41 -32
- data/lib/alchemy/upgrader.rb +3 -1
- data/lib/alchemy/upgrader/two_point_five.rb +15 -8
- data/lib/alchemy/upgrader/two_point_one.rb +10 -10
- data/lib/alchemy/upgrader/two_point_two.rb +96 -51
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy_cms.rb +5 -46
- data/lib/rails/generators/alchemy/deploy_script/templates/deploy.rb.tt +1 -1
- data/lib/rails/generators/alchemy/devise/devise_generator.rb +9 -4
- data/lib/rails/generators/alchemy/essence/essence_generator.rb +7 -6
- data/lib/rails/generators/alchemy/essence/templates/editor.html.erb +1 -1
- data/lib/rails/generators/alchemy/scaffold/files/_standard.html.erb +1 -0
- data/lib/rails/generators/alchemy/scaffold/scaffold_generator.rb +1 -0
- data/lib/rails/generators/alchemy/site_layouts/site_layouts_generator.rb +23 -0
- data/lib/rails/generators/alchemy/site_layouts/templates/layout.html.erb +1 -0
- data/lib/rails/generators/alchemy/site_layouts/templates/layout.html.haml +1 -0
- data/lib/rails/generators/alchemy/site_layouts/templates/layout.html.slim +1 -0
- data/lib/rails/templates/alchemy.rb +2 -2
- data/lib/tasks/alchemy/db.rake +3 -1
- data/lib/tasks/alchemy/tidy.rake +82 -0
- data/lib/tasks/alchemy/upgrade.rake +2 -1
- data/spec/controllers/admin/attachments_controller_spec.rb +124 -0
- data/spec/controllers/admin/base_controller_spec.rb +35 -0
- data/spec/controllers/admin/clipboard_controller_spec.rb +1 -1
- data/spec/controllers/admin/contents_controller_spec.rb +17 -26
- data/spec/controllers/admin/dashboard_controller_spec.rb +121 -0
- data/spec/controllers/admin/elements_controller_spec.rb +1 -1
- data/spec/controllers/admin/essence_files_controller_spec.rb +67 -0
- data/spec/controllers/admin/essence_pictures_controller_spec.rb +161 -0
- data/spec/controllers/admin/languages_controller_spec.rb +1 -1
- data/spec/controllers/admin/layoutpages_controller_spec.rb +28 -0
- data/spec/controllers/admin/pages_controller_spec.rb +164 -118
- data/spec/controllers/admin/pictures_controller_spec.rb +89 -0
- data/spec/controllers/admin/trash_controller_spec.rb +21 -31
- data/spec/controllers/admin/users_controller_spec.rb +114 -85
- data/spec/controllers/attachments_controller_spec.rb +6 -2
- data/spec/controllers/base_controller_spec.rb +22 -0
- data/spec/controllers/elements_controller_spec.rb +1 -1
- data/spec/controllers/messages_controller_spec.rb +200 -0
- data/spec/controllers/pictures_controller_spec.rb +1 -1
- data/spec/controllers/user_sessions_controller_spec.rb +7 -6
- data/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/dummy/config/alchemy/cells.yml +2 -0
- data/spec/dummy/config/application.rb +19 -8
- data/spec/dummy/db/migrate/{20130214233001_alchemy_two_point_five.rb → 20130827094554_alchemy_two_point_six.rb} +29 -6
- data/spec/dummy/db/schema.rb +1 -1
- data/spec/fast_specs.rb +15 -0
- data/spec/helpers/admin/base_helper_spec.rb +53 -34
- data/spec/helpers/admin/contents_helper_spec.rb +15 -7
- data/spec/helpers/admin/elements_helper_spec.rb +79 -34
- data/spec/helpers/admin/essences_helper_spec.rb +45 -31
- data/spec/helpers/admin/navigation_helper_spec.rb +204 -0
- data/spec/helpers/admin/pages_helper_spec.rb +25 -15
- data/spec/helpers/admin/tags_helper_spec.rb +62 -2
- data/spec/helpers/elements_helper_spec.rb +202 -138
- data/spec/helpers/pages_helper_spec.rb +48 -0
- data/spec/helpers/url_helper_spec.rb +7 -0
- data/spec/libraries/config_spec.rb +110 -3
- data/spec/libraries/essence_spec.rb +29 -9
- data/spec/libraries/page_layout_spec.rb +134 -0
- data/spec/libraries/resource_spec.rb +3 -16
- data/spec/libraries/resources_helper_spec.rb +4 -8
- data/spec/libraries/shell_spec.rb +1 -0
- data/spec/libraries/tinymce_spec.rb +61 -0
- data/spec/mailers/messages_spec.rb +23 -0
- data/spec/models/attachment_spec.rb +45 -0
- data/spec/models/cell_spec.rb +62 -9
- data/spec/models/content_spec.rb +110 -28
- data/spec/models/element_spec.rb +275 -253
- data/spec/models/essence_date_spec.rb +25 -0
- data/spec/models/essence_file_spec.rb +23 -0
- data/spec/models/essence_html_spec.rb +13 -0
- data/spec/models/essence_picture_spec.rb +16 -0
- data/spec/models/essence_text_spec.rb +29 -0
- data/spec/models/language_spec.rb +34 -0
- data/spec/models/message_spec.rb +43 -0
- data/spec/models/page_spec.rb +726 -567
- data/spec/models/picture_spec.rb +98 -0
- data/spec/models/site_spec.rb +60 -2
- data/spec/models/tag_spec.rb +31 -0
- data/spec/models/user_spec.rb +4 -4
- data/spec/spec_helper.rb +49 -58
- data/spec/support/alchemy/controller_helpers.rb +35 -0
- data/spec/support/alchemy/{specs_helpers.rb → integration_helpers.rb} +4 -8
- data/spec/{factories.rb → support/factories.rb} +11 -1
- data/vendor/assets/javascripts/jquery_plugins/jquery.ui.nestedSortable.js +2 -8
- metadata +166 -106
- data/Guardfile +0 -16
- data/app/assets/javascripts/alchemy/alchemy.dirty.js +0 -93
- data/app/assets/javascripts/alchemy/alchemy.dragndrop.js +0 -122
- data/app/models/alchemy/tree_node.rb +0 -4
- data/app/views/alchemy/admin/pages/_page_infos.html.erb +0 -3
- data/app/views/alchemy/admin/partials/_sub_navigation_tab.html.erb +0 -8
- data/app/views/alchemy/messages/contact_form_mail.text.erb +0 -12
- data/config/initializers/kaminari_config.rb +0 -9
- data/db/migrate/20130221200514_migrate_attachments_to_dragonfly.rb +0 -21
- data/db/migrate/20130312205327_change_alchemy_users_role_to_roles.rb +0 -11
- data/lib/alchemy/auth_engine.rb +0 -7
- data/lib/alchemy/authentication_helpers.rb +0 -9
- data/lib/alchemy/ferret_search.rb +0 -84
- data/lib/extensions/array.rb +0 -25
- data/lib/extensions/hash.rb +0 -34
- data/spec/dummy/db/migrate/20130221200514_migrate_attachments_to_dragonfly.rb +0 -21
- data/spec/dummy/db/migrate/20130312205327_change_alchemy_users_role_to_roles.rb +0 -11
- data/spec/models/page_layout_spec.rb +0 -60
@@ -0,0 +1,188 @@
|
|
1
|
+
module Alchemy
|
2
|
+
|
3
|
+
# Holds everything concerning the building and creating of contents and the related essence object.
|
4
|
+
#
|
5
|
+
module Content::Factory
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
SKIPPED_ATTRIBUTES_ON_COPY = %w(position created_at updated_at creator_id updater_id id)
|
10
|
+
|
11
|
+
# Builds a new content as descriped in the elements.yml file.
|
12
|
+
#
|
13
|
+
# @param [Alchemy::Element]
|
14
|
+
# The element the content is for
|
15
|
+
# @param [Hash]
|
16
|
+
# The content description used for finding the content in +elements.yml+ file
|
17
|
+
#
|
18
|
+
def build(element, essence_hash)
|
19
|
+
if (description = content_description(element, essence_hash)).blank?
|
20
|
+
raise ContentDefinitionError, "No description found in elements.yml for #{essence_hash.inspect} and #{element.inspect}"
|
21
|
+
else
|
22
|
+
new(name: description['name'], element_id: element.id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Creates a new content from elements description in the +elements.yml+ file.
|
27
|
+
#
|
28
|
+
# 1. It builds the content
|
29
|
+
# 2. It creates the essence record (content object gets saved)
|
30
|
+
#
|
31
|
+
# @return [Alchemy::Content]
|
32
|
+
#
|
33
|
+
def create_from_scratch(element, essence_hash)
|
34
|
+
essence_hash.stringify_keys!
|
35
|
+
if content = build(element, essence_hash)
|
36
|
+
content.create_essence!(essence_hash['essence_type'])
|
37
|
+
end
|
38
|
+
content
|
39
|
+
end
|
40
|
+
|
41
|
+
# Makes a copy of source and also copies the associated essence.
|
42
|
+
#
|
43
|
+
# You can pass a differences hash to update the attributes of the copy.
|
44
|
+
#
|
45
|
+
# === Example
|
46
|
+
#
|
47
|
+
# @copy = Alchemy::Content.copy(@content, {:element_id => 3})
|
48
|
+
# @copy.element_id # => 3
|
49
|
+
#
|
50
|
+
def copy(source, differences = {})
|
51
|
+
attributes = source.attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY).merge(differences.stringify_keys)
|
52
|
+
content = self.create!(attributes)
|
53
|
+
new_essence = content.essence.class.new(content.essence.attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY))
|
54
|
+
new_essence.save!
|
55
|
+
raise "Essence not cloned" if new_essence.id == content.essence_id
|
56
|
+
content.update_attributes(essence_id: new_essence.id)
|
57
|
+
content
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the content description for building a content.
|
61
|
+
#
|
62
|
+
# 1. It looks in the element's contents description
|
63
|
+
# 2. It builds a description hash from essence type, if the the name key is not present
|
64
|
+
#
|
65
|
+
def content_description(element, essence_hash)
|
66
|
+
essence_hash.stringify_keys!
|
67
|
+
# No name given. We build the content from essence type.
|
68
|
+
if essence_hash['name'].blank? && essence_hash['essence_type'].present?
|
69
|
+
content_description_from_essence_type(element, essence_hash['essence_type'])
|
70
|
+
else
|
71
|
+
content_description_from_element(element, essence_hash['name'])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns a hash for building a content from essence type.
|
76
|
+
#
|
77
|
+
# @param [Alchemy::Element]
|
78
|
+
# The element the content is for.
|
79
|
+
# @param [String]
|
80
|
+
# The essence type the content is from
|
81
|
+
#
|
82
|
+
def content_description_from_essence_type(element, essence_type)
|
83
|
+
{
|
84
|
+
'type' => essence_type,
|
85
|
+
'name' => content_name_from_element_and_essence_type(element, essence_type)
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
# A name for content from its essence type and amount of same essences in element.
|
90
|
+
#
|
91
|
+
# Example:
|
92
|
+
#
|
93
|
+
# essence_picture_1
|
94
|
+
#
|
95
|
+
def content_name_from_element_and_essence_type(element, essence_type)
|
96
|
+
essences_of_same_type = element.contents.where(essence_type: normalize_essence_type(essence_type))
|
97
|
+
"#{essence_type.classify.demodulize.underscore}_#{essences_of_same_type.count + 1}"
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the content description hash from element.
|
101
|
+
#
|
102
|
+
# It first uses the normal content description described in the +elements.yml+ +contents+ array.
|
103
|
+
#
|
104
|
+
# If the content description could not be found it tries to load it from +available_contents+ array.
|
105
|
+
#
|
106
|
+
# @param [Alchemy::Element]
|
107
|
+
# The element instance the content is for
|
108
|
+
# @param [String]
|
109
|
+
# The name of the content
|
110
|
+
#
|
111
|
+
def content_description_from_element(element, name)
|
112
|
+
element.content_description_for(name) ||
|
113
|
+
element.available_content_description_for(name)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns all content descriptions from elements.yml
|
117
|
+
#
|
118
|
+
def descriptions
|
119
|
+
Element.descriptions.collect { |e| e['contents'] }.flatten.compact
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns a normalized Essence type
|
123
|
+
#
|
124
|
+
# Adds Alchemy module name in front of given essence type
|
125
|
+
#
|
126
|
+
# @param [String]
|
127
|
+
# the essence type to normalize
|
128
|
+
#
|
129
|
+
def normalize_essence_type(essence_type)
|
130
|
+
essence_type = essence_type.classify
|
131
|
+
if essence_type.match(/^Alchemy::/)
|
132
|
+
essence_type
|
133
|
+
else
|
134
|
+
essence_type.gsub!(/^Essence/, 'Alchemy::Essence')
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end # end class methods
|
139
|
+
|
140
|
+
# Instance Methods
|
141
|
+
|
142
|
+
# Returns the description hash from +elements.yml+ file.
|
143
|
+
#
|
144
|
+
def description
|
145
|
+
if element.blank?
|
146
|
+
log_warning "Content with id #{self.id} is missing its Element."
|
147
|
+
return {}
|
148
|
+
end
|
149
|
+
Content.content_description_from_element(element, name) || {}
|
150
|
+
end
|
151
|
+
alias_method :definition, :description
|
152
|
+
|
153
|
+
# Creates essence from description.
|
154
|
+
#
|
155
|
+
# If an optional type is passed, this type of essence gets created.
|
156
|
+
#
|
157
|
+
def create_essence!(type = nil)
|
158
|
+
self.essence = essence_class(type).create!(prepared_attributes_for_essence)
|
159
|
+
self.save!
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
# Returns a class constant from description's type field.
|
165
|
+
#
|
166
|
+
# If an optional type is passed, this type of essence gets constantized.
|
167
|
+
#
|
168
|
+
def essence_class(type = nil)
|
169
|
+
Content.normalize_essence_type(type || description['type']).constantize
|
170
|
+
end
|
171
|
+
|
172
|
+
# Prepares the attributes for creating the essence.
|
173
|
+
#
|
174
|
+
# 1. It sets a default text if given in +elements.yml+
|
175
|
+
# 2. It sets do_not_index value for EssenceText and EssenceRichtext essences
|
176
|
+
#
|
177
|
+
def prepared_attributes_for_essence
|
178
|
+
attributes = {
|
179
|
+
ingredient: default_text(description['default'])
|
180
|
+
}
|
181
|
+
if description['type'] == "EssenceRichtext" || description['type'] == "EssenceText"
|
182
|
+
attributes.merge!(do_not_index: !!description['do_not_index'])
|
183
|
+
end
|
184
|
+
attributes
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
@@ -1,3 +1,7 @@
|
|
1
|
+
require 'acts-as-taggable-on'
|
2
|
+
require 'userstamp'
|
3
|
+
require 'acts_as_list'
|
4
|
+
|
1
5
|
module Alchemy
|
2
6
|
class Element < ActiveRecord::Base
|
3
7
|
include Logger
|
@@ -24,7 +28,7 @@ module Alchemy
|
|
24
28
|
|
25
29
|
has_many :contents, :order => :position, :dependent => :destroy
|
26
30
|
belongs_to :cell
|
27
|
-
belongs_to :page
|
31
|
+
belongs_to :page, touch: true
|
28
32
|
has_and_belongs_to_many :to_be_sweeped_pages, :class_name => 'Alchemy::Page', :uniq => true, :join_table => 'alchemy_elements_alchemy_pages'
|
29
33
|
|
30
34
|
validates_uniqueness_of :position, :scope => [:page_id, :cell_id], :if => lambda { |e| e.position != nil }
|
@@ -49,6 +53,10 @@ module Alchemy
|
|
49
53
|
# TODO: add this as default_scope
|
50
54
|
#default_scope { from_current_site }
|
51
55
|
|
56
|
+
# Concerns
|
57
|
+
include Definitions
|
58
|
+
include Presenters
|
59
|
+
|
52
60
|
# class methods
|
53
61
|
class << self
|
54
62
|
|
@@ -56,14 +64,13 @@ module Alchemy
|
|
56
64
|
def new_from_scratch(attributes)
|
57
65
|
attributes = attributes.dup.symbolize_keys
|
58
66
|
return new if attributes[:name].blank?
|
59
|
-
return nil if
|
67
|
+
return nil if definitions.blank?
|
60
68
|
# clean the name from cell name
|
61
69
|
attributes[:name] = attributes[:name].split('#').first
|
62
|
-
element_scratch =
|
63
|
-
if element_scratch
|
70
|
+
if element_scratch = definitions.detect { |el| el['name'] == attributes[:name] }
|
64
71
|
new(element_scratch.merge(attributes).except(*FORBIDDEN_DEFINITION_ATTRIBUTES))
|
65
72
|
else
|
66
|
-
raise ElementDefinitionError, "Element
|
73
|
+
raise ElementDefinitionError, "Element definition for #{attributes[:name]} not found. Please check your elements.yml"
|
67
74
|
end
|
68
75
|
end
|
69
76
|
|
@@ -74,73 +81,6 @@ module Alchemy
|
|
74
81
|
return element
|
75
82
|
end
|
76
83
|
|
77
|
-
# Returns the descriptions from elements.yml file.
|
78
|
-
#
|
79
|
-
# Place a elements.yml file inside your apps config/alchemy folder to define
|
80
|
-
# your own set of elements
|
81
|
-
#
|
82
|
-
def descriptions
|
83
|
-
if ::File.exists? "#{::Rails.root}/config/alchemy/elements.yml"
|
84
|
-
::YAML.load_file("#{::Rails.root}/config/alchemy/elements.yml") || []
|
85
|
-
else
|
86
|
-
raise LoadError, "Could not find elements.yml file! Please run: rails generate alchemy:scaffold"
|
87
|
-
end
|
88
|
-
rescue TypeError => e
|
89
|
-
warn "Your elements.yml is empty."
|
90
|
-
[]
|
91
|
-
end
|
92
|
-
alias_method :definitions, :descriptions
|
93
|
-
|
94
|
-
# pastes a element from the clipboard in the session to page
|
95
|
-
def paste_from_clipboard(page_id, element, method, position)
|
96
|
-
element_copy = copy(element, :page_id => page_id)
|
97
|
-
element_copy.insert_at(position)
|
98
|
-
if method == "move" && element_copy.valid?
|
99
|
-
element.destroy
|
100
|
-
end
|
101
|
-
element_copy
|
102
|
-
end
|
103
|
-
|
104
|
-
# List all element definitions for +self.page#page_layout+
|
105
|
-
def all_for_page(page)
|
106
|
-
raise TypeError if page.class.name != "Alchemy::Page"
|
107
|
-
# if page_layout has cells, collect elements from cells and group them by cellname
|
108
|
-
page_layout = PageLayout.get(page.page_layout)
|
109
|
-
if page_layout.blank?
|
110
|
-
warn "Could not find page_layout description for page: #{page.name}"
|
111
|
-
return []
|
112
|
-
end
|
113
|
-
elements_for_layout = []
|
114
|
-
elements_for_layout += all_definitions_for(page_layout['elements'])
|
115
|
-
return [] if elements_for_layout.blank?
|
116
|
-
# all unique and limited elements from this layout
|
117
|
-
limited_elements = elements_for_layout.select{ |m| m["unique"] == true || (m["amount"] > 0 unless m["amount"].nil?) }
|
118
|
-
elements_already_on_the_page = page.elements.not_trashed
|
119
|
-
# delete all elements from the elements that could be placed that are unique or limited and already and the page
|
120
|
-
elements_counts = Hash.new(0)
|
121
|
-
elements_already_on_the_page.each { |e| elements_counts[e.name] += 1 }
|
122
|
-
limited_elements.each do |limited_element|
|
123
|
-
next if elements_counts[limited_element["name"]] == 0
|
124
|
-
if limited_element["unique"]
|
125
|
-
elements_for_layout.delete(limited_element) if elements_counts[limited_element["name"]] > 0
|
126
|
-
next
|
127
|
-
end
|
128
|
-
unless limited_element["amount"].nil?
|
129
|
-
elements_for_layout.delete(limited_element) if elements_counts[limited_element["name"]] >= limited_element["amount"]
|
130
|
-
end
|
131
|
-
end
|
132
|
-
elements_for_layout
|
133
|
-
end
|
134
|
-
|
135
|
-
def all_definitions_for(element_names)
|
136
|
-
return [] if element_names.blank?
|
137
|
-
if element_names.to_s == "all"
|
138
|
-
definitions
|
139
|
-
else
|
140
|
-
definitions.select { |e| element_names.include? e['name'] }
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
84
|
# This methods does a copy of source and all depending contents and all of their depending essences.
|
145
85
|
#
|
146
86
|
# == Options
|
@@ -165,53 +105,36 @@ module Alchemy
|
|
165
105
|
element
|
166
106
|
end
|
167
107
|
|
168
|
-
# List all elements from page_layout
|
169
|
-
def elements_for_layout(layout)
|
170
|
-
elements = []
|
171
|
-
layout_elements = PageLayout.get(layout)["elements"]
|
172
|
-
return Element.descriptions if layout_elements == "all"
|
173
|
-
Element.descriptions.each do |element|
|
174
|
-
if layout_elements.include?(element["name"])
|
175
|
-
elements << element
|
176
|
-
end
|
177
|
-
end
|
178
|
-
elements
|
179
|
-
end
|
180
|
-
|
181
|
-
def get_from_clipboard(clipboard)
|
182
|
-
return nil if clipboard.blank?
|
183
|
-
find_by_id(clipboard[:element_id])
|
184
|
-
end
|
185
|
-
|
186
108
|
def all_from_clipboard(clipboard)
|
187
109
|
return [] if clipboard.nil?
|
188
|
-
find_all_by_id(clipboard.collect { |
|
110
|
+
find_all_by_id(clipboard.collect { |e| e[:id] })
|
189
111
|
end
|
190
112
|
|
113
|
+
# All elements in clipboard that could be placed on page
|
114
|
+
#
|
191
115
|
def all_from_clipboard_for_page(clipboard, page)
|
192
116
|
return [] if clipboard.nil? || page.nil?
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
clipboard_elements.select { |ce| allowed_element_names.include?(ce.name) }
|
117
|
+
all_from_clipboard(clipboard).select { |ce|
|
118
|
+
page.available_element_names.include?(ce.name)
|
119
|
+
}
|
197
120
|
end
|
198
121
|
|
199
122
|
end
|
200
123
|
|
201
124
|
# Returns next public element from same page.
|
125
|
+
#
|
202
126
|
# Pass an element name to get next of this kind.
|
127
|
+
#
|
203
128
|
def next(name = nil)
|
204
|
-
|
205
|
-
elements = elements.named(name) if name.present?
|
206
|
-
elements.reorder("position ASC").limit(1).first
|
129
|
+
previous_or_next('>', name)
|
207
130
|
end
|
208
131
|
|
209
132
|
# Returns previous public element from same page.
|
133
|
+
#
|
210
134
|
# Pass an element name to get previous of this kind.
|
135
|
+
#
|
211
136
|
def prev(name = nil)
|
212
|
-
|
213
|
-
elements = elements.named(name) if name.present?
|
214
|
-
elements.reorder("position DESC").limit(1).first
|
137
|
+
previous_or_next('<', name)
|
215
138
|
end
|
216
139
|
|
217
140
|
# Stores the page into `to_be_sweeped_pages` (Pages that have to be sweeped after updating element).
|
@@ -265,9 +188,9 @@ module Alchemy
|
|
265
188
|
contents.find_by_name(rss_title['name'])
|
266
189
|
end
|
267
190
|
|
268
|
-
# Returns the content that is marked as rss
|
191
|
+
# Returns the content that is marked as rss definition.
|
269
192
|
#
|
270
|
-
# Mark a content as rss
|
193
|
+
# Mark a content as rss definition in your +elements.yml+ file:
|
271
194
|
#
|
272
195
|
# - name: news
|
273
196
|
# contents:
|
@@ -282,114 +205,29 @@ module Alchemy
|
|
282
205
|
|
283
206
|
# Returns the array with the hashes for all element contents in the elements.yml file
|
284
207
|
def content_descriptions
|
285
|
-
return nil if
|
286
|
-
|
287
|
-
end
|
288
|
-
|
289
|
-
# Returns the array with the hashes for all element available_contents in the elements.yml file
|
290
|
-
def available_content_descriptions
|
291
|
-
return nil if description.blank?
|
292
|
-
description['available_contents']
|
208
|
+
return nil if definition.blank?
|
209
|
+
definition['contents']
|
293
210
|
end
|
294
211
|
|
295
|
-
# Returns the
|
212
|
+
# Returns the definition for given content_name
|
296
213
|
def content_description_for(content_name)
|
297
214
|
if content_descriptions.blank?
|
298
|
-
|
215
|
+
log_warning "Element #{self.name} is missing the content definition for #{content_name}"
|
299
216
|
return nil
|
300
217
|
else
|
301
218
|
content_descriptions.detect { |d| d['name'] == content_name }
|
302
219
|
end
|
303
220
|
end
|
304
221
|
|
305
|
-
# Returns the
|
222
|
+
# Returns the definition for given content_name inside the available_contents
|
306
223
|
def available_content_description_for(content_name)
|
307
|
-
return nil if
|
308
|
-
|
309
|
-
end
|
310
|
-
|
311
|
-
# returns the description of the element with my name in element.yml
|
312
|
-
def description
|
313
|
-
description = self.class.descriptions.detect { |d| d['name'] == self.name }
|
314
|
-
if description.blank?
|
315
|
-
warn "Could not find element definition for #{self.name}. Please check your elements.yml!"
|
316
|
-
return {}
|
317
|
-
else
|
318
|
-
return description
|
319
|
-
end
|
320
|
-
end
|
321
|
-
alias_method :definition, :description
|
322
|
-
|
323
|
-
# Human name for displaying in selectboxes and element editor views.
|
324
|
-
# The name is beeing translated from elements name value as described in config/alchemy/elements.yml
|
325
|
-
#
|
326
|
-
# Translate the name in your config/locales language file. Example:
|
327
|
-
#
|
328
|
-
# de:
|
329
|
-
# alchemy:
|
330
|
-
# element_names:
|
331
|
-
# contactform: 'Kontakt Formular'
|
332
|
-
#
|
333
|
-
# If no translation is found a humanized name is used.
|
334
|
-
#
|
335
|
-
def display_name
|
336
|
-
return name.humanize if description.blank?
|
337
|
-
I18n.t(description['name'], scope: 'element_names', default: description['name'].to_s.humanize)
|
338
|
-
end
|
339
|
-
|
340
|
-
# Gets the preview text from the first Content found in the +elements.yml+ Element description file.
|
341
|
-
# You can flag a Content as +take_me_for_preview+ to take this as preview.
|
342
|
-
def preview_text(maxlength = 30)
|
343
|
-
return "" if description.blank?
|
344
|
-
my_contents = description["contents"]
|
345
|
-
return "" if my_contents.blank?
|
346
|
-
content_flagged_as_preview = my_contents.select { |a| a["take_me_for_preview"] }.first
|
347
|
-
if content_flagged_as_preview.blank?
|
348
|
-
content_to_take_as_preview = my_contents.first
|
349
|
-
else
|
350
|
-
content_to_take_as_preview = content_flagged_as_preview
|
351
|
-
end
|
352
|
-
preview_content = self.contents.select { |content| content.name == content_to_take_as_preview["name"] }.first
|
353
|
-
return "" if preview_content.blank? || preview_content.essence.blank?
|
354
|
-
text = preview_content.essence.preview_text(maxlength)
|
355
|
-
text.size > maxlength ? "#{text[0..maxlength]}..." : text
|
356
|
-
end
|
357
|
-
|
358
|
-
# Generates a preview text containing Element#display_name and Element#preview_text.
|
359
|
-
# It is displayed inside the head of the Element in the Elements.list overlay window from the Alchemy Admin::Page#edit view.
|
360
|
-
#
|
361
|
-
# === Example
|
362
|
-
#
|
363
|
-
# A Element described as:
|
364
|
-
#
|
365
|
-
# - name: funky_element
|
366
|
-
# display_name: Funky Element
|
367
|
-
# contents:
|
368
|
-
# - name: headline
|
369
|
-
# type: EssenceText
|
370
|
-
# - name: text
|
371
|
-
# type EssenceRichtext
|
372
|
-
# take_me_for_preview: true
|
373
|
-
#
|
374
|
-
# With "I want to tell you a funky story" as stripped_body for the EssenceRichtext Content produces:
|
375
|
-
#
|
376
|
-
# Funky Element: I want to tell ...
|
377
|
-
#
|
378
|
-
# Options:
|
379
|
-
#
|
380
|
-
# maxlength(integer). [Default 30] : Length of characters after the text will be cut off.
|
381
|
-
#
|
382
|
-
def display_name_with_preview_text(maxlength = 30)
|
383
|
-
"#{display_name}: #{preview_text(maxlength)}"
|
384
|
-
end
|
385
|
-
|
386
|
-
def dom_id
|
387
|
-
"#{name}_#{id}"
|
224
|
+
return nil if available_contents.blank?
|
225
|
+
available_contents.detect { |d| d['name'] == content_name }
|
388
226
|
end
|
389
227
|
|
390
228
|
# returns the collection of available essence_types that can be created for this element depending on its description in elements.yml
|
391
229
|
def available_contents
|
392
|
-
|
230
|
+
definition['available_contents']
|
393
231
|
end
|
394
232
|
|
395
233
|
# Returns the contents ingredient for passed content name.
|
@@ -537,7 +375,7 @@ module Alchemy
|
|
537
375
|
|
538
376
|
# Returns true if the definition of this element has a taggable true value.
|
539
377
|
def taggable?
|
540
|
-
|
378
|
+
definition['taggable'] == true
|
541
379
|
end
|
542
380
|
|
543
381
|
def to_partial_path
|
@@ -589,14 +427,27 @@ module Alchemy
|
|
589
427
|
# creates the contents for this element as described in the elements.yml
|
590
428
|
def create_contents
|
591
429
|
contents = []
|
592
|
-
if
|
593
|
-
|
430
|
+
if definition["contents"].blank?
|
431
|
+
log_warning "Could not find any content descriptions for element: #{self.name}"
|
594
432
|
else
|
595
|
-
|
433
|
+
definition["contents"].each do |content_hash|
|
596
434
|
contents << Content.create_from_scratch(self, content_hash.symbolize_keys)
|
597
435
|
end
|
598
436
|
end
|
599
437
|
end
|
600
438
|
|
439
|
+
# Returns previous or next public element from same page.
|
440
|
+
#
|
441
|
+
# @param [String]
|
442
|
+
# Pass '>' or '<' to find next or previous public element.
|
443
|
+
# @param [String]
|
444
|
+
# Pass an element name to get previous of this kind.
|
445
|
+
#
|
446
|
+
def previous_or_next(dir, name = nil)
|
447
|
+
elements = page.elements.published.where("#{self.class.table_name}.position #{dir} #{position}")
|
448
|
+
elements = elements.named(name) if name.present?
|
449
|
+
elements.reorder("position #{dir == '>' ? 'ASC' : 'DESC'}").limit(1).first
|
450
|
+
end
|
451
|
+
|
601
452
|
end
|
602
453
|
end
|