diff options
Diffstat (limited to 'app/models')
| -rw-r--r-- | app/models/node.rb | 51 | ||||
| -rw-r--r-- | app/models/page.rb | 28 |
2 files changed, 50 insertions, 29 deletions
diff --git a/app/models/node.rb b/app/models/node.rb index 4b7c9772..5c28a786 100644 --- a/app/models/node.rb +++ b/app/models/node.rb | |||
| @@ -128,6 +128,7 @@ class Node < ApplicationRecord | |||
| 128 | 128 | ||
| 129 | def autosave! attributes, current_user | 129 | def autosave! attributes, current_user |
| 130 | ensure_autosave!(current_user) | 130 | ensure_autosave!(current_user) |
| 131 | attributes = attributes.except(:slug, "slug") if attributes[:slug].blank? && attributes["slug"].blank? | ||
| 131 | self.autosave.assign_attributes(attributes) | 132 | self.autosave.assign_attributes(attributes) |
| 132 | self.autosave.save! | 133 | self.autosave.save! |
| 133 | self.autosave | 134 | self.autosave |
| @@ -227,17 +228,9 @@ class Node < ApplicationRecord | |||
| 227 | self.reload | 228 | self.reload |
| 228 | end | 229 | end |
| 229 | 230 | ||
| 230 | def staged_slug=(value) | ||
| 231 | if head.blank? | ||
| 232 | self.slug = value | ||
| 233 | else | ||
| 234 | super | ||
| 235 | end | ||
| 236 | end | ||
| 237 | |||
| 238 | def publish_draft! current_user = nil | 231 | def publish_draft! current_user = nil |
| 239 | # Return nil if nothing to publish and no staged changes | 232 | # Return nil if nothing to publish |
| 240 | return nil unless self.draft || staged_slug || staged_parent_id | 233 | return nil unless self.draft |
| 241 | 234 | ||
| 242 | guard_live_change!(current_user, :target_path => prospective_unique_name) | 235 | guard_live_change!(current_user, :target_path => prospective_unique_name) |
| 243 | 236 | ||
| @@ -263,26 +256,28 @@ class Node < ApplicationRecord | |||
| 263 | **NodeAction.head_diff(outgoing_head, self.head)) | 256 | **NodeAction.head_diff(outgoing_head, self.head)) |
| 264 | end | 257 | end |
| 265 | 258 | ||
| 266 | if staged_slug && (staged_slug != slug) | 259 | if self.head.slug.present? && self.head.slug != slug |
| 267 | self.slug = staged_slug | 260 | self.slug = self.head.slug |
| 268 | self.staged_slug = nil | ||
| 269 | end | 261 | end |
| 270 | 262 | ||
| 271 | if staged_parent_id && (staged_parent_id != parent_id) | 263 | if self.head.parent_node_id && self.head.parent_node_id != parent_id |
| 272 | new_parent = Node.find(staged_parent_id) | 264 | new_parent = Node.find_by(:id => self.head.parent_node_id) |
| 273 | 265 | ||
| 274 | if new_parent == self || self.descendants.include?(new_parent) | 266 | unless new_parent |
| 267 | errors.add(:base, :move_target_missing) | ||
| 268 | raise ActiveRecord::RecordInvalid.new(self) | ||
| 269 | end | ||
| 270 | |||
| 271 | if new_parent == self || self.descendants.include?(new_parent) || | ||
| 272 | new_parent.trash_node? || new_parent.in_trash? | ||
| 275 | errors.add(:base, :move_under_self) | 273 | errors.add(:base, :move_under_self) |
| 276 | raise ActiveRecord::RecordInvalid.new(self) | 274 | raise ActiveRecord::RecordInvalid.new(self) |
| 277 | end | 275 | end |
| 278 | 276 | ||
| 279 | self.staged_parent_id = nil | ||
| 280 | self.save! | 277 | self.save! |
| 281 | self.move_to_child_of(new_parent) | 278 | self.move_to_child_of(new_parent) |
| 282 | else | 279 | else |
| 283 | unless self.save | 280 | raise ActiveRecord::RecordInvalid.new(self) unless self.save |
| 284 | raise ActiveRecord::RecordInvalid.new(self) | ||
| 285 | end | ||
| 286 | end | 281 | end |
| 287 | 282 | ||
| 288 | self.reload | 283 | self.reload |
| @@ -369,9 +364,12 @@ class Node < ApplicationRecord | |||
| 369 | # subtree comes back exactly as it sits in the Trash: all drafts, | 364 | # subtree comes back exactly as it sits in the Trash: all drafts, |
| 370 | # nothing published. Republication is a separate, witnessed act | 365 | # nothing published. Republication is a separate, witnessed act |
| 371 | # per node. | 366 | # per node. |
| 372 | def restore_from_trash! new_parent, current_user = nil | 367 | def restore_from_trash! current_user = nil |
| 373 | return nil unless in_trash? | 368 | return nil unless in_trash? |
| 374 | 369 | ||
| 370 | target = (draft || head)&.parent_node_id | ||
| 371 | new_parent = target ? Node.find_by(:id => target) : nil | ||
| 372 | |||
| 375 | if new_parent.nil? || new_parent == self || descendants.include?(new_parent) || | 373 | if new_parent.nil? || new_parent == self || descendants.include?(new_parent) || |
| 376 | new_parent.trash_node? || new_parent.in_trash? | 374 | new_parent.trash_node? || new_parent.in_trash? |
| 377 | errors.add(:base, :restore_target_invalid) | 375 | errors.add(:base, :restore_target_invalid) |
| @@ -593,11 +591,9 @@ class Node < ApplicationRecord | |||
| 593 | root? || self.class.restricted_path?(unique_name) | 591 | root? || self.class.restricted_path?(unique_name) |
| 594 | end | 592 | end |
| 595 | 593 | ||
| 594 | # Falls back to the live address when the draft records none. | ||
| 596 | def prospective_unique_name | 595 | def prospective_unique_name |
| 597 | target_parent = staged_parent_id ? Node.find_by(:id => staged_parent_id) : parent | 596 | (draft || head)&.prospective_unique_name || unique_name |
| 598 | return nil unless target_parent | ||
| 599 | |||
| 600 | [target_parent.unique_name.presence, staged_slug.presence || slug].compact.join("/") | ||
| 601 | end | 597 | end |
| 602 | 598 | ||
| 603 | # Returns immutable node id for all new nodes so that the atom feed entry ids | 599 | # Returns immutable node id for all new nodes so that the atom feed entry ids |
| @@ -702,7 +698,7 @@ class Node < ApplicationRecord | |||
| 702 | # that draft and publishes it. | 698 | # that draft and publishes it. |
| 703 | def initialize_empty_page | 699 | def initialize_empty_page |
| 704 | if self.pages.empty? | 700 | if self.pages.empty? |
| 705 | self.draft = self.pages.create! | 701 | self.draft = self.pages.create!(:slug => self.slug, :parent_node_id => self.parent_id) |
| 706 | self.save | 702 | self.save |
| 707 | end | 703 | end |
| 708 | end | 704 | end |
| @@ -744,14 +740,11 @@ class Node < ApplicationRecord | |||
| 744 | def reserved_slug_stays_reserved | 740 | def reserved_slug_stays_reserved |
| 745 | if parent&.root? && !trash_node_already_me? | 741 | if parent&.root? && !trash_node_already_me? |
| 746 | errors.add(:slug, :reserved_for_trash) if slug == CccConventions::TRASH_SLUG | 742 | errors.add(:slug, :reserved_for_trash) if slug == CccConventions::TRASH_SLUG |
| 747 | errors.add(:staged_slug, :reserved_for_trash) if staged_slug == CccConventions::TRASH_SLUG | ||
| 748 | end | 743 | end |
| 749 | 744 | ||
| 750 | if persisted? && slug_was == CccConventions::TRASH_SLUG && Node.find(id).trash_node? | 745 | if persisted? && slug_was == CccConventions::TRASH_SLUG && Node.find(id).trash_node? |
| 751 | errors.add(:slug, :trash_immutable) if slug_changed? | 746 | errors.add(:slug, :trash_immutable) if slug_changed? |
| 752 | errors.add(:parent_id, :trash_immutable) if parent_id_changed? | 747 | errors.add(:parent_id, :trash_immutable) if parent_id_changed? |
| 753 | errors.add(:staged_slug, :trash_must_be_empty) if staged_slug.present? | ||
| 754 | errors.add(:staged_parent_id, :trash_must_be_empty) if staged_parent_id.present? | ||
| 755 | end | 748 | end |
| 756 | end | 749 | end |
| 757 | 750 | ||
diff --git a/app/models/page.rb b/app/models/page.rb index 8313b1d4..4635f1b4 100644 --- a/app/models/page.rb +++ b/app/models/page.rb | |||
| @@ -20,9 +20,13 @@ class Page < ApplicationRecord | |||
| 20 | :inclusion => { :in => ->(_) { Page.custom_templates } }, | 20 | :inclusion => { :in => ->(_) { Page.custom_templates } }, |
| 21 | :allow_blank => true, | 21 | :allow_blank => true, |
| 22 | :if => :template_name_changed? | 22 | :if => :template_name_changed? |
| 23 | validates_format_of :slug, :with => /\A[A-Za-z0-9][A-Za-z0-9_-]*\z/, | ||
| 24 | :unless => -> { slug.blank? } | ||
| 25 | validate :page_slug_not_reserved | ||
| 23 | 26 | ||
| 24 | # Associations | 27 | # Associations |
| 25 | belongs_to :node, optional: true | 28 | belongs_to :node, optional: true |
| 29 | belongs_to :parent_node, :class_name => "Node", :optional => true | ||
| 26 | belongs_to :user, optional: true | 30 | belongs_to :user, optional: true |
| 27 | belongs_to :editor, :class_name => "User", optional: true | 31 | belongs_to :editor, :class_name => "User", optional: true |
| 28 | has_many :related_assets, :dependent => :destroy | 32 | has_many :related_assets, :dependent => :destroy |
| @@ -202,6 +206,8 @@ class Page < ApplicationRecord | |||
| 202 | page.translations.reload | 206 | page.translations.reload |
| 203 | 207 | ||
| 204 | # Clone untranslated attributes | 208 | # Clone untranslated attributes |
| 209 | self.slug = page.slug | ||
| 210 | self.parent_node_id = page.parent_node_id | ||
| 205 | self.tag_list = page.tag_list | 211 | self.tag_list = page.tag_list |
| 206 | self.template_name ||= page.template_name | 212 | self.template_name ||= page.template_name |
| 207 | self.published_at = page.published_at | 213 | self.published_at = page.published_at |
| @@ -292,6 +298,20 @@ class Page < ApplicationRecord | |||
| 292 | published_at.nil? ? true : published_at < Time.now | 298 | published_at.nil? ? true : published_at < Time.now |
| 293 | end | 299 | end |
| 294 | 300 | ||
| 301 | # The address this page will have once published. | ||
| 302 | def prospective_unique_name | ||
| 303 | return nil if parent_node_id.nil? | ||
| 304 | |||
| 305 | parent = Node.find_by(:id => parent_node_id) | ||
| 306 | return nil unless parent | ||
| 307 | |||
| 308 | [parent.unique_name.presence, slug].compact.join("/") | ||
| 309 | end | ||
| 310 | |||
| 311 | def parent_node_missing? | ||
| 312 | parent_node_id.present? && !Node.exists?(:id => parent_node_id) | ||
| 313 | end | ||
| 314 | |||
| 295 | def effective_lang | 315 | def effective_lang |
| 296 | if translated_locales.empty? | 316 | if translated_locales.empty? |
| 297 | return 'de' | 317 | return 'de' |
| @@ -333,6 +353,14 @@ class Page < ApplicationRecord | |||
| 333 | end | 353 | end |
| 334 | end | 354 | end |
| 335 | 355 | ||
| 356 | def page_slug_not_reserved | ||
| 357 | return unless slug == CccConventions::TRASH_SLUG | ||
| 358 | return if node&.trash_node? | ||
| 359 | return unless parent_node_id && Node.find_by(:id => parent_node_id)&.root? | ||
| 360 | |||
| 361 | errors.add(:slug, :reserved_for_trash) | ||
| 362 | end | ||
| 363 | |||
| 336 | # Installs (or re-installs) the trigger that keeps page_translations' | 364 | # Installs (or re-installs) the trigger that keeps page_translations' |
| 337 | # search_vector in sync. Idempotent, safe to call on every boot. | 365 | # search_vector in sync. Idempotent, safe to call on every boot. |
| 338 | # search_vector is populated by a raw Postgres trigger, not anything | 366 | # search_vector is populated by a raw Postgres trigger, not anything |
