diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/controllers/nodes_controller.rb | 2 | ||||
| -rw-r--r-- | app/models/node.rb | 27 | ||||
| -rw-r--r-- | app/models/page.rb | 36 |
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? |
