summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/application_record.rb3
-rw-r--r--app/models/asset.rb43
-rw-r--r--app/models/concerns/file_attachment.rb139
-rw-r--r--app/models/event.rb14
-rw-r--r--app/models/locked_by_another_user.rb1
-rw-r--r--app/models/menu_item.rb10
-rw-r--r--app/models/node.rb110
-rw-r--r--app/models/occurrence.rb27
-rw-r--r--app/models/page.rb104
-rw-r--r--app/models/permission.rb6
-rw-r--r--app/models/related_asset.rb8
-rw-r--r--app/models/user.rb4
12 files changed, 313 insertions, 156 deletions
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
new file mode 100644
index 0000000..10a4cba
--- /dev/null
+++ b/app/models/application_record.rb
@@ -0,0 +1,3 @@
1class ApplicationRecord < ActiveRecord::Base
2 self.abstract_class = true
3end
diff --git a/app/models/asset.rb b/app/models/asset.rb
index 5bfea76..aca0ee8 100644
--- a/app/models/asset.rb
+++ b/app/models/asset.rb
@@ -1,39 +1,12 @@
1class Asset < ActiveRecord::Base 1class Asset < ApplicationRecord
2
3 include FileAttachment
2 4
3 has_many :related_assets, :dependent => :destroy 5 has_many :related_assets, :dependent => :destroy
4 has_many :pages, :through => :related_assets 6 has_many :pages, :through => :related_assets
5 7
6 has_attached_file( 8 scope :images, -> { where(:upload_content_type => ["image/gif", "image/jpeg", "image/png", "image/webp"]) }
7 :upload, 9 scope :documents, -> { where(:upload_content_type => ["application/pdf", "text/plain", "text/rtf"]) }
8 :styles => { 10 scope :audio, -> { where(:upload_content_type => ["audio/mpeg", "audio/x-m4a", "audio/wav", "audio/x-wav"]) }
9 :medium => "300x300", 11
10 :thumb => "100x100",
11 :headline => "460x250#"
12 }
13 )
14
15 named_scope :images, :conditions => {
16 :upload_content_type => [
17 "image/gif",
18 "image/jpeg",
19 "image/png"
20 ]
21 }
22
23 named_scope :documents, :conditions => {
24 :upload_content_type => [
25 "application/pdf",
26 "text/plain",
27 "text/rtf"
28 ]
29 }
30
31 named_scope :audio, :conditions => {
32 :upload_content_type => [
33 "audio/mpeg",
34 "audio/x-m4a",
35 "audio/wav",
36 "audio/x-wav"
37 ]
38 }
39end 12end
diff --git a/app/models/concerns/file_attachment.rb b/app/models/concerns/file_attachment.rb
new file mode 100644
index 0000000..0e99fa2
--- /dev/null
+++ b/app/models/concerns/file_attachment.rb
@@ -0,0 +1,139 @@
1# FileAttachment — minimal drop-in replacement for Paperclip's has_attached_file.
2#
3# Provides the same interface used throughout this codebase:
4# asset.upload.url -> "/system/uploads/:id/original/:filename"
5# asset.upload.url(:thumb) -> "/system/uploads/:id/thumb/:filename"
6# asset.upload.content_type -> string
7# asset.upload.size -> integer (bytes)
8#
9# Files are stored at:
10# Rails.root/public/system/uploads/:id/:style/:filename
11#
12# Image variants are generated via ImageMagick (convert) on upload.
13# Non-image files get only an original, no variants.
14#
15# To replace an asset: assign a new file to asset.upload= and save.
16# The filename is fixed on first upload and preserved on replacement,
17# keeping all public URLs stable.
18#
19# Future: if more sophisticated asset management is needed (versioning,
20# S3, on-demand resizing), replace this module and keep the interface.
21
22module FileAttachment
23 extend ActiveSupport::Concern
24
25 STYLES = {
26 medium: { geometry: "300x300>", format: nil },
27 thumb: { geometry: "100x100>", format: nil },
28 headline: { geometry: "460x250!", format: nil }
29 }.freeze
30
31 IMAGE_CONTENT_TYPES = %w[image/jpeg image/gif image/png image/webp].freeze
32 VECTOR_CONTENT_TYPES = %w[image/svg+xml].freeze
33 DISPLAYABLE_AS_IMAGE = IMAGE_CONTENT_TYPES + VECTOR_CONTENT_TYPES
34
35 included do
36 attr_reader :upload
37
38 after_initialize :build_upload_proxy
39 after_save :process_upload
40 before_destroy :delete_upload_files
41 end
42
43 def upload=(uploaded_file)
44 return if uploaded_file.blank?
45 @pending_upload = uploaded_file
46 # Populate the database columns immediately so validations can use them
47 self.upload_file_name = sanitize_filename(uploaded_file.original_filename)
48 self.upload_content_type = uploaded_file.content_type.to_s.split(';').first.strip
49 self.upload_file_size = uploaded_file.size
50 self.upload_updated_at = Time.current
51 build_upload_proxy
52 end
53
54 private
55
56 def build_upload_proxy
57 @upload = UploadProxy.new(self)
58 end
59
60 def process_upload
61 return unless @pending_upload
62 uploaded_file = @pending_upload
63 @pending_upload = nil
64
65 old_dir = Rails.root.join("public", "system", "uploads", id.to_s)
66 FileUtils.rm_rf(old_dir) if Dir.exist?(old_dir)
67
68 original_path = file_path(:original)
69 FileUtils.mkdir_p(File.dirname(original_path))
70 FileUtils.cp(uploaded_file.tempfile.path, original_path)
71
72 if IMAGE_CONTENT_TYPES.include?(upload_content_type)
73 generate_variants(original_path)
74 elsif VECTOR_CONTENT_TYPES.include?(upload_content_type)
75 generate_svg_variants(original_path)
76 end
77 end
78
79 def generate_variants(original_path)
80 STYLES.each do |style, options|
81 dest_path = file_path(style)
82 FileUtils.mkdir_p(File.dirname(dest_path))
83 system("magick", original_path, "-resize", options[:geometry], dest_path)
84 end
85 end
86
87 def generate_svg_variants(original_path)
88 STYLES.each do |style, _|
89 dest_path = file_path(style)
90 FileUtils.mkdir_p(File.dirname(dest_path))
91 FileUtils.cp(original_path, dest_path)
92 end
93 end
94
95 def delete_upload_files
96 dir = Rails.root.join("public", "system", "uploads", id.to_s)
97 FileUtils.rm_rf(dir) if Dir.exist?(dir)
98 end
99
100 def file_path(style)
101 Rails.root.join(
102 "public", "system", "uploads",
103 id.to_s, style.to_s, upload_file_name
104 ).to_s
105 end
106
107 def sanitize_filename(filename)
108 File.basename(filename).gsub(/[^\w\.\-]/, '_')
109 end
110
111 # Proxy object returned by asset.upload, providing the Paperclip-compatible
112 # interface used in views: .url, .url(:style), .content_type, .size
113 class UploadProxy
114 def initialize(record)
115 @record = record
116 end
117
118 def url(style = :original)
119 return "" if @record.upload_file_name.blank?
120 "/system/uploads/#{@record.id}/#{style}/#{@record.upload_file_name}"
121 end
122
123 def content_type
124 @record.upload_content_type.to_s
125 end
126
127 def size
128 @record.upload_file_size.to_i
129 end
130
131 def present?
132 @record.upload_file_name.present?
133 end
134
135 def blank?
136 !present?
137 end
138 end
139end
diff --git a/app/models/event.rb b/app/models/event.rb
index 60b8521..94a22e3 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -1,4 +1,4 @@
1class Event < ActiveRecord::Base 1class Event < ApplicationRecord
2 2
3 # Associations 3 # Associations
4 4
@@ -12,14 +12,12 @@ class Event < ActiveRecord::Base
12 # Instance Methods 12 # Instance Methods
13 13
14 def occurrences_in_range start_time, end_time 14 def occurrences_in_range start_time, end_time
15 self.occurrences.find( 15 self.occurrences.where(
16 :all, :conditions => [ 16 "start_time > ? AND end_time < ?",
17 "start_time > ? AND end_time < ?", 17 start_time, end_time
18 start_time, end_time
19 ]
20 ) 18 )
21 end 19 end
22 20
23 private 21 private
24 def generate_occurences 22 def generate_occurences
25 Occurrence.generate self 23 Occurrence.generate self
diff --git a/app/models/locked_by_another_user.rb b/app/models/locked_by_another_user.rb
new file mode 100644
index 0000000..6f9e272
--- /dev/null
+++ b/app/models/locked_by_another_user.rb
@@ -0,0 +1 @@
class LockedByAnotherUser < StandardError; end
diff --git a/app/models/menu_item.rb b/app/models/menu_item.rb
index 054e7ee..7769b7f 100644
--- a/app/models/menu_item.rb
+++ b/app/models/menu_item.rb
@@ -1,6 +1,6 @@
1class MenuItem < ActiveRecord::Base 1class MenuItem < ApplicationRecord
2 2
3 default_scope :conditions => {:type => "MenuItem"} 3 default_scope -> { where(:type => "MenuItem") }
4 4
5 translates :title 5 translates :title
6 6
@@ -24,5 +24,5 @@ end
24 24
25 25
26class FeaturedArticle < MenuItem 26class FeaturedArticle < MenuItem
27 default_scope :conditions => {:type => "FeaturedArticle"} 27 default_scope -> { where(:type => "FeaturedArticle") }
28end \ No newline at end of file 28end
diff --git a/app/models/node.rb b/app/models/node.rb
index 6c11fed..92ecc12 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -1,14 +1,14 @@
1class Node < ActiveRecord::Base 1class Node < ApplicationRecord
2 # Mixins and Plugins 2 # Mixins and Plugins
3 acts_as_nested_set 3 acts_as_nested_set
4 4
5 # Associations 5 # Associations
6 has_many :pages, :order => "revision ASC" 6 has_many :pages, -> { order("revision ASC") }, :dependent => :destroy
7 belongs_to :head, :class_name => "Page", :foreign_key => :head_id 7 belongs_to :head, :class_name => "Page", :foreign_key => :head_id, :dependent => :destroy, optional: true
8 belongs_to :draft, :class_name => "Page", :foreign_key => :draft_id 8 belongs_to :draft, :class_name => "Page", :foreign_key => :draft_id, :dependent => :destroy, optional: true
9 has_many :permissions 9 has_many :permissions, :dependent => :destroy
10 has_one :event 10 has_one :event, :dependent => :destroy
11 belongs_to :lock_owner, :class_name => "User", :foreign_key => :locking_user_id 11 belongs_to :lock_owner, :class_name => "User", :foreign_key => :locking_user_id, optional: true
12 12
13 # Callbacks 13 # Callbacks
14 after_create :initialize_empty_page 14 after_create :initialize_empty_page
@@ -16,20 +16,20 @@ class Node < ActiveRecord::Base
16 after_save :update_unique_names_of_children 16 after_save :update_unique_names_of_children
17 17
18 # Validations 18 # Validations
19 validates_length_of :slug, :within => 1..255, :unless => "parent_id.nil?" 19 validates_length_of :slug, :within => 1..255, :unless => -> { parent_id.nil? }
20 validates_presence_of :slug, :unless => "parent_id.nil?" 20 validates_presence_of :slug, :unless => -> { parent_id.nil? }
21 validates_uniqueness_of :slug, :scope => :parent_id, :unless => "parent_id.nil?" 21 validates_uniqueness_of :slug, :scope => :parent_id, :unless => -> { parent_id.nil? }
22 validates_presence_of :parent_id, :unless => "Node.root.nil?" 22 validates_presence_of :parent_id, :unless => -> { Node.root.nil? }
23 23
24 validate :borders # This should never ever happen. 24 validate :borders # This should never ever happen.
25 25
26 # Index for Fulltext Search 26 # Index for Fulltext Search
27 define_index do 27 # define_index do
28 indexes head.translations.title 28 # indexes head.translations.title
29 indexes slug 29 # indexes slug
30 indexes unique_name 30 # indexes unique_name
31 indexes head.translations.body 31 # indexes head.translations.body
32 end 32 # end
33 33
34 # Class methods 34 # Class methods
35 35
@@ -39,8 +39,8 @@ class Node < ActiveRecord::Base
39 # revision with -1. It raises an Argument error if the revision is not a 39 # revision with -1. It raises an Argument error if the revision is not a
40 # Fixnum 40 # Fixnum
41 def self.find_page path, revision = -1 41 def self.find_page path, revision = -1
42 unless revision.class == Fixnum 42 unless revision.is_a?(Integer)
43 raise ArgumentError, "revision must be a Fixnum" 43 raise ArgumentError, "revision must be a Integer"
44 end 44 end
45 45
46 node = Node.find_by_unique_name(path) 46 node = Node.find_by_unique_name(path)
@@ -60,6 +60,7 @@ class Node < ActiveRecord::Base
60 # Instance Methods 60 # Instance Methods
61 61
62 def find_or_create_draft current_user 62 def find_or_create_draft current_user
63 self.wipe_draft!
63 if draft && self.lock_owner == current_user 64 if draft && self.lock_owner == current_user
64 draft 65 draft
65 elsif draft && self.lock_owner.nil? 66 elsif draft && self.lock_owner.nil?
@@ -72,7 +73,7 @@ class Node < ActiveRecord::Base
72 raise( 73 raise(
73 LockedByAnotherUser, 74 LockedByAnotherUser,
74 "Page is locked by another user who is working on it! " \ 75 "Page is locked by another user who is working on it! " \
75 "Last modification: #{draft.updated_at.to_s(:db)}" 76 "Last modification: #{draft.updated_at.to_fs(:db)}"
76 ) 77 )
77 else 78 else
78 lock_for! current_user 79 lock_for! current_user
@@ -94,25 +95,55 @@ class Node < ActiveRecord::Base
94 end 95 end
95 96
96 def publish_draft! 97 def publish_draft!
98 # Return nil if nothing to publish and no staged changes
99 return nil unless self.draft || staged_slug || staged_parent_id
100
97 if self.draft 101 if self.draft
98 self.head = self.draft 102 self.head = self.draft
99 self.head.published_at ||= Time.now 103 self.head.published_at ||= Time.now
100 self.head.save! 104 self.head.save!
101
102 self.draft = nil 105 self.draft = nil
106 end
103 107
104 if staged_slug && (staged_slug != slug) 108 if staged_slug && (staged_slug != slug)
105 self.slug = staged_slug 109 self.slug = staged_slug
106 end 110 self.staged_slug = nil
111 end
107 112
108 if staged_parent_id && (staged_parent_id != parent_id) 113 if staged_parent_id && (staged_parent_id != parent_id)
109 self.parent_id = staged_parent_id 114 new_parent = Node.find(staged_parent_id)
115 self.staged_parent_id = nil
116 self.save!
117 self.move_to_child_of(new_parent)
118 else
119 unless self.save
120 raise ActiveRecord::RecordInvalid.new(self)
110 end 121 end
122 end
111 123
112 self.save! 124 self.reload
125 self.update_unique_name
126 self.send(:update_unique_names_of_children)
127 self.unlock!
128 self
129 end
130
131 # removes a draft and the lock if it is older than a day and still
132 # identical to head
133 def wipe_draft!
134 unless self.draft
113 self.unlock! 135 self.unlock!
114 self 136 return
115 end 137 end
138 return unless self.head
139 return unless self.draft.updated_at < 1.day.ago
140 return if self.head.has_changes_to? self.draft
141
142 self.draft.destroy
143 self.draft_id = nil
144 self.unlock!
145 self.save!
146 self.reload
116 end 147 end
117 148
118 def restore_revision! revision 149 def restore_revision! revision
@@ -126,7 +157,7 @@ class Node < ActiveRecord::Base
126 157
127 # returns an array with all parts of a unique_name rather than a string 158 # returns an array with all parts of a unique_name rather than a string
128 def unique_path 159 def unique_path
129 unique_name.split("/") rescue [unique_name] 160 unique_name.to_s.split("/")
130 end 161 end
131 162
132 # returns array with pages up to root excluding root 163 # returns array with pages up to root excluding root
@@ -180,6 +211,15 @@ class Node < ActiveRecord::Base
180 self.created_at < new_id_format_date ? unique_path : id 211 self.created_at < new_id_format_date ? unique_path : id
181 end 212 end
182 213
214 # Full-text search across all locale translations using PostgreSQL tsvector.
215 # Uses 'simple' dictionary (no stemming, no stopwords) so queries work
216 # across German and English content without language detection.
217 def self.search(term, _ = {})
218 joins(head: :translations)
219 .where("page_translations.search_vector @@ plainto_tsquery('simple', ?)", term)
220 .distinct
221 end
222
183 protected 223 protected
184 def lock_for! current_user 224 def lock_for! current_user
185 self.lock_owner = current_user 225 self.lock_owner = current_user
@@ -208,8 +248,12 @@ class Node < ActiveRecord::Base
208 # Hopefully until no childrens occur 248 # Hopefully until no childrens occur
209 def update_unique_names_of_children 249 def update_unique_names_of_children
210 unless root? 250 unless root?
211 self.descendants.each do |descendant| 251 # Use parent_id-based traversal instead of lft/rgt descendants
212 descendant.update_unique_name 252 # due to awesome_nested_set not refreshing parent lft/rgt in memory
253 Node.where(:parent_id => self.id).each do |child|
254 child.reload
255 child.update_unique_name
256 child.send(:update_unique_names_of_children)
213 end 257 end
214 end 258 end
215 end 259 end
@@ -220,7 +264,3 @@ class Node < ActiveRecord::Base
220 end 264 end
221 end 265 end
222end 266end
223
224class LockedByAnotherUser < StandardError; end
225
226
diff --git a/app/models/occurrence.rb b/app/models/occurrence.rb
index 591e1e0..3baf447 100644
--- a/app/models/occurrence.rb
+++ b/app/models/occurrence.rb
@@ -1,7 +1,7 @@
1# TODO Make a gem out of the c wrapper 1# TODO Make a gem out of the c wrapper
2require 'chaos_calendar' 2require 'chaos_calendar'
3 3
4class Occurrence < ActiveRecord::Base 4class Occurrence < ApplicationRecord
5 5
6 # Associations 6 # Associations
7 7
@@ -11,29 +11,22 @@ class Occurrence < ActiveRecord::Base
11 # Class Methods 11 # Class Methods
12 12
13 def self.find_in_range start_time, end_time 13 def self.find_in_range start_time, end_time
14 find( 14 includes(:node)
15 :all, 15 .where("start_time > ? AND end_time < ?", start_time, end_time)
16 :include => :node, 16 .order("start_time")
17 :conditions => [
18 "start_time > ? AND end_time < ?", start_time, end_time
19 ]
20 )
21 end 17 end
22 18
23 def self.find_next 19 def self.find_next
24 find( 20 includes(:node)
25 :all, 21 .where("start_time > ?", Time.now)
26 :limit => 1, 22 .limit(1)
27 :include => :node,
28 :conditions => ["start_time > ?", Time.now]
29 )
30 end 23 end
31 24
32 # Deletes all Occurrences which belong to the given event. Afterwards a few 25 # Deletes all Occurrences which belong to the given event. Afterwards a few
33 # variables are set to save repetitive queries. The occurrences of the given 26 # variables are set to save repetitive queries. The occurrences of the given
34 # event are then calculated and created. 27 # event are then calculated and created.
35 def self.generate event 28 def self.generate event
36 self.delete_all(:event_id => event.id) 29 self.where(:event_id => event.id).delete_all
37 30
38 node = event.node 31 node = event.node
39 duration = (event.end_time - event.start_time) 32 duration = (event.end_time - event.start_time)
diff --git a/app/models/page.rb b/app/models/page.rb
index c5da386..e6baf20 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -1,25 +1,9 @@
1require 'xml' 1require 'xml'
2 2
3class Page < ActiveRecord::Base 3class 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 = File.join(RAILS_ROOT, 'app', 'views', PUBLIC_TEMPLATE_PATH) 6 FULL_PUBLIC_TEMPLATE_PATH = Rails.root.join('app', 'views', PUBLIC_TEMPLATE_PATH)
7
8 # named scopes
9
10 named_scope(
11 :drafts,
12 :joins => :node,
13 :include => [:translations],
14 :conditions => ["nodes.draft_id = pages.id"]
15 )
16
17 named_scope(
18 :heads,
19 :joins => :node,
20 :include => [:translations],
21 :conditions => ["nodes.head_id = pages.id"]
22 )
23 7
24 # Mixins and Plugins 8 # Mixins and Plugins
25 acts_as_taggable 9 acts_as_taggable
@@ -28,20 +12,21 @@ class Page < ActiveRecord::Base
28 translates :title, :abstract, :body # Globalize2 12 translates :title, :abstract, :body # Globalize2
29 13
30 # Associations 14 # Associations
31 belongs_to :node 15 belongs_to :node, optional: true
32 belongs_to :user 16 belongs_to :user, optional: true
33 belongs_to :editor, :class_name => "User" 17 belongs_to :editor, :class_name => "User", optional: true
34 has_many :related_assets 18 has_many :related_assets
35 has_many :assets, :through => :related_assets, :order => "position ASC" 19 has_many :assets, -> { order("position ASC") }, :through => :related_assets
20
21 # Named scopes
22 scope :drafts, -> { joins(:node).includes(:translations).where("nodes.draft_id = pages.id") }
23 scope :heads, -> { joins(:node).includes(:translations).where("nodes.head_id = pages.id") }
36 24
37 # Filter 25 # Filter
38 before_create :set_page_title 26 before_create :set_page_title
39 before_create :set_template 27 before_create :set_template
40 before_save :rewrite_links_in_body 28 before_save :rewrite_links_in_body
41 29
42 # Security
43 attr_accessible :title, :abstract, :body, :template_name, :published_at, :user_id
44
45 # Class Methods 30 # Class Methods
46 31
47 # This method is most likely called from the ContentHelper.render_collection 32 # This method is most likely called from the ContentHelper.render_collection
@@ -51,8 +36,8 @@ class Page < ActiveRecord::Base
51 # partially or entirely overwritten by the options hash. Afterwards the merged 36 # partially or entirely overwritten by the options hash. Afterwards the merged
52 # parameters are used to query the DB for Pages matching these parameters. 37 # parameters are used to query the DB for Pages matching these parameters.
53 # The aggregation only takes published pages into account. 38 # The aggregation only takes published pages into account.
54 def self.aggregate options, page=1
55 39
40 def self.aggregate options, page=1
56 defaults = { 41 defaults = {
57 :tags => "", 42 :tags => "",
58 :limit => 25, 43 :limit => 25,
@@ -62,15 +47,24 @@ class Page < ActiveRecord::Base
62 47
63 options = defaults.merge options 48 options = defaults.merge options
64 49
65 Page.heads.paginate( 50 scope = Page.heads
66 find_options_for_find_tagged_with( 51 unless options[:tags].blank?
67 options[:tags].gsub(/\s/, ","), :match_all => true 52 tag_names = options[:tags].gsub(/\s/, ",").split(",").map(&:strip).map(&:downcase).uniq.reject(&:blank?)
68 ).merge( 53
69 :page => page, 54 unless tag_names.empty?
70 :per_page => options[:limit], 55 scope = scope
71 :order => "#{options[:order_by]} #{options[:order_direction]}" 56 .joins("JOIN taggings ON taggings.taggable_id = pages.id
72 ) 57 AND taggings.taggable_type = 'Page'
73 ) 58 AND taggings.context = 'tags'")
59 .joins("JOIN tags ON tags.id = taggings.tag_id")
60 .where("LOWER(tags.name) IN (?)", tag_names)
61 .group("pages.id")
62 .having("COUNT(DISTINCT tags.id) = ?", tag_names.length)
63 end
64 end
65
66 scope.order("#{options[:order_by]} #{options[:order_direction]}")
67 .paginate(:page => page, :per_page => options[:limit])
74 end 68 end
75 69
76 def self.custom_templates 70 def self.custom_templates
@@ -89,11 +83,24 @@ class Page < ActiveRecord::Base
89 # outdated_translations? for more information. 83 # outdated_translations? for more information.
90 # Takes :locale => <locale> and :delta_time => 12.hours as options 84 # Takes :locale => <locale> and :delta_time => 12.hours as options
91 def self.find_with_outdated_translations options = {} 85 def self.find_with_outdated_translations options = {}
92 Page.all(:include => :translations).select do |page| 86 Page.includes(:translations).select do |page|
93 page.outdated_translations? options 87 page.outdated_translations? options
94 end 88 end
95 end 89 end
96 90
91 # Is used to compare a node's head with the node's draft
92
93 def has_changes_to? draft
94 return true unless node == draft.node
95 return true unless assets == draft.assets
96 return true unless tag_list == draft.tag_list
97 return true unless template_name == draft.template_name
98 return true unless translated_locales.sort_by(&:to_s) == draft.translated_locales.sort_by(&:to_s)
99 changed = false
100 translated_locales.each { |locale| I18n.with_locale(locale) { changed = true unless title == draft.title && abstract == draft.abstract && body == draft.body } }
101 return changed
102 end
103
97 # Instance Methods 104 # Instance Methods
98 105
99 def public_template_path 106 def public_template_path
@@ -105,7 +112,7 @@ class Page < ActiveRecord::Base
105 end 112 end
106 113
107 def template_exists? 114 def template_exists?
108 File.exists? "#{full_public_template_path}.html.erb" 115 File.exist? "#{full_public_template_path}.html.erb"
109 end 116 end
110 117
111 def valid_template 118 def valid_template
@@ -136,7 +143,7 @@ class Page < ActiveRecord::Base
136 143
137 # Clone translated attributes 144 # Clone translated attributes
138 page.translations.each do |translation| 145 page.translations.each do |translation|
139 self.translations.create!(translation.attributes) 146 self.translations.create!(translation.attributes.except("id", "page_id", "created_at", "updated_at"))
140 end 147 end
141 148
142 # Clone asset references 149 # Clone asset references
@@ -149,6 +156,16 @@ class Page < ActiveRecord::Base
149 published_at.nil? ? true : published_at < Time.now 156 published_at.nil? ? true : published_at < Time.now
150 end 157 end
151 158
159 def effective_lang
160 if translated_locales.empty?
161 return 'de'
162 elsif translated_locales.include?(I18n.locale)
163 return I18n.locale
164 else
165 return translated_locales.first
166 end
167 end
168
152 # Returns true if a page has translations where one of them is significantly 169 # Returns true if a page has translations where one of them is significantly
153 # older than the other. 170 # older than the other.
154 # Takes the I18n.default locale and a second :locale to test if the 171 # Takes the I18n.default locale and a second :locale to test if the
@@ -165,8 +182,8 @@ class Page < ActiveRecord::Base
165 182
166 translations = self.translations 183 translations = self.translations
167 184
168 default = *(translations.select {|x| x.locale == I18n.default_locale}) 185 default = translations.find {|x| x.locale.to_s == I18n.default_locale.to_s }
169 custom = *(translations.select {|x| x.locale == options[:locale]}) 186 custom = translations.find {|x| x.locale.to_s == options[:locale].to_s }
170 187
171 if translations.size > 1 && default && custom 188 if translations.size > 1 && default && custom
172 difference = (default.updated_at - custom.updated_at).to_i.abs 189 difference = (default.updated_at - custom.updated_at).to_i.abs
@@ -215,11 +232,6 @@ class Page < ActiveRecord::Base
215 links = links.reject { |l| l[:href] =~ /system\/uploads/ } 232 links = links.reject { |l| l[:href] =~ /system\/uploads/ }
216 locales = I18n.available_locales.reject {|l| l == :root} 233 locales = I18n.available_locales.reject {|l| l == :root}
217 234
218 if xml_doc.find("//p/aggregate")[0]
219 aggregate_tags = xml_doc.find("//aggregate")
220 aggregate_tags[0].parent.replace_with aggregate_tags[0]
221 end
222
223 links.each do |link| 235 links.each do |link|
224 unless locales.include? link[:href].slice(1,2).to_sym 236 unless locales.include? link[:href].slice(1,2).to_sym
225 unless link[:href] =~ /sytem\/uploads/ 237 unless link[:href] =~ /sytem\/uploads/
@@ -238,4 +250,4 @@ class Page < ActiveRecord::Base
238 end 250 end
239 end 251 end
240 252
241end \ No newline at end of file 253end
diff --git a/app/models/permission.rb b/app/models/permission.rb
index 438538e..1383a4b 100644
--- a/app/models/permission.rb
+++ b/app/models/permission.rb
@@ -1,4 +1,4 @@
1class Permission < ActiveRecord::Base 1class Permission < ApplicationRecord
2 # Validations 2 # Validations
3 validates_presence_of :user_id, :node_id, :granted 3 validates_presence_of :user_id, :node_id, :granted
4 validates_inclusion_of :granted, :in => [true, false] 4 validates_inclusion_of :granted, :in => [true, false]
@@ -8,6 +8,6 @@ class Permission < ActiveRecord::Base
8 belongs_to :node 8 belongs_to :node
9 9
10 # Named scopes 10 # Named scopes
11 named_scope :for_node, lambda { |node| { :conditions => ['node_id = ?', (node.is_a? Node ? node.id : node)] } } 11 scope :for_node, ->(node) { where('node_id = ?', (node.is_a?(Node) ? node.id : node)) }
12 named_scope :for_user, lambda { |user| { :conditions => ['user_id = ?', (user.is_a? User ? user.id : user)] } } 12 scope :for_user, ->(user) { where('user_id = ?', (user.is_a?(User) ? user.id : user)) }
13end 13end
diff --git a/app/models/related_asset.rb b/app/models/related_asset.rb
index af09420..8f16460 100644
--- a/app/models/related_asset.rb
+++ b/app/models/related_asset.rb
@@ -1,8 +1,8 @@
1class RelatedAsset < ActiveRecord::Base 1class RelatedAsset < ApplicationRecord
2 belongs_to :page 2 belongs_to :page
3 belongs_to :asset 3 belongs_to :asset
4 4
5 acts_as_list :scope => :page_id 5 acts_as_list :scope => :page_id
6 6
7 default_scope :order => "position ASC" 7 default_scope -> { order("position ASC") }
8end \ No newline at end of file 8end
diff --git a/app/models/user.rb b/app/models/user.rb
index ce5503f..92ac33a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,6 +1,6 @@
1require 'digest/sha1' 1require 'digest/sha1'
2 2
3class User < ActiveRecord::Base 3class User < ApplicationRecord
4 # Mixins and Plugins 4 # Mixins and Plugins
5 include Authentication 5 include Authentication
6 include Authentication::ByPassword 6 include Authentication::ByPassword
@@ -21,8 +21,6 @@ class User < ActiveRecord::Base
21 validates_format_of :email, :with => Authentication.email_regex, 21 validates_format_of :email, :with => Authentication.email_regex,
22 :message => Authentication.bad_email_message 22 :message => Authentication.bad_email_message
23 23
24 attr_accessible :login, :email, :password, :password_confirmation, :admin
25
26 # Authenticates a user by their login name and unencrypted password. Returns the user or nil. 24 # Authenticates a user by their login name and unencrypted password. Returns the user or nil.
27 def self.authenticate(login, password) 25 def self.authenticate(login, password)
28 return nil if login.blank? || password.blank? 26 return nil if login.blank? || password.blank?