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,52 @@
|
|
1
|
+
module Alchemy
|
2
|
+
|
3
|
+
# Module concerning element definitions
|
4
|
+
#
|
5
|
+
module Element::Definitions
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Returns the definitions from elements.yml file.
|
11
|
+
#
|
12
|
+
# Place a +elements.yml+ file inside your apps +config/alchemy+ folder to define
|
13
|
+
# your own set of elements
|
14
|
+
#
|
15
|
+
def definitions
|
16
|
+
@definitions ||= read_definitions_file
|
17
|
+
end
|
18
|
+
alias_method :descriptions, :definitions
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Reads the element definitions file named +elements.yml+ from +config/alchemy/+ folder.
|
23
|
+
#
|
24
|
+
def read_definitions_file
|
25
|
+
if ::File.exists?(definitions_file_path)
|
26
|
+
::YAML.load_file(definitions_file_path) || []
|
27
|
+
else
|
28
|
+
raise LoadError, "Could not find elements.yml file! Please run `rails generate alchemy:scaffold`"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the +elements.yml+ file path
|
33
|
+
#
|
34
|
+
def definitions_file_path
|
35
|
+
Rails.root.join 'config/alchemy/elements.yml'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# The definition of this element.
|
40
|
+
#
|
41
|
+
def definition
|
42
|
+
if definition = self.class.definitions.detect { |d| d['name'] == name }
|
43
|
+
definition
|
44
|
+
else
|
45
|
+
log_warning "Could not find element definition for #{self.name}. Please check your elements.yml file!"
|
46
|
+
return {}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
alias_method :description, :definition
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Alchemy
|
2
|
+
|
3
|
+
# Methods used for presenting an Alchemy Element.
|
4
|
+
#
|
5
|
+
module Element::Presenters
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Human name for displaying elements in select boxes and element editor views.
|
11
|
+
#
|
12
|
+
# The name is beeing translated from given name value as described in +config/alchemy/elements.yml+
|
13
|
+
#
|
14
|
+
# Translate the name in your +config/locales+ language file.
|
15
|
+
#
|
16
|
+
# == Example:
|
17
|
+
#
|
18
|
+
# de:
|
19
|
+
# alchemy:
|
20
|
+
# element_names:
|
21
|
+
# contactform: 'Kontakt Formular'
|
22
|
+
#
|
23
|
+
# If no translation is found a humanized name is used.
|
24
|
+
#
|
25
|
+
def display_name_for(name)
|
26
|
+
I18n.t(name, scope: 'element_names', default: name.to_s.humanize)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the translated name
|
31
|
+
#
|
32
|
+
# @see Alchemy::Element::Presenters#display_name_for
|
33
|
+
#
|
34
|
+
def display_name
|
35
|
+
self.class.display_name_for(description['name'] || self.name)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns a preview text for element.
|
39
|
+
#
|
40
|
+
# It's taken from the first Content found in the +elements.yml+ description file.
|
41
|
+
#
|
42
|
+
# You can flag a Content as +take_me_for_preview+ to take this as preview.
|
43
|
+
#
|
44
|
+
# @param maxlength [Fixnum] (30)
|
45
|
+
# Length of characters after the text will be cut off.
|
46
|
+
#
|
47
|
+
def preview_text(maxlength = 30)
|
48
|
+
(contents.detect(&:preview_content?) || contents.first).try(:preview_text, maxlength)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Generates a preview text containing Element#display_name and Element#preview_text.
|
52
|
+
#
|
53
|
+
# It is displayed inside the head of the Element in the Elements.list overlay window from the Alchemy Admin::Page#edit view.
|
54
|
+
#
|
55
|
+
# === Example
|
56
|
+
#
|
57
|
+
# A Element described as:
|
58
|
+
#
|
59
|
+
# - name: funky_element
|
60
|
+
# display_name: Funky Element
|
61
|
+
# contents:
|
62
|
+
# - name: headline
|
63
|
+
# type: EssenceText
|
64
|
+
# - name: text
|
65
|
+
# type EssenceRichtext
|
66
|
+
# take_me_for_preview: true
|
67
|
+
#
|
68
|
+
# With "I want to tell you a funky story" as stripped_body for the EssenceRichtext Content produces:
|
69
|
+
#
|
70
|
+
# Funky Element: I want to tell ...
|
71
|
+
#
|
72
|
+
# @param maxlength [Fixnum] (30)
|
73
|
+
# Length of characters after the text will be cut off.
|
74
|
+
#
|
75
|
+
def display_name_with_preview_text(maxlength = 30)
|
76
|
+
"#{display_name}: #{preview_text(maxlength)}"
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns a dom id used for elements html id tag.
|
80
|
+
#
|
81
|
+
def dom_id
|
82
|
+
"#{name}_#{id}"
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -1,14 +1,13 @@
|
|
1
1
|
module Alchemy
|
2
2
|
class EssenceFile < ActiveRecord::Base
|
3
|
-
|
4
3
|
attr_accessible :title, :css_class, :attachment_id
|
5
|
-
|
6
|
-
acts_as_essence(
|
7
|
-
:ingredient_column => :attachment,
|
8
|
-
:preview_text_method => :name
|
9
|
-
)
|
10
|
-
|
11
4
|
belongs_to :attachment
|
5
|
+
acts_as_essence ingredient_column: 'attachment'
|
6
|
+
|
7
|
+
def preview_text(max=30)
|
8
|
+
return "" if attachment.blank?
|
9
|
+
attachment.name.to_s[0..max-1]
|
10
|
+
end
|
12
11
|
|
13
12
|
end
|
14
13
|
end
|
@@ -16,15 +16,30 @@ module Alchemy
|
|
16
16
|
:picture_id
|
17
17
|
)
|
18
18
|
|
19
|
-
acts_as_essence
|
20
|
-
:ingredient_column => :picture,
|
21
|
-
:preview_text_method => :name
|
22
|
-
)
|
19
|
+
acts_as_essence ingredient_column: 'picture'
|
23
20
|
|
24
21
|
belongs_to :picture
|
25
22
|
before_save :fix_crop_values
|
26
23
|
before_save :replace_newlines
|
27
24
|
|
25
|
+
def preview_text(max=30)
|
26
|
+
return "" if picture.nil?
|
27
|
+
picture.name.to_s[0..max-1]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns a hash suitable for the js image cropper.
|
31
|
+
#
|
32
|
+
def cropping_mask
|
33
|
+
crop_from = self.crop_from.split('x')
|
34
|
+
crop_size = self.crop_size.split('x')
|
35
|
+
{
|
36
|
+
x1: crop_from[0].to_i,
|
37
|
+
y1: crop_from[1].to_i,
|
38
|
+
x2: crop_from[0].to_i + crop_size[0].to_i,
|
39
|
+
y2: crop_from[1].to_i + crop_size[1].to_i
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
28
43
|
private
|
29
44
|
|
30
45
|
def fix_crop_values
|
@@ -17,31 +17,31 @@ module Alchemy
|
|
17
17
|
include ::ActiveModel::Conversion
|
18
18
|
include ::ActiveModel::MassAssignmentSecurity
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
class << self
|
21
|
+
def attr_accessor(*vars)
|
22
|
+
@attributes ||= {}
|
23
|
+
vars.map { |v| @attributes[v] = nil}
|
24
|
+
super(*vars)
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
def attributes
|
28
|
+
@attributes
|
29
|
+
end
|
29
30
|
|
30
|
-
|
31
|
-
|
31
|
+
def config
|
32
|
+
Config.get(:mailer)
|
33
|
+
end
|
32
34
|
end
|
33
35
|
|
34
|
-
@@config = Config.get(:mailer)
|
35
|
-
|
36
36
|
attr_accessor :contact_form_id, :ip
|
37
37
|
attr_accessible :contact_form_id
|
38
38
|
|
39
|
-
|
39
|
+
config['fields'].each do |field|
|
40
40
|
attr_accessor field.to_sym
|
41
41
|
attr_accessible field.to_sym
|
42
42
|
end
|
43
43
|
|
44
|
-
|
44
|
+
config['validate_fields'].each do |field|
|
45
45
|
validates_presence_of field
|
46
46
|
|
47
47
|
case field.to_sym
|
@@ -60,6 +60,10 @@ module Alchemy
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
+
def attributes
|
64
|
+
self.class.attributes
|
65
|
+
end
|
66
|
+
|
63
67
|
def persisted? #:nodoc:
|
64
68
|
false
|
65
69
|
end
|
data/app/models/alchemy/page.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
require 'acts-as-taggable-on'
|
2
|
+
require 'awesome_nested_set'
|
3
|
+
require 'userstamp'
|
4
|
+
|
1
5
|
module Alchemy
|
2
6
|
class Page < ActiveRecord::Base
|
3
7
|
|
@@ -45,6 +49,7 @@ module Alchemy
|
|
45
49
|
has_many :folded_pages
|
46
50
|
has_many :legacy_urls, :class_name => 'Alchemy::LegacyPageUrl'
|
47
51
|
belongs_to :language
|
52
|
+
belongs_to :locker, class_name: 'Alchemy::User', foreign_key: 'locked_by'
|
48
53
|
|
49
54
|
validates_presence_of :language, :on => :create, :unless => :root
|
50
55
|
validates_presence_of :page_layout, :unless => :systempage?
|
@@ -54,37 +59,15 @@ module Alchemy
|
|
54
59
|
attr_accessor :do_not_validate_language
|
55
60
|
|
56
61
|
before_save :set_language_code, :unless => :systempage?
|
57
|
-
before_save :
|
58
|
-
|
62
|
+
before_save :set_restrictions_to_child_pages, :if => :restricted_changed?, :unless => :systempage?
|
63
|
+
before_save :inherit_restricted_status, :if => proc { parent && parent.restricted? }, :unless => :systempage?
|
59
64
|
after_update :create_legacy_url, :if => :urlname_changed?, :unless => :redirects_to_external?
|
60
65
|
|
61
|
-
scope :language_roots, where(:language_root => true)
|
62
|
-
scope :layoutpages, where(:layoutpage => true)
|
63
|
-
scope :all_locked, where(:locked => true)
|
64
|
-
scope :all_locked_by, lambda { |user| where(:locked => true, :locked_by => user.id) }
|
65
|
-
scope :not_locked, where(:locked => false)
|
66
|
-
scope :visible, where(:visible => true)
|
67
|
-
scope :published, where(:public => true)
|
68
|
-
scope :not_restricted, where(:restricted => false)
|
69
|
-
scope :restricted, where(:restricted => true)
|
70
|
-
scope :public_language_roots, lambda {
|
71
|
-
where(:language_root => true, :language_code => Language.all_codes_for_published, :public => true)
|
72
|
-
}
|
73
|
-
scope :all_last_edited_from, lambda { |user| where(:updater_id => user.id).order('updated_at DESC').limit(5) }
|
74
|
-
# Returns all pages that have the given language_id
|
75
|
-
scope :with_language, lambda { |language_id| where(:language_id => language_id) }
|
76
|
-
scope :contentpages, where(:layoutpage => [false, nil]).where(Page.arel_table[:parent_id].not_eq(nil))
|
77
|
-
# Returns all pages that are not locked and public.
|
78
|
-
# Used for flushing all page caches at once.
|
79
|
-
scope :flushables, not_locked.published.contentpages
|
80
|
-
scope :searchables, not_restricted.published.contentpages
|
81
|
-
# Scope for only the pages from Alchemy::Site.current
|
82
|
-
scope :from_current_site, lambda { where(:alchemy_languages => {site_id: Site.current || Site.default}).joins(:language) }
|
83
|
-
# TODO: add this as default_scope
|
84
|
-
#default_scope { from_current_site }
|
85
|
-
|
86
66
|
# Concerns
|
67
|
+
include Scopes
|
68
|
+
include Natures
|
87
69
|
include Naming
|
70
|
+
include Users
|
88
71
|
include Cells
|
89
72
|
include Elements
|
90
73
|
|
@@ -101,26 +84,23 @@ module Alchemy
|
|
101
84
|
self.language_roots.find_by_language_id(language_id)
|
102
85
|
end
|
103
86
|
|
104
|
-
# Creates a copy of source
|
87
|
+
# Creates a copy of given source.
|
105
88
|
#
|
106
89
|
# Also copies all elements included in source.
|
107
90
|
#
|
108
91
|
# === Note:
|
92
|
+
#
|
109
93
|
# It prevents the element auto generator from running.
|
110
94
|
#
|
111
95
|
# @param source [Alchemy::Page]
|
96
|
+
# The source page the copy is taken from
|
112
97
|
# @param differences [Hash]
|
98
|
+
# A optional hash with attributes that take precedence over the source attributes
|
113
99
|
#
|
114
100
|
# @return [Alchemy::Page]
|
115
101
|
#
|
116
102
|
def copy(source, differences = {})
|
117
|
-
source
|
118
|
-
differences.stringify_keys!
|
119
|
-
attributes = source.attributes.merge(differences)
|
120
|
-
attributes.merge!(DEFAULT_ATTRIBUTES_FOR_COPY)
|
121
|
-
new_name = differences['name'].present? ? differences['name'] : "#{source.name} (#{I18n.t('Copy')})"
|
122
|
-
attributes.merge!('name' => new_name)
|
123
|
-
page = self.new(attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY))
|
103
|
+
page = Alchemy::Page.new(attributes_from_source_for_copy(source, differences))
|
124
104
|
page.tag_list = source.tag_list
|
125
105
|
if page.save!
|
126
106
|
copy_cells(source, page)
|
@@ -151,6 +131,19 @@ module Alchemy
|
|
151
131
|
end
|
152
132
|
end
|
153
133
|
|
134
|
+
def paste_from_clipboard(source, new_parent, new_name)
|
135
|
+
page = copy(source, {
|
136
|
+
parent_id: new_parent.id,
|
137
|
+
language: new_parent.language,
|
138
|
+
name: new_name,
|
139
|
+
title: new_name
|
140
|
+
})
|
141
|
+
if source.children.any?
|
142
|
+
source.copy_children_to(page)
|
143
|
+
end
|
144
|
+
return page
|
145
|
+
end
|
146
|
+
|
154
147
|
def all_from_clipboard(clipboard)
|
155
148
|
return [] if clipboard.blank?
|
156
149
|
self.find_all_by_id(clipboard.collect { |i| i[:id] })
|
@@ -173,151 +166,101 @@ module Alchemy
|
|
173
166
|
options
|
174
167
|
end
|
175
168
|
|
176
|
-
|
177
|
-
|
178
|
-
# Instance methods
|
179
|
-
#
|
180
|
-
|
181
|
-
# Finds the previous page on the same structure level. Otherwise it returns nil.
|
182
|
-
# Options:
|
183
|
-
# => :restricted => boolean (standard: nil) - next restricted page (true), skip restricted pages (false), ignore restriction (nil)
|
184
|
-
# => :public => boolean (standard: true) - next public page (true), skip public pages (false)
|
185
|
-
def previous(options = {})
|
186
|
-
next_or_previous(:previous, {
|
187
|
-
:restricted => nil,
|
188
|
-
:public => true
|
189
|
-
}.merge(options))
|
190
|
-
end
|
191
|
-
alias_method :previous_page, :previous
|
192
|
-
|
193
|
-
# Finds the next page on the same structure level. Otherwise it returns nil.
|
194
|
-
# Options:
|
195
|
-
# => :restricted => boolean (standard: nil) - next restricted page (true), skip restricted pages (false), ignore restriction (nil)
|
196
|
-
# => :public => boolean (standard: true) - next public page (true), skip public pages (false)
|
197
|
-
def next(options = {})
|
198
|
-
next_or_previous(:next, {
|
199
|
-
:restricted => nil,
|
200
|
-
:public => true
|
201
|
-
}.merge(options))
|
202
|
-
end
|
203
|
-
alias_method :next_page, :next
|
204
|
-
|
205
|
-
def lock(user)
|
206
|
-
self.locked = true
|
207
|
-
self.locked_by = user.id
|
208
|
-
self.save(:validate => false)
|
209
|
-
end
|
210
|
-
|
211
|
-
def unlock
|
212
|
-
self.locked = false
|
213
|
-
self.locked_by = nil
|
214
|
-
self.do_not_sweep = true
|
215
|
-
self.save
|
216
|
-
end
|
217
|
-
|
218
|
-
# Returns the name of the creator of this page.
|
219
|
-
def creator
|
220
|
-
@page_creator ||= User.find_by_id(creator_id)
|
221
|
-
return I18n.t('unknown') if @page_creator.nil?
|
222
|
-
@page_creator.name
|
223
|
-
end
|
169
|
+
private
|
224
170
|
|
225
|
-
|
226
|
-
|
227
|
-
@
|
228
|
-
|
229
|
-
@
|
230
|
-
|
171
|
+
# Aggregates the attributes from given source for copy of page.
|
172
|
+
#
|
173
|
+
# @param [Alchemy::Page]
|
174
|
+
# The source page
|
175
|
+
# @param [Hash]
|
176
|
+
# A optional hash with attributes that take precedence over the source attributes
|
177
|
+
#
|
178
|
+
def attributes_from_source_for_copy(source, differences = {})
|
179
|
+
source.attributes.stringify_keys!
|
180
|
+
differences.stringify_keys!
|
181
|
+
attributes = source.attributes.merge(differences)
|
182
|
+
attributes.merge!(DEFAULT_ATTRIBUTES_FOR_COPY)
|
183
|
+
attributes.merge!('name' => new_name_for_copy(differences['name'], source.name))
|
184
|
+
attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY)
|
185
|
+
end
|
231
186
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
187
|
+
# Returns a new name for copy of page.
|
188
|
+
#
|
189
|
+
# If the differences hash includes a new name this is taken.
|
190
|
+
# Otherwise +source.name+
|
191
|
+
#
|
192
|
+
# @param [String]
|
193
|
+
# The differences hash that contains a new name
|
194
|
+
# @param [String]
|
195
|
+
# The name of the source
|
196
|
+
#
|
197
|
+
def new_name_for_copy(custom_name, source_name)
|
198
|
+
return custom_name if custom_name.present?
|
199
|
+
"#{source_name} (#{I18n.t('Copy')})"
|
200
|
+
end
|
238
201
|
|
239
|
-
def locker
|
240
|
-
User.find_by_id(self.locked_by)
|
241
202
|
end
|
242
203
|
|
243
|
-
|
244
|
-
|
245
|
-
folded_page.folded = status
|
246
|
-
folded_page.save
|
247
|
-
end
|
204
|
+
# Instance methods
|
205
|
+
#
|
248
206
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
folded_page.folded
|
207
|
+
# Touches the timestamps and userstamps
|
208
|
+
def touch
|
209
|
+
Page.where(id: self.id).update_all(updated_at: Time.now, updater_id: User.stamper)
|
253
210
|
end
|
254
211
|
|
255
|
-
# Returns
|
212
|
+
# Returns the previous page on the same level or nil.
|
213
|
+
#
|
214
|
+
# For options @see #next_or_previous
|
256
215
|
#
|
257
|
-
def
|
258
|
-
|
259
|
-
visible: visible?,
|
260
|
-
public: public?,
|
261
|
-
locked: locked?,
|
262
|
-
restricted: restricted?
|
263
|
-
}
|
216
|
+
def previous(options = {})
|
217
|
+
next_or_previous('<', options)
|
264
218
|
end
|
219
|
+
alias_method :previous_page, :previous
|
265
220
|
|
266
|
-
# Returns the
|
221
|
+
# Returns the next page on the same level or nil.
|
267
222
|
#
|
268
|
-
# @
|
223
|
+
# For options @see #next_or_previous
|
269
224
|
#
|
270
|
-
def
|
271
|
-
|
272
|
-
end
|
273
|
-
|
274
|
-
def has_controller?
|
275
|
-
!PageLayout.get(self.page_layout).nil? && !PageLayout.get(self.page_layout)["controller"].blank?
|
225
|
+
def next(options = {})
|
226
|
+
next_or_previous('>', options)
|
276
227
|
end
|
228
|
+
alias_method :next_page, :next
|
277
229
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
230
|
+
# Locks the page to given user without updating the timestamps
|
231
|
+
#
|
232
|
+
def lock!(user)
|
233
|
+
# Yes, since +update_columns+ is not available in Rails 3.2,
|
234
|
+
# we use this workaround to update straight in the db.
|
235
|
+
Page.where(id: self.id).update_all(locked: true, locked_by: user.id)
|
283
236
|
end
|
284
237
|
|
285
|
-
#
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
else
|
292
|
-
description
|
293
|
-
end
|
238
|
+
# Unlocks the page without updating the timestamps
|
239
|
+
#
|
240
|
+
def unlock!
|
241
|
+
# Yes, since +update_columns+ is not available in Rails 3.2,
|
242
|
+
# we use this workaround to update straight in the db.
|
243
|
+
Page.where(id: self.id).update_all(locked: false, locked_by: nil)
|
294
244
|
end
|
295
|
-
alias_method :definition, :layout_description
|
296
245
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
I18n.t(self.page_layout, :scope => :page_layout_names)
|
246
|
+
def fold!(user_id, status)
|
247
|
+
folded_page = folded_pages.find_or_create_by_user_id(user_id)
|
248
|
+
folded_page.folded = status
|
249
|
+
folded_page.save
|
302
250
|
end
|
303
251
|
|
304
252
|
def changed_publicity?
|
305
253
|
self.public_was != self.public
|
306
254
|
end
|
307
255
|
|
308
|
-
# Sets my restricted value to all child pages
|
309
|
-
#
|
310
256
|
def set_restrictions_to_child_pages
|
311
|
-
descendants.
|
312
|
-
|
313
|
-
|
314
|
-
def contains_feed?
|
315
|
-
definition["feed"]
|
257
|
+
descendants.each do |child|
|
258
|
+
child.update_attributes(:restricted => self.restricted?)
|
259
|
+
end
|
316
260
|
end
|
317
261
|
|
318
|
-
|
319
|
-
|
320
|
-
!!definition["redirects_to_external"]
|
262
|
+
def inherit_restricted_status
|
263
|
+
self.restricted = parent.restricted?
|
321
264
|
end
|
322
265
|
|
323
266
|
# Returns the first published child
|
@@ -342,29 +285,6 @@ module Alchemy
|
|
342
285
|
end
|
343
286
|
end
|
344
287
|
|
345
|
-
def locker_name
|
346
|
-
return I18n.t('unknown') if self.locker.nil?
|
347
|
-
self.locker.name
|
348
|
-
end
|
349
|
-
|
350
|
-
def rootpage?
|
351
|
-
!self.new_record? && self.parent_id.blank?
|
352
|
-
end
|
353
|
-
|
354
|
-
def systempage?
|
355
|
-
return true if Page.root.nil?
|
356
|
-
rootpage? || (self.parent_id == Page.root.id && !self.language_root?)
|
357
|
-
end
|
358
|
-
|
359
|
-
# Overwrites the cache_key method.
|
360
|
-
def cache_key(request = nil)
|
361
|
-
"alchemy/pages/#{id}"
|
362
|
-
end
|
363
|
-
|
364
|
-
def taggable?
|
365
|
-
definition['taggable'] == true
|
366
|
-
end
|
367
|
-
|
368
288
|
# Publishes the page
|
369
289
|
#
|
370
290
|
# Sets public true and saves the object.
|
@@ -373,43 +293,35 @@ module Alchemy
|
|
373
293
|
self.save
|
374
294
|
end
|
375
295
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
# or if a page is external or if the URL is the same
|
380
|
-
#
|
381
|
-
# @param [TreeNode]
|
382
|
-
# A tree node with new lft, rgt, depth, url, parent_id and restricted indexes to be updated
|
383
|
-
#
|
384
|
-
def update_node!(node)
|
385
|
-
hash = {lft: node.left, rgt: node.right, parent_id: node.parent, depth: node.depth, restricted: node.restricted}
|
386
|
-
|
387
|
-
if Config.get(:url_nesting) && !self.redirects_to_external? && self.urlname != node.url
|
388
|
-
LegacyPageUrl.create(page_id: self.id, urlname: self.urlname)
|
389
|
-
hash.merge!(urlname: node.url)
|
390
|
-
end
|
391
|
-
|
392
|
-
self.class.update_all(hash, {id: self.id})
|
296
|
+
def set_language_from_parent_or_default_language
|
297
|
+
self.language = self.parent.language || Language.get_default
|
298
|
+
set_language_code
|
393
299
|
end
|
394
300
|
|
395
301
|
private
|
396
302
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
303
|
+
# Returns the next or previous page on the same level or nil.
|
304
|
+
#
|
305
|
+
# @param [String]
|
306
|
+
# Pass '>' for next and '<' for previous page.
|
307
|
+
#
|
308
|
+
# @option options [Boolean] :restricted (nil)
|
309
|
+
# only restricted pages (true), skip restricted pages (false)
|
310
|
+
# @option options [Boolean] :public (true)
|
311
|
+
# only public pages (true), skip public pages (false)
|
312
|
+
#
|
313
|
+
def next_or_previous(dir = '>', options = {})
|
314
|
+
options = {
|
315
|
+
restricted: false,
|
316
|
+
public: true
|
317
|
+
}.update(options)
|
318
|
+
|
319
|
+
self_and_siblings
|
320
|
+
.where(["#{self.class.table_name}.lft #{dir} ?", lft])
|
321
|
+
.where(public: options[:public])
|
322
|
+
.where(restricted: options[:restricted])
|
323
|
+
.order(dir == '>' ? 'lft' : 'lft DESC')
|
324
|
+
.limit(1).first
|
413
325
|
end
|
414
326
|
|
415
327
|
def set_language_code
|
@@ -422,11 +334,5 @@ module Alchemy
|
|
422
334
|
legacy_urls.find_or_create_by_urlname(:urlname => urlname_was)
|
423
335
|
end
|
424
336
|
|
425
|
-
# Sets my restricted status to parent's restricted status
|
426
|
-
#
|
427
|
-
def inherit_restricted_status
|
428
|
-
self.restricted = parent.restricted?
|
429
|
-
end
|
430
|
-
|
431
337
|
end
|
432
338
|
end
|