summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/nodes_controller.rb3
-rw-r--r--app/helpers/node_actions_helper.rb12
-rw-r--r--app/models/node.rb20
-rw-r--r--app/models/node_action.rb1
-rw-r--r--app/views/nodes/edit.html.erb6
-rw-r--r--config/locales/de.yml3
-rw-r--r--config/locales/en.yml3
-rw-r--r--test/models/node_test.rb29
8 files changed, 75 insertions, 2 deletions
diff --git a/app/controllers/nodes_controller.rb b/app/controllers/nodes_controller.rb
index 5b919b1f..22606674 100644
--- a/app/controllers/nodes_controller.rb
+++ b/app/controllers/nodes_controller.rb
@@ -116,7 +116,8 @@ class NodesController < ApplicationController
116 rescue LockedByAnotherUser => e 116 rescue LockedByAnotherUser => e
117 flash[:error] = e.message 117 flash[:error] = e.message
118 redirect_to node_path(@node) 118 redirect_to node_path(@node)
119 rescue ActiveRecord::RecordInvalid 119 rescue ActiveRecord::RecordInvalid => e
120 flash.now[:error] = e.record.errors.full_messages.to_sentence
120 @page = @node.autosave || @node.draft || @node.head 121 @page = @node.autosave || @node.draft || @node.head
121 render :action => :edit 122 render :action => :edit
122 end 123 end
diff --git a/app/helpers/node_actions_helper.rb b/app/helpers/node_actions_helper.rb
index 4cf990b8..c4de46cc 100644
--- a/app/helpers/node_actions_helper.rb
+++ b/app/helpers/node_actions_helper.rb
@@ -28,7 +28,8 @@ module NodeActionsHelper
28 "admin_revoke" => "shield-minus", 28 "admin_revoke" => "shield-minus",
29 "event_create" => "calendar-plus", 29 "event_create" => "calendar-plus",
30 "event_update" => "calendar-event", 30 "event_update" => "calendar-event",
31 "event_destroy" => "calendar-x" 31 "event_destroy" => "calendar-x",
32 "node_external_url" => "world"
32 }.freeze 33 }.freeze
33 34
34 def verb_icon action 35 def verb_icon action
@@ -64,6 +65,7 @@ module NodeActionsHelper
64 return true if m["translation_diff"].present? 65 return true if m["translation_diff"].present?
65 return true if m["changes"].present? || m["description_changed"] 66 return true if m["changes"].present? || m["description_changed"]
66 return true if m["title"].is_a?(Hash) && m.dig("title", "from") != m.dig("title", "to") 67 return true if m["title"].is_a?(Hash) && m.dig("title", "from") != m.dig("title", "to")
68 return true if m["external_url"].present?
67 %w[author tags template_changed assets assets_changed assets_reordered 69 %w[author tags template_changed assets assets_changed assets_reordered
68 abstract_changed body_changed].any? { |key| m[key].present? } 70 abstract_changed body_changed].any? { |key| m[key].present? }
69 end 71 end
@@ -95,6 +97,9 @@ module NodeActionsHelper
95 end 97 end
96 items << t("node_actions.assets_reordered") if m["assets_reordered"] 98 items << t("node_actions.assets_reordered") if m["assets_reordered"]
97 items << t("node_actions.assets_changed") if m["assets_changed"] 99 items << t("node_actions.assets_changed") if m["assets_changed"]
100 items << t("node_actions.detail_external_url",
101 :from => m.dig("external_url", "from").presence || t("node_actions.event_none"),
102 :to => m.dig("external_url", "to").presence || t("node_actions.event_none")) if m["external_url"]
98 items 103 items
99 end 104 end
100 105
@@ -400,6 +405,11 @@ module NodeActionsHelper
400 event_sentence(action, "event_destroy") 405 event_sentence(action, "event_destroy")
401 end 406 end
402 407
408 def summarize_node_external_url action
409 t("node_actions.node_external_url", :actor => actor_ref(action),
410 :subject => subject_ref(action)).html_safe
411 end
412
403 def event_sentence action, key 413 def event_sentence action, key
404 if action.node 414 if action.node
405 t("node_actions.#{key}_on", :actor => actor_ref(action), 415 t("node_actions.#{key}_on", :actor => actor_ref(action),
diff --git a/app/models/node.rb b/app/models/node.rb
index f8c29b48..4b7c9772 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -42,6 +42,9 @@ class Node < ApplicationRecord
42 :inclusion => { :in => ->(_) { Page.custom_templates } }, 42 :inclusion => { :in => ->(_) { Page.custom_templates } },
43 :allow_blank => true, 43 :allow_blank => true,
44 :if => :default_template_name_changed? 44 :if => :default_template_name_changed?
45 validates :external_url, :format => { :with => %r{\Ahttps?://}i,
46 :allow_blank => true,
47 :message => :must_be_http }
45 48
46 # Everything outside the Trash subtree, the Trash node included. 49 # Everything outside the Trash subtree, the Trash node included.
47 # Relies on unique_name being authoritative for tree position -- 50 # Relies on unique_name being authoritative for tree position --
@@ -605,6 +608,23 @@ class Node < ApplicationRecord
605 self.created_at < new_id_format_date ? unique_path : id 608 self.created_at < new_id_format_date ? unique_path : id
606 end 609 end
607 610
611 def update_external_url!(url, current_user = nil)
612 normalised = url.presence
613 return false if normalised == external_url
614
615 guard_live_change!(current_user)
616 previous = external_url
617
618 transaction do
619 update!(:external_url => normalised)
620 NodeAction.record!(:node => self, :user => current_user,
621 :action => "node_external_url",
622 :path => unique_name,
623 :external_url => { "from" => previous, "to" => normalised })
624 end
625 true
626 end
627
608 # Full-text search across all locale translations using PostgreSQL tsvector. 628 # Full-text search across all locale translations using PostgreSQL tsvector.
609 # Uses 'simple' dictionary (no stemming, no stopwords) so queries work 629 # Uses 'simple' dictionary (no stemming, no stopwords) so queries work
610 # across German and English content without language detection. 630 # across German and English content without language detection.
diff --git a/app/models/node_action.rb b/app/models/node_action.rb
index bfa469b1..f1e4eaea 100644
--- a/app/models/node_action.rb
+++ b/app/models/node_action.rb
@@ -134,6 +134,7 @@ class NodeAction < ApplicationRecord
134 # from the node verbs' "tags", which is a pair, 134 # from the node verbs' "tags", which is a pair,
135 # so one renderer cannot mistake the other. 135 # so one renderer cannot mistake the other.
136 # "path" -- the node's unique_name, when it has a node 136 # "path" -- the node's unique_name, when it has a node
137 # "external_url" -- pair
137 # 138 #
138 # On "event_update" only, and only when something changed -- an 139 # On "event_update" only, and only when something changed -- an
139 # update that changes nothing records no entry at all: 140 # update that changes nothing records no entry at all:
diff --git a/app/views/nodes/edit.html.erb b/app/views/nodes/edit.html.erb
index b65416e1..c153c241 100644
--- a/app/views/nodes/edit.html.erb
+++ b/app/views/nodes/edit.html.erb
@@ -114,6 +114,12 @@
114 %> 114 %>
115 </div> 115 </div>
116 116
117 <div class="layout_row_label"><%= t(".external_url") %></div>
118 <div class="layout_row_content">
119 <%= f.text_field :external_url %>
120 <p class="field_hint"><%= t(".external_url_hint") %></p>
121 </div>
122
117 <div class="layout_row_label"><%= t(".tags") %></div> 123 <div class="layout_row_label"><%= t(".tags") %></div>
118 <div class="layout_row_content"> 124 <div class="layout_row_content">
119 <%= text_field_tag :tag_list, @page.tag_list.join(', ') %> 125 <%= text_field_tag :tag_list, @page.tag_list.join(', ') %>
diff --git a/config/locales/de.yml b/config/locales/de.yml
index bfa58360..5197b00c 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -255,6 +255,7 @@ de:
255 detail_event_allday: "ganztägig %{from} → %{to}" 255 detail_event_allday: "ganztägig %{from} → %{to}"
256 detail_event_end: "Ende %{from} → %{to}" 256 detail_event_end: "Ende %{from} → %{to}"
257 event_none: "—" 257 event_none: "—"
258 node_external_url: "%{actor} hat die externe Homepage von %{subject} geändert"
258 unknown_event: "ein Termin" 259 unknown_event: "ein Termin"
259 260
260 open_gallery: "Gallerie anzeigen" 261 open_gallery: "Gallerie anzeigen"
@@ -456,6 +457,8 @@ de:
456 set_headline_label: "Als Aufmacherbild setzen" 457 set_headline_label: "Als Aufmacherbild setzen"
457 set_headline_title: "Dieses Foto als Aufmacherbild der Seite verwenden" 458 set_headline_title: "Dieses Foto als Aufmacherbild der Seite verwenden"
458 remove_image_label: "Bild entfernen" 459 remove_image_label: "Bild entfernen"
460 external_url: "Externe Homepage"
461 external_url_hint: "Die eigene Website einer Dezentrale, wird auf dessen Seite angezeigt. Vorsicht, Änderung wird ohne Draft sofort veröffentlicht."
459 show: 462 show:
460 status: "Status" 463 status: "Status"
461 head: "Head" 464 head: "Head"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index f786ca81..7e484c66 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -201,6 +201,7 @@ en:
201 detail_event_coordinates: "coordinates changed" 201 detail_event_coordinates: "coordinates changed"
202 detail_event_allday: "all-day %{from} → %{to}" 202 detail_event_allday: "all-day %{from} → %{to}"
203 detail_event_end: "end %{from} → %{to}" 203 detail_event_end: "end %{from} → %{to}"
204 node_external_url: "%{actor} changed the external homepage of %{subject}"
204 event_none: "—" 205 event_none: "—"
205 unknown_event: "an event" 206 unknown_event: "an event"
206 207
@@ -403,6 +404,8 @@ en:
403 set_headline_label: "Set as headline image" 404 set_headline_label: "Set as headline image"
404 set_headline_title: "Use this photo as the page's headline image" 405 set_headline_title: "Use this photo as the page's headline image"
405 remove_image_label: "Remove image" 406 remove_image_label: "Remove image"
407 external_url: "External homepage"
408 external_url_hint: "A chapter's own website, shown on its page. N.B.: Changing takes effect immediately."
406 show: 409 show:
407 status: "Status" 410 status: "Status"
408 head: "Head" 411 head: "Head"
diff --git a/test/models/node_test.rb b/test/models/node_test.rb
index a34094a0..c7fee58e 100644
--- a/test/models/node_test.rb
+++ b/test/models/node_test.rb
@@ -1072,4 +1072,33 @@ class NodeTest < ActiveSupport::TestCase
1072 assert_not node.reload.in_trash? 1072 assert_not node.reload.in_trash?
1073 assert_equal club.id, node.parent_id 1073 assert_equal club.id, node.parent_id
1074 end 1074 end
1075
1076 test "setting an external url is gated and witnessed" do
1077 node = Node.root.children.create!(:slug => "chapter_url_test")
1078
1079 assert_difference -> { NodeAction.where(:action => "node_external_url").count }, 1 do
1080 assert node.update_external_url!("https://example.org", users(:aaron))
1081 end
1082
1083 assert_equal "https://example.org", node.reload.external_url
1084 entry = NodeAction.where(:action => "node_external_url").last
1085 assert_equal({ "from" => nil, "to" => "https://example.org" },
1086 entry.metadata["external_url"])
1087 end
1088
1089 test "a javascript url is refused" do
1090 node = Node.root.children.create!(:slug => "chapter_url_reject")
1091 assert_raises(ActiveRecord::RecordInvalid) do
1092 node.update_external_url!("javascript:alert(1)", users(:aaron))
1093 end
1094 end
1095
1096 test "setting an external url on a restricted node needs redaktion" do
1097 node = Node.find_by(:unique_name => "updates") ||
1098 Node.root.children.create!(:slug => "updates")
1099
1100 assert_raises(ActiveRecord::RecordInvalid) do
1101 node.update_external_url!("https://example.org", users(:quentin))
1102 end
1103 end
1075end 1104end