summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/nodes_controller.rb2
-rw-r--r--app/models/node.rb27
-rw-r--r--app/models/page.rb36
3 files changed, 60 insertions, 5 deletions
diff --git a/app/controllers/nodes_controller.rb b/app/controllers/nodes_controller.rb
index ef47f258..47b8573a 100644
--- a/app/controllers/nodes_controller.rb
+++ b/app/controllers/nodes_controller.rb
@@ -282,7 +282,7 @@ class NodesController < ApplicationController
282 def page_params 282 def page_params
283 params.fetch(:page, {}).permit(:title, :abstract, :body, :template_name, 283 params.fetch(:page, {}).permit(:title, :abstract, :body, :template_name,
284 :published_at, :user_id, :slug, :parent_node_id, 284 :published_at, :user_id, :slug, :parent_node_id,
285 :external_url) 285 :external_url, :redirect, :redirect_node_id)
286 end 286 end
287 287
288 def find_node 288 def find_node
diff --git a/app/models/node.rb b/app/models/node.rb
index 6fc6c3dc..52e06d2d 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -236,6 +236,32 @@ class Node < ApplicationRecord
236 raise ActiveRecord::RecordInvalid.new(self) 236 raise ActiveRecord::RecordInvalid.new(self)
237 end 237 end
238 238
239 if self.draft.redirect.present?
240 if self.draft.redirect_node_id == self.id
241 errors.add(:base, :redirect_to_self)
242 raise ActiveRecord::RecordInvalid.new(self)
243 end
244
245 if self.draft.redirect_node_id.present?
246 target = Node.find_by(:id => self.draft.redirect_node_id)
247
248 unless target
249 errors.add(:base, :redirect_target_missing)
250 raise ActiveRecord::RecordInvalid.new(self)
251 end
252
253 if target.head&.redirect.present?
254 errors.add(:base, :redirect_to_redirect)
255 raise ActiveRecord::RecordInvalid.new(self)
256 end
257 end
258
259 if Page.redirecting_to(self.id).exists?
260 errors.add(:base, :redirect_would_chain)
261 raise ActiveRecord::RecordInvalid.new(self)
262 end
263 end
264
239 path_before = self.unique_name 265 path_before = self.unique_name
240 266
241 ActiveRecord::Base.transaction do 267 ActiveRecord::Base.transaction do
@@ -602,6 +628,7 @@ class Node < ApplicationRecord
602 def self.search(term, _ = {}) 628 def self.search(term, _ = {})
603 joins(head: :translations) 629 joins(head: :translations)
604 .where("page_translations.search_vector @@ plainto_tsquery('simple', ?)", term) 630 .where("page_translations.search_vector @@ plainto_tsquery('simple', ?)", term)
631 .where(:pages => { :redirect => nil })
605 .distinct 632 .distinct
606 end 633 end
607 634
diff --git a/app/models/page.rb b/app/models/page.rb
index c2dc227b..9115ccb0 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -4,6 +4,7 @@ class Page < ApplicationRecord
4 4
5 PUBLIC_TEMPLATE_PATH = File.join(%w(custom page_templates public)) 5 PUBLIC_TEMPLATE_PATH = File.join(%w(custom page_templates public))
6 FULL_PUBLIC_TEMPLATE_PATH = Rails.root.join('app', 'views', PUBLIC_TEMPLATE_PATH) 6 FULL_PUBLIC_TEMPLATE_PATH = Rails.root.join('app', 'views', PUBLIC_TEMPLATE_PATH)
7 REDIRECT_MODES = %w[temporary permanent].freeze
7 8
8 # Mixins and Plugins 9 # Mixins and Plugins
9 acts_as_taggable 10 acts_as_taggable
@@ -23,6 +24,7 @@ class Page < ApplicationRecord
23 validates :external_url, :format => { :with => %r{\Ahttps?://}i, 24 validates :external_url, :format => { :with => %r{\Ahttps?://}i,
24 :allow_blank => true, 25 :allow_blank => true,
25 :message => :must_be_http } 26 :message => :must_be_http }
27 validates :redirect, :inclusion => { :in => REDIRECT_MODES }, :allow_nil => true
26 validates_format_of :slug, :with => /\A[A-Za-z0-9][A-Za-z0-9_-]*\z/, 28 validates_format_of :slug, :with => /\A[A-Za-z0-9][A-Za-z0-9_-]*\z/,
27 :unless => -> { slug.blank? } 29 :unless => -> { slug.blank? }
28 validate :page_slug_not_reserved 30 validate :page_slug_not_reserved
@@ -181,7 +183,6 @@ class Page < ApplicationRecord
181 end 183 end
182 184
183 def valid_template 185 def valid_template
184
185 if template_name && template_exists? 186 if template_name && template_exists?
186 public_template_path 187 public_template_path
187 else 188 else
@@ -212,15 +213,16 @@ class Page < ApplicationRecord
212 self.slug = page.slug 213 self.slug = page.slug
213 self.parent_node_id = page.parent_node_id 214 self.parent_node_id = page.parent_node_id
214 self.external_url = page.external_url 215 self.external_url = page.external_url
216 self.redirect = page.redirect
217 self.redirect_node_id = page.redirect_node_id
215 self.tag_list = page.tag_list 218 self.tag_list = page.tag_list
216 self.template_name ||= page.template_name 219 self.template_name ||= page.template_name
217 self.published_at = page.published_at 220 self.published_at = page.published_at
218 221
219 # Clone translated attributes -- update each locale in place rather 222 # Clone translated attributes, update each locale in place rather
220 # than delete-and-recreate, so a locale whose content is genuinely 223 # than delete-and-recreate, so a locale whose content is genuinely
221 # unchanged keeps its real created_at/updated_at instead of looking 224 # unchanged keeps its real created_at/updated_at instead of looking
222 # freshly touched on every single save (which was silently defeating 225 # freshly touched on every single save.
223 # Page.find_with_outdated_translations' whole staleness comparison).
224 # search_vector is excluded deliberately: it's DB-trigger-maintained 226 # search_vector is excluded deliberately: it's DB-trigger-maintained
225 # from title/abstract, not real content, and comparing a precomputed 227 # from title/abstract, not real content, and comparing a precomputed
226 # tsvector risked a false "changed" from representation noise alone. 228 # tsvector risked a false "changed" from representation noise alone.
@@ -309,6 +311,32 @@ class Page < ApplicationRecord
309 published_at.nil? ? true : published_at < Time.now 311 published_at.nil? ? true : published_at < Time.now
310 end 312 end
311 313
314 # The destination this page sends visitors to, or nil. An internal target
315 # wins over an external one. A target that is restricted or has no head is
316 # no destination at all, so the page renders itself rather than linking to
317 # nothing. The banner partial calls this too, so the precedence cannot
318 # drift between the redirect and the link.
319 def redirect_target
320 return nil if redirect.blank?
321
322 if redirect_node_id.present?
323 node = Node.find_by(:id => redirect_node_id)
324 return nil unless node&.head && !node.restricted?
325 return node.unique_name
326 end
327
328 external_url.presence
329 end
330
331 def redirect_status
332 redirect == "permanent" ? :moved_permanently : :found
333 end
334
335 # Nodes whose published page redirects here
336 def self.redirecting_to(node_id)
337 Node.where(:head_id => where(:redirect_node_id => node_id).select(:id))
338 end
339
312 # The address this page will have once published. 340 # The address this page will have once published.
313 def prospective_unique_name 341 def prospective_unique_name
314 return nil if parent_node_id.nil? 342 return nil if parent_node_id.nil?