alchemy_cms 2.6.2.1 → 2.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/alchemy_cms.gemspec +0 -1
- data/app/assets/stylesheets/alchemy/flash.scss +1 -0
- data/app/controllers/alchemy/admin/pages_controller.rb +90 -21
- data/app/controllers/alchemy/admin/pictures_controller.rb +1 -1
- data/app/controllers/alchemy/admin/users_controller.rb +1 -11
- data/app/controllers/alchemy/users_controller.rb +0 -3
- data/app/models/alchemy/page.rb +31 -10
- data/app/models/alchemy/tree_node.rb +4 -0
- data/app/models/alchemy/user.rb +16 -0
- data/app/views/alchemy/admin/pages/_page.html.erb +1 -1
- data/app/views/alchemy/admin/resources/_date.html.erb +8 -0
- data/app/views/alchemy/admin/resources/_datetime.html.erb +1 -2
- data/app/views/alchemy/admin/users/_table.html.erb +2 -2
- data/config/locales/alchemy.de.yml +1 -1
- data/config/locales/alchemy.en.yml +1 -0
- data/lib/alchemy/version.rb +1 -1
- data/spec/controllers/admin/pages_controller_spec.rb +89 -0
- data/spec/controllers/admin/users_controller_spec.rb +66 -30
- data/spec/controllers/users_controller_spec.rb +1 -2
- data/spec/libraries/resource_spec.rb +1 -3
- data/spec/libraries/resources_helper_spec.rb +1 -4
- data/spec/models/page_spec.rb +143 -9
- data/spec/models/user_spec.rb +40 -0
- data/vendor/assets/javascripts/jquery_plugins/jquery.ui.nestedSortable.js +8 -2
- metadata +63 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c1c44138b3e7be76b6a952789f4b54045aa8f8a
|
4
|
+
data.tar.gz: c8cd62e435145ef96d4b874ad68b840cbdf8524c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f3bde0e560a5c53c7305a6371851e31b2a980dae1b49781e98ec3c1129dcfc2542ae8686cb5e02d13378881e50edbdbf0b315e211236bc23d4407867088bb8b
|
7
|
+
data.tar.gz: 6194a45096869a6ae0faed7e4fdad33c88a326e1505bc716c886388ea33efb6ea0e265dbc0275a76fb2973e21138a17fe577cb135b5472c2c283019311b17f44
|
data/alchemy_cms.gemspec
CHANGED
@@ -57,7 +57,6 @@ POST_INSTALL
|
|
57
57
|
s.add_development_dependency %q<capybara>, ['~> 2.0.3']
|
58
58
|
s.add_development_dependency %q<factory_girl_rails>
|
59
59
|
s.add_development_dependency %q<rspec-rails>, ['~> 2.13.1']
|
60
|
-
s.add_development_dependency %q<sqlite3>
|
61
60
|
s.add_development_dependency %q<yard>
|
62
61
|
s.add_development_dependency %q<redcarpet>
|
63
62
|
|
@@ -208,17 +208,19 @@ module Alchemy
|
|
208
208
|
@sorting = true
|
209
209
|
end
|
210
210
|
|
211
|
+
# Receives a JSON object representing a language tree to be ordered
|
212
|
+
# and updates all pages in that language structure to their correct indexes
|
211
213
|
def order
|
212
|
-
@page_root = Page.language_root_for(session[:language_id])
|
213
|
-
|
214
|
-
# Taken from https://github.com/matenia/jQuery-Awesome-Nested-Set-Drag-and-Drop
|
215
214
|
neworder = JSON.parse(params[:set])
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
215
|
+
rootpage = Page.language_root_for(session[:language_id])
|
216
|
+
|
217
|
+
tree = create_tree(neworder, rootpage)
|
218
|
+
|
219
|
+
Alchemy::Page.transaction do
|
220
|
+
tree.each do |key, node|
|
221
|
+
dbitem = Page.find(key)
|
222
|
+
dbitem.update_node!(node)
|
223
|
+
end
|
222
224
|
end
|
223
225
|
|
224
226
|
flash[:notice] = _t("Pages order saved")
|
@@ -248,25 +250,92 @@ module Alchemy
|
|
248
250
|
|
249
251
|
private
|
250
252
|
|
251
|
-
|
252
|
-
|
253
|
+
# Returns the current left index and the aggregated hash of tree nodes indexed by page id visited so far
|
254
|
+
#
|
255
|
+
# Visits a batch of children nodes, assigns them the correct ordering indexes and spuns recursively the same
|
256
|
+
# procedure on their children, if any
|
257
|
+
#
|
258
|
+
# @param [Array]
|
259
|
+
# An array of children nodes to be visited
|
260
|
+
# @param [Integer]
|
261
|
+
# The lft attribute that should be given to the first node in the array
|
262
|
+
# @param [Integer]
|
263
|
+
# The page id of the parent of this batch of children nodes
|
264
|
+
# @param [Integer]
|
265
|
+
# The depth at which these children reside
|
266
|
+
# @param [Hash]
|
267
|
+
# A Hash of TreeNode's indexed by their page ids
|
268
|
+
# @param [String]
|
269
|
+
# The url for the parent node of these children
|
270
|
+
# @param [Boolean]
|
271
|
+
# Whether these children reside in a restricted branch according to their ancestors
|
272
|
+
#
|
273
|
+
def visit_nodes(nodes, my_left, parent, depth, tree, url, restricted)
|
274
|
+
nodes.each do |item|
|
275
|
+
my_right = my_left + 1
|
276
|
+
my_restricted = item['restricted'] || restricted
|
277
|
+
urls = process_url(url, item)
|
278
|
+
|
279
|
+
if item['children']
|
280
|
+
my_right, tree = visit_nodes(item['children'], my_left + 1, item['id'], depth + 1, tree, urls[:children_path], my_restricted)
|
281
|
+
end
|
282
|
+
|
283
|
+
tree[item['id']] = TreeNode.new(my_left, my_right, parent, depth, urls[:my_urlname], my_restricted)
|
284
|
+
my_left = my_right + 1
|
285
|
+
end
|
286
|
+
|
287
|
+
[my_left, tree]
|
253
288
|
end
|
254
289
|
|
255
|
-
|
256
|
-
|
290
|
+
# Returns a Hash of TreeNode's indexed by their page ids
|
291
|
+
#
|
292
|
+
# Grabs the array representing a tree structure of pages passed as a parameter,
|
293
|
+
# visits it and creates a map of TreeNodes indexed by page id featuring Nested Set
|
294
|
+
# ordering information consisting of the left, right, depth and parent_id indexes as
|
295
|
+
# well as a node's url and restricted status
|
296
|
+
#
|
297
|
+
# @param [Array]
|
298
|
+
# An Array representing a tree of Alchemy::Page's
|
299
|
+
# @param [Alchemy::Page]
|
300
|
+
# The root page for the language being ordered
|
301
|
+
#
|
302
|
+
def create_tree(items, rootpage)
|
303
|
+
_, tree = visit_nodes(items, rootpage.lft + 1, rootpage.id, rootpage.depth + 1, {}, "", rootpage.restricted)
|
304
|
+
tree
|
257
305
|
end
|
258
306
|
|
259
|
-
#
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
307
|
+
# Returns a pair, the path that a given tree node should take, and the path its children should take
|
308
|
+
#
|
309
|
+
# This function will add a node's own slug into their ancestor's path
|
310
|
+
# in order to create the full URL of a node
|
311
|
+
#
|
312
|
+
# NOTE: external and invisible pages are not part of the full path of their children
|
313
|
+
#
|
314
|
+
# @param [String]
|
315
|
+
# The node's ancestors path
|
316
|
+
# @param [Hash]
|
317
|
+
# A children node
|
318
|
+
#
|
319
|
+
def process_url(ancestors_path, item)
|
320
|
+
default_urlname = (ancestors_path.blank? ? "" : "#{ancestors_path}/") + item['slug']
|
321
|
+
|
322
|
+
pair = {my_urlname: default_urlname, children_path: default_urlname}
|
323
|
+
|
324
|
+
if item['external'] == true || item['visible'] == false
|
325
|
+
# children ignore an ancestor in their path if external or invisible
|
326
|
+
pair[:children_path] = ancestors_path
|
267
327
|
end
|
328
|
+
|
329
|
+
pair
|
330
|
+
end
|
331
|
+
|
332
|
+
def load_page
|
333
|
+
@page = Page.find(params[:id])
|
268
334
|
end
|
269
335
|
|
336
|
+
def pages_from_raw_request
|
337
|
+
request.raw_post.split('&').map { |i| i = {i.split('=')[0].gsub(/[^0-9]/, '') => i.split('=')[1]} }
|
338
|
+
end
|
270
339
|
end
|
271
340
|
end
|
272
341
|
end
|
@@ -164,7 +164,7 @@ module Alchemy
|
|
164
164
|
def pictures_per_page_for_size(size)
|
165
165
|
case size
|
166
166
|
when 'small'
|
167
|
-
per_page = in_overlay? ?
|
167
|
+
per_page = in_overlay? ? 25 : (per_page_value_for_screen_size * 2.9).floor
|
168
168
|
when 'large'
|
169
169
|
per_page = in_overlay? ? 4 : (per_page_value_for_screen_size / 1.7).floor + 1
|
170
170
|
else
|
@@ -27,14 +27,7 @@ module Alchemy
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def create
|
30
|
-
@user = User.
|
31
|
-
if @user.save
|
32
|
-
if @user.role == "registered" && params[:send_credentials]
|
33
|
-
Notifications.registered_user_created(@user).deliver
|
34
|
-
elsif params[:send_credentials]
|
35
|
-
Notifications.admin_user_created(@user).deliver
|
36
|
-
end
|
37
|
-
end
|
30
|
+
@user = User.create(params[:user])
|
38
31
|
render_errors_or_redirect(
|
39
32
|
@user,
|
40
33
|
admin_users_path,
|
@@ -50,9 +43,6 @@ module Alchemy
|
|
50
43
|
else
|
51
44
|
@user.update_without_password(params[:user])
|
52
45
|
end
|
53
|
-
if params[:send_credentials]
|
54
|
-
Notifications.admin_user_created(@user).deliver
|
55
|
-
end
|
56
46
|
render_errors_or_redirect(
|
57
47
|
@user,
|
58
48
|
admin_users_path,
|
@@ -18,9 +18,6 @@ module Alchemy
|
|
18
18
|
def create
|
19
19
|
@user = User.new(params[:user])
|
20
20
|
if @user.save
|
21
|
-
if params[:send_credentials]
|
22
|
-
Notifications.admin_user_created(@user).deliver
|
23
|
-
end
|
24
21
|
flash[:notice] = _t('Successfully signup admin user')
|
25
22
|
sign_in :user, @user
|
26
23
|
redirect_to admin_dashboard_path
|
data/app/models/alchemy/page.rb
CHANGED
@@ -54,8 +54,8 @@ module Alchemy
|
|
54
54
|
attr_accessor :do_not_validate_language
|
55
55
|
|
56
56
|
before_save :set_language_code, :unless => :systempage?
|
57
|
-
before_save :
|
58
|
-
|
57
|
+
before_save :inherit_restricted_status, if: -> { parent && parent.restricted? }, unless: :systempage?
|
58
|
+
after_update :set_restrictions_to_child_pages, unless: :systempage?
|
59
59
|
after_update :create_legacy_url, :if => :urlname_changed?, :unless => :redirects_to_external?
|
60
60
|
|
61
61
|
scope :language_roots, where(:language_root => true)
|
@@ -305,14 +305,10 @@ module Alchemy
|
|
305
305
|
self.public_was != self.public
|
306
306
|
end
|
307
307
|
|
308
|
+
# Sets my restricted value to all child pages
|
309
|
+
#
|
308
310
|
def set_restrictions_to_child_pages
|
309
|
-
descendants.
|
310
|
-
child.update_attributes(:restricted => self.restricted?)
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
def inherit_restricted_status
|
315
|
-
self.restricted = parent.restricted?
|
311
|
+
descendants.update_all(restricted: self.restricted?)
|
316
312
|
end
|
317
313
|
|
318
314
|
def contains_feed?
|
@@ -321,7 +317,7 @@ module Alchemy
|
|
321
317
|
|
322
318
|
# Returns true or false if the pages layout_description for config/alchemy/page_layouts.yml contains redirects_to_external: true
|
323
319
|
def redirects_to_external?
|
324
|
-
definition["redirects_to_external"]
|
320
|
+
!!definition["redirects_to_external"]
|
325
321
|
end
|
326
322
|
|
327
323
|
# Returns the first published child
|
@@ -377,6 +373,25 @@ module Alchemy
|
|
377
373
|
self.save
|
378
374
|
end
|
379
375
|
|
376
|
+
# Updates an Alchemy::Page based on a new ordering to be applied to it
|
377
|
+
#
|
378
|
+
# Note: Page's urls should not be updated (and a legacy URL created) if nesting is OFF
|
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})
|
393
|
+
end
|
394
|
+
|
380
395
|
private
|
381
396
|
|
382
397
|
def next_or_previous(direction = :next, options = {})
|
@@ -407,5 +422,11 @@ module Alchemy
|
|
407
422
|
legacy_urls.find_or_create_by_urlname(:urlname => urlname_was)
|
408
423
|
end
|
409
424
|
|
425
|
+
# Sets my restricted status to parent's restricted status
|
426
|
+
#
|
427
|
+
def inherit_restricted_status
|
428
|
+
self.restricted = parent.restricted?
|
429
|
+
end
|
430
|
+
|
410
431
|
end
|
411
432
|
end
|
data/app/models/alchemy/user.rb
CHANGED
@@ -18,11 +18,15 @@ module Alchemy
|
|
18
18
|
:password,
|
19
19
|
:password_confirmation,
|
20
20
|
:roles,
|
21
|
+
:send_credentials,
|
21
22
|
:tag_list
|
22
23
|
)
|
23
24
|
|
25
|
+
attr_accessor :send_credentials
|
26
|
+
|
24
27
|
has_many :folded_pages
|
25
28
|
|
29
|
+
validates_uniqueness_of :login
|
26
30
|
validates_presence_of :roles
|
27
31
|
|
28
32
|
# Unlock all locked pages before destroy and before the user gets logged out.
|
@@ -33,6 +37,8 @@ module Alchemy
|
|
33
37
|
end
|
34
38
|
end
|
35
39
|
|
40
|
+
after_save :deliver_welcome_mail, if: -> { send_credentials }
|
41
|
+
|
36
42
|
scope :admins, where(arel_table[:roles].matches("%admin%")) # not pleased with that approach
|
37
43
|
# mysql regexp word matching would be much nicer, but it's not included in SQLite functions per se.
|
38
44
|
# scope :admins, where("#{table_name}.roles REGEXP '[[:<:]]admin[[:>:]]'")
|
@@ -153,5 +159,15 @@ module Alchemy
|
|
153
159
|
self.class.logged_in_timeout
|
154
160
|
end
|
155
161
|
|
162
|
+
# Delivers a welcome mail depending from user's role.
|
163
|
+
#
|
164
|
+
def deliver_welcome_mail
|
165
|
+
if has_role?('author') || has_role?('editor') || has_role?('admin')
|
166
|
+
Notifications.admin_user_created(self).deliver
|
167
|
+
else
|
168
|
+
Notifications.registered_user_created(self).deliver
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
156
172
|
end
|
157
173
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<li id="page_<%= page.id %>" class="page_level_<%= "#{page.level} #{page.page_layout}" %>">
|
1
|
+
<li id="page_<%= page.id %>" class="page_level_<%= "#{page.level} #{page.page_layout}" %>" data-slug="<%= page.slug %>" data-restricted="<%= page.restricted? %>" data-visible="<%= page.visible? %>" data-external="<%= page.redirects_to_external? %>">
|
2
2
|
<div class="sitemap_page<%= page.locked ? ' locked' : '' %>" name="<%= page.name %>">
|
3
3
|
<div class="sitemap_left_images">
|
4
4
|
<%= sitemapFolderLink(page) unless page.children.blank? || @sorting %>
|
@@ -1,2 +1 @@
|
|
1
|
-
|
2
|
-
<td class="input"><%= f.text_field attribute[:name], :type => :date -%></td>
|
1
|
+
<%= render 'date', attribute: attribute, f: f %>
|
@@ -57,8 +57,8 @@
|
|
57
57
|
<tr>
|
58
58
|
<td> </td>
|
59
59
|
<td class="checkbox long">
|
60
|
-
<%=
|
61
|
-
<%=
|
60
|
+
<%= f.check_box(:send_credentials, checked: @user.new_record?) %>
|
61
|
+
<%= f.label(:send_credentials) %>
|
62
62
|
</td>
|
63
63
|
</tr>
|
64
64
|
<tr>
|
@@ -280,7 +280,6 @@ de:
|
|
280
280
|
"Select all": "Alle auswählen"
|
281
281
|
"Select an content": "Wählen Sie einen Inhalt aus"
|
282
282
|
"Select style": "Stilvorlage"
|
283
|
-
"Send email with credentials": "Sende Benutzerdaten per Email"
|
284
283
|
"Send reset instructions": "Anweisungen senden"
|
285
284
|
"Show Elements Window": "Elementeliste anzeigen"
|
286
285
|
"Show Preview Window": "Seitenvorschau anzeigen"
|
@@ -940,6 +939,7 @@ de:
|
|
940
939
|
password_confirmation: "Passwort Bestätigung"
|
941
940
|
roles: "Benutzerrollen"
|
942
941
|
tag_list: Tags
|
942
|
+
send_credentials: "Sende Benutzerdaten per E-Mail"
|
943
943
|
|
944
944
|
alchemy/site:
|
945
945
|
name: "Bezeichnung"
|
data/lib/alchemy/version.rb
CHANGED
@@ -37,6 +37,95 @@ module Alchemy
|
|
37
37
|
|
38
38
|
end
|
39
39
|
|
40
|
+
describe '#order' do
|
41
|
+
let(:page_1) { FactoryGirl.create(:page, visible: true) }
|
42
|
+
let(:page_2) { FactoryGirl.create(:page, visible: true) }
|
43
|
+
let(:page_3) { FactoryGirl.create(:page, visible: true) }
|
44
|
+
let(:page_item_1) { {id: page_1.id, slug: page_1.slug, restricted: false, external: page_1.redirects_to_external?, visible: page_1.visible?, children: [page_item_2]} }
|
45
|
+
let(:page_item_2) { {id: page_2.id, slug: page_2.slug, restricted: false, external: page_2.redirects_to_external?, visible: page_2.visible?, children: [page_item_3]} }
|
46
|
+
let(:page_item_3) { {id: page_3.id, slug: page_3.slug, restricted: false, external: page_3.redirects_to_external?, visible: page_3.visible? } }
|
47
|
+
let(:set_of_pages) { [page_item_1] }
|
48
|
+
|
49
|
+
it "stores the new order" do
|
50
|
+
xhr :post, :order, set: set_of_pages.to_json
|
51
|
+
page_1.reload
|
52
|
+
expect(page_1.descendants).to eq([page_2, page_3])
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'with url nesting enabled' do
|
56
|
+
before { Alchemy::Config.stub(get: true) }
|
57
|
+
|
58
|
+
it "updates the pages urlnames" do
|
59
|
+
xhr :post, :order, set: set_of_pages.to_json
|
60
|
+
[page_1, page_2, page_3].map(&:reload)
|
61
|
+
expect(page_1.urlname).to eq("#{page_1.slug}")
|
62
|
+
expect(page_2.urlname).to eq("#{page_1.slug}/#{page_2.slug}")
|
63
|
+
expect(page_3.urlname).to eq("#{page_1.slug}/#{page_2.slug}/#{page_3.slug}")
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with invisible page in tree' do
|
67
|
+
let(:page_item_2) do
|
68
|
+
{
|
69
|
+
id: page_2.id,
|
70
|
+
slug: page_2.slug,
|
71
|
+
children: [page_item_3],
|
72
|
+
visible: false
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
it "does not use this pages slug in urlnames of descendants" do
|
77
|
+
xhr :post, :order, set: set_of_pages.to_json
|
78
|
+
[page_1, page_2, page_3].map(&:reload)
|
79
|
+
expect(page_1.urlname).to eq("#{page_1.slug}")
|
80
|
+
expect(page_2.urlname).to eq("#{page_1.slug}/#{page_2.slug}")
|
81
|
+
expect(page_3.urlname).to eq("#{page_1.slug}/#{page_3.slug}")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with external page in tree' do
|
86
|
+
let(:page_item_2) do
|
87
|
+
{
|
88
|
+
id: page_2.id,
|
89
|
+
slug: page_2.slug,
|
90
|
+
children: [page_item_3],
|
91
|
+
external: true
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
it "does not use this pages slug in urlnames of descendants" do
|
96
|
+
xhr :post, :order, set: set_of_pages.to_json
|
97
|
+
[page_1, page_2, page_3].map(&:reload)
|
98
|
+
expect(page_3.urlname).to eq("#{page_1.slug}/#{page_3.slug}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'with restricted page in tree' do
|
103
|
+
let(:page_2) { FactoryGirl.create(:page, restricted: true) }
|
104
|
+
let(:page_item_2) do
|
105
|
+
{
|
106
|
+
id: page_2.id,
|
107
|
+
slug: page_2.slug,
|
108
|
+
children: [page_item_3],
|
109
|
+
restricted: true
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
it "updates restricted status of descendants" do
|
114
|
+
xhr :post, :order, set: set_of_pages.to_json
|
115
|
+
page_3.reload
|
116
|
+
expect(page_3.restricted).to be_true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it "creates legacy urls" do
|
121
|
+
xhr :post, :order, set: set_of_pages.to_json
|
122
|
+
[page_2, page_3].map(&:reload)
|
123
|
+
expect(page_2.legacy_urls.size).to eq(1)
|
124
|
+
expect(page_3.legacy_urls.size).to eq(1)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
40
129
|
describe "#configure" do
|
41
130
|
render_views
|
42
131
|
|