alchemy_cms 2.6.2.1 → 2.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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