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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 948d15555a14793226d6299ec3ff46b2140554d9
4
- data.tar.gz: d36b05919165ec6d164865c451ef078af3b151b7
3
+ metadata.gz: 2c1c44138b3e7be76b6a952789f4b54045aa8f8a
4
+ data.tar.gz: c8cd62e435145ef96d4b874ad68b840cbdf8524c
5
5
  SHA512:
6
- metadata.gz: af33ea6919a6859b03b41905e02c3dd5edf8ac9095efb4f1af077722b6e7dbcf3631201c5a35035370e20a63420c331125995d16b9bfb99bf3dff5cec66b97fe
7
- data.tar.gz: cd84e726b6a0cab16516220b7275771c22622661fb33b217f6536ff25d50dacc52594dfae60cb90428cbb4f673be9931820fd043c9ecb25e293ac35c4a747666
6
+ metadata.gz: 3f3bde0e560a5c53c7305a6371851e31b2a980dae1b49781e98ec3c1129dcfc2542ae8686cb5e02d13378881e50edbdbf0b315e211236bc23d4407867088bb8b
7
+ data.tar.gz: 6194a45096869a6ae0faed7e4fdad33c88a326e1505bc716c886388ea33efb6ea0e265dbc0275a76fb2973e21138a17fe577cb135b5472c2c283019311b17f44
@@ -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
 
@@ -15,6 +15,7 @@ div#flash_notices {
15
15
  top: 2*$default-padding;
16
16
  @extend .icon-cross:before;
17
17
  font-size: 14px;
18
+ font-family: 'Alchemy Icons';
18
19
  }
19
20
  }
20
21
  }
@@ -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
- prev_item = nil
217
- neworder.each do |item|
218
- dbitem = Page.find(item['id'])
219
- prev_item.nil? ? dbitem.move_to_child_of(@page_root) : dbitem.move_to_right_of(prev_item)
220
- sort_children(item, dbitem) unless item['children'].nil?
221
- prev_item = dbitem.reload
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
- def load_page
252
- @page = Page.find(params[:id])
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
- def pages_from_raw_request
256
- request.raw_post.split('&').map { |i| i = {i.split('=')[0].gsub(/[^0-9]/, '') => i.split('=')[1]} }
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
- # Taken from https://github.com/matenia/jQuery-Awesome-Nested-Set-Drag-and-Drop
260
- def sort_children(element, dbitem)
261
- prevchild = nil
262
- element['children'].each do |child|
263
- childitem = Page.find(child['id'])
264
- prevchild.nil? ? childitem.move_to_child_of(dbitem) : childitem.move_to_right_of(prevchild)
265
- sort_children(child, childitem) unless child['children'].nil?
266
- prevchild = childitem
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? ? 37 : (per_page_value_for_screen_size * 2.9).floor
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.new(params[: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
@@ -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 :set_restrictions_to_child_pages, :if => :restricted_changed?, :unless => :systempage?
58
- before_save :inherit_restricted_status, :if => proc { parent && parent.restricted? }, :unless => :systempage?
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.each do |child|
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
@@ -0,0 +1,4 @@
1
+ module Alchemy
2
+ class TreeNode < Struct.new(:left, :right, :parent, :depth, :url, :restricted)
3
+ end
4
+ end
@@ -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 %>
@@ -0,0 +1,8 @@
1
+ <td class="label"><%= f.label attribute[:name] %></td>
2
+ <td class="input">
3
+ <%= f.text_field(
4
+ attribute[:name],
5
+ type: 'date',
6
+ value: l(resource_instance_variable.send(attribute[:name]) || Time.now, format: :datepicker)
7
+ ) %>
8
+ </td>
@@ -1,2 +1 @@
1
- <td class="label"><%= f.label attribute[:name] %></td>
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>&nbsp;</td>
59
59
  <td class="checkbox long">
60
- <%= check_box_tag('send_credentials', true, @user.new_record?) %>
61
- <%= label_tag('send_credentials', _t('Send email with credentials')) %>
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"
@@ -674,6 +674,7 @@ en:
674
674
  password: "Password"
675
675
  password_confirmation: "Password confirmation"
676
676
  role: "Userrole"
677
+ send_credentials: "Send email with credentials"
677
678
 
678
679
  alchemy/site:
679
680
  host: primary Host
@@ -1,6 +1,6 @@
1
1
  module Alchemy
2
2
 
3
- VERSION = "2.6.2.1"
3
+ VERSION = "2.6.3"
4
4
 
5
5
  def self.version
6
6
  VERSION
@@ -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