skylinecms 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. data/.yardopts +1 -1
  2. data/CHANGELOG +50 -0
  3. data/README.md +11 -3
  4. data/app/controllers/skyline/application_controller.rb +6 -5
  5. data/app/controllers/skyline/articles_controller.rb +4 -4
  6. data/app/controllers/skyline/content_controller.rb +2 -4
  7. data/app/controllers/skyline/media/data_controller.rb +1 -0
  8. data/app/controllers/skyline/site/media_files_data_controller.rb +1 -0
  9. data/app/controllers/skyline/user_preferences_controller.rb +1 -1
  10. data/app/controllers/skyline/users_controller.rb +5 -1
  11. data/app/controllers/skyline/variant_current_editor_controller.rb +6 -1
  12. data/app/controllers/skyline/variants_controller.rb +2 -2
  13. data/app/helpers/skyline/content_helper.rb +1 -1
  14. data/app/helpers/skyline/editors/editor.rb +6 -6
  15. data/app/helpers/skyline/editors/joinable_list.rb +1 -1
  16. data/app/helpers/skyline/form_helper.rb +1 -0
  17. data/app/helpers/skyline/menu_helper.rb +0 -8
  18. data/app/helpers/skyline/presenters/presenter.rb +2 -2
  19. data/app/middleware/skyline/sprockets_middleware.rb +3 -8
  20. data/app/models/skyline/article.rb +29 -25
  21. data/app/models/skyline/article_version.rb +20 -20
  22. data/app/models/skyline/associated_tag.rb +1 -1
  23. data/app/models/skyline/grant.rb +1 -1
  24. data/app/models/skyline/link_section_link.rb +1 -1
  25. data/app/models/skyline/media_cache.rb +1 -1
  26. data/app/models/skyline/media_dir.rb +0 -2
  27. data/app/models/skyline/media_node.rb +1 -1
  28. data/app/models/skyline/object_ref.rb +3 -3
  29. data/app/models/skyline/page.rb +1 -3
  30. data/app/models/skyline/publication.rb +3 -1
  31. data/app/models/skyline/ref_object.rb +1 -1
  32. data/app/models/skyline/referable_uri.rb +1 -1
  33. data/app/models/skyline/right.rb +1 -1
  34. data/app/models/skyline/role.rb +1 -1
  35. data/app/models/skyline/section.rb +9 -9
  36. data/app/models/skyline/sections/content_collection_section.rb +3 -3
  37. data/app/models/skyline/sections/link_section.rb +3 -3
  38. data/app/models/skyline/sections/media_section.rb +2 -0
  39. data/app/models/skyline/sections/rss_section.rb +11 -12
  40. data/app/models/skyline/tag.rb +1 -1
  41. data/app/models/skyline/user.rb +213 -202
  42. data/app/models/skyline/user_preference.rb +2 -2
  43. data/app/models/skyline/variant.rb +18 -14
  44. data/app/observers/skyline/version_stamper.rb +1 -1
  45. data/app/templates/skyline/page/default/index.html.erb +1 -1
  46. data/app/views/skyline/articles/_security.html.erb +1 -1
  47. data/app/views/skyline/articles/edit.html.erb +1 -1
  48. data/app/views/skyline/content/{_add.rhtml → _add.html.erb} +0 -0
  49. data/app/views/skyline/content/{_filter.rhtml → _filter.html.erb} +0 -0
  50. data/app/views/skyline/content/{create.rhtml → create.html.erb} +0 -0
  51. data/app/views/skyline/content/{edit.rhtml → edit.html.erb} +0 -0
  52. data/app/views/skyline/content/{error.rhtml → error.html.erb} +0 -0
  53. data/app/views/skyline/content/{list.rhtml → list.html.erb} +3 -3
  54. data/app/views/skyline/content/{show.rhtml → show.html.erb} +0 -0
  55. data/app/views/skyline/content_sections/_tags.html.erb +1 -1
  56. data/app/views/skyline/shared/_header_area.html.erb +1 -1
  57. data/app/views/skyline/users/_new.html.erb +1 -1
  58. data/config/initializers/middlewares.rb +5 -1
  59. data/config/initializers/mime_types.rb +0 -6
  60. data/config/initializers/observers.rb +1 -1
  61. data/config/locales/en-US.yml +1 -1
  62. data/db/fixtures/roles_and_rights.rb +5 -5
  63. data/db/fixtures/roots.rb +1 -1
  64. data/doc/concepts.md +37 -0
  65. data/lib/skyline/belongs_to_referable.rb +5 -5
  66. data/lib/skyline/content/class_meta_data.rb +4 -3
  67. data/lib/skyline/content/content.rb +28 -18
  68. data/lib/skyline/content/exportable.rb +3 -3
  69. data/lib/skyline/content/field_meta_data.rb +12 -11
  70. data/lib/skyline/content/implementation.rb +7 -6
  71. data/lib/skyline/content/meta_data/field.rb +1 -1
  72. data/lib/skyline/content/stack.rb +1 -1
  73. data/lib/skyline/content/versioning/version.rb +1 -1
  74. data/lib/skyline/engine.rb +1 -1
  75. data/lib/skyline/has_many_referables_in.rb +4 -4
  76. data/lib/skyline/plugins/manager.rb +4 -3
  77. data/lib/skyline/plugins/plugin.rb +9 -1
  78. data/lib/skyline/rendering/renderer.rb +12 -2
  79. data/lib/skyline/sections/interface.rb +1 -1
  80. data/lib/skyline/taggable.rb +4 -4
  81. data/lib/skyline/version.rb +2 -2
  82. data/public/skyline/javascripts/src/application.js +1 -1
  83. data/public/skyline/javascripts/src/application_preinit.js +1 -0
  84. data/public/skyline/javascripts/src/library_uploader.js +1 -1
  85. data/public/skyline/javascripts/src/skyline.editor/src/skyline.editor.js +18 -18
  86. data/public/skyline/javascripts/src/skyline.editor/src/tinymce_preinit.js +4 -0
  87. data/public/skyline/javascripts/src/skyline.js +20 -18
  88. data/public/skyline/javascripts/src/skyline/src/skyline.js +2 -2
  89. data/public/skyline/javascripts/src/skyline/src/uploader.js +1 -1
  90. data/skylinecms.gemspec +7 -5
  91. data/test/factories.rb +2 -0
  92. data/test/integration/media_browser_super_access_test.rb +3 -3
  93. data/test/integration/media_browser_user_access_test.rb +2 -2
  94. data/test/integration/user_preferences_test.rb +5 -5
  95. data/test/test_helper.rb +1 -0
  96. data/test/unit/article_test.rb +1 -1
  97. data/test/unit/locales_test.rb +13 -3
  98. data/test/unit/test_content_object_test.rb +1 -1
  99. data/test/unit/test_section_test.rb +3 -2
  100. data/test/unit/user_preference_test.rb +2 -2
  101. data/vendor/digitpaint/nested_attributes_positioning.rb +5 -5
  102. metadata +66 -40
@@ -1,6 +1,6 @@
1
1
  # @private
2
2
  class Skyline::ArticleVersion < ActiveRecord::Base
3
- set_table_name :skyline_article_versions
3
+ self.table_name = "skyline_article_versions"
4
4
 
5
5
  belongs_to :article, :class_name => "Skyline::Article", :foreign_key => "article_id"
6
6
  belongs_to :data, :polymorphic => true, :dependent => :destroy
@@ -15,41 +15,41 @@ class Skyline::ArticleVersion < ActiveRecord::Base
15
15
 
16
16
  accepts_nested_attributes_for :data
17
17
 
18
- def build_data(data_attributes)
19
- params = data_attributes.dup
20
- raise ArgumentError, "Missing class parameter when building data" unless params["class"]
21
- klass = params.delete("class")
22
- self.data = klass.constantize.new(params)
18
+ def build_data(*params, &block)
19
+ attrs = params.first.dup
20
+ raise ArgumentError, "Missing class parameter when building data" unless attrs["class"]
21
+ klass = attrs.delete("class")
22
+ self.data = klass.constantize.new(attrs)
23
23
  end
24
24
 
25
25
  def to_text
26
26
  self.sections.collect{|s| s.to_text}.join(" ").squeeze(" ")
27
27
  end
28
28
 
29
- def clone
30
- super.tap do |clone|
31
- clone.created_at = nil
32
- clone.updated_at = nil
33
- clone.sections = self.sections.collect{|section| section.clone}
34
- clone.data = self.data.clone
29
+ def dup
30
+ super.tap do |dup|
31
+ dup.created_at = nil
32
+ dup.updated_at = nil
33
+ dup.sections = self.sections.collect{|section| section.dup}
34
+ dup.data = self.data.dup
35
35
  end
36
36
  end
37
37
 
38
38
  # Clones this object and makes it have another class
39
- def clone_to_class(klass_or_proxy)
40
- clone = klass_or_proxy.build
39
+ def dup_to_class(klass_or_proxy)
40
+ dup = klass_or_proxy.build
41
41
 
42
42
  attrs = clone_attributes(:read_attribute_before_type_cast)
43
43
  attrs.delete(self.class.primary_key)
44
44
  attrs.delete("created_at")
45
45
  attrs.delete("updated_at")
46
- attrs["type"] = clone.type
47
- clone.send :instance_variable_set, '@attributes', attrs
46
+ attrs["type"] = dup.type
47
+ dup.send :instance_variable_set, '@attributes', attrs
48
48
 
49
- clone.variant = self if clone.respond_to?(:variant) # only call clone.variant= for publications
50
- clone.sections = self.sections.collect{|section| section.clone}
51
- clone.data = self.data.clone
52
- clone
49
+ dup.variant = self if dup.respond_to?(:variant) # only call clone.variant= for publications
50
+ dup.sections = self.sections.collect{|section| section.dup}
51
+ dup.data = self.data.dup
52
+ dup
53
53
  end
54
54
 
55
55
  # Custom data method that ensures that you always have
@@ -1,6 +1,6 @@
1
1
  # @private
2
2
  class Skyline::AssociatedTag < ActiveRecord::Base
3
- set_table_name :skyline_associated_tags
3
+ self.table_name = "skyline_associated_tags"
4
4
 
5
5
  belongs_to :taggable, :polymorphic => true
6
6
  belongs_to :tag
@@ -1,6 +1,6 @@
1
1
  # @private
2
2
  class Skyline::Grant < ActiveRecord::Base
3
- set_table_name :skyline_grants
3
+ self.table_name = "skyline_grants"
4
4
 
5
5
  belongs_to :user, :class_name => "Skyline::User"
6
6
  belongs_to :role, :class_name => "Skyline::Role"
@@ -3,7 +3,7 @@ class Skyline::LinkSectionLink < ActiveRecord::Base
3
3
  include Skyline::BelongsToReferable
4
4
  include Skyline::Positionable
5
5
 
6
- set_table_name :skyline_link_section_links
6
+ self.table_name = "skyline_link_section_links"
7
7
 
8
8
  self.positionable_scope = :link_section_id
9
9
 
@@ -1,6 +1,6 @@
1
1
  # @private
2
2
  class Skyline::MediaCache < ActiveRecord::Base
3
- set_table_name :skyline_media_cache
3
+ self.table_name = "skyline_media_cache"
4
4
 
5
5
  # Cache path must be a Pathname object
6
6
  cattr_accessor :cache_path
@@ -1,6 +1,5 @@
1
1
  # @private
2
2
  class Skyline::MediaDir < Skyline::MediaNode
3
- extend ActiveSupport::Memoizable
4
3
  include UniqueIdentifiers
5
4
 
6
5
  has_many :files, :foreign_key => "parent_id", :class_name => "Skyline::MediaFile"
@@ -14,7 +13,6 @@ class Skyline::MediaDir < Skyline::MediaNode
14
13
  validate :only_one_root
15
14
 
16
15
  class << self
17
- extend ActiveSupport::Memoizable
18
16
  # returns an Array of hashes
19
17
  #
20
18
  # ==== Returns
@@ -1,6 +1,6 @@
1
1
  # @private
2
2
  class Skyline::MediaNode < ActiveRecord::Base
3
- set_table_name :skyline_media_nodes
3
+ self.table_name = "skyline_media_nodes"
4
4
 
5
5
  belongs_to :directory, :foreign_key => "parent_id", :class_name => "Skyline::MediaDir"
6
6
 
@@ -35,9 +35,9 @@ class Skyline::ObjectRef < Skyline::RefObject
35
35
  self.referable_type == "Skyline::MediaFile"
36
36
  end
37
37
 
38
- def clone
39
- super.tap do |clone|
40
- clone.referable = self.referable.clone if self.referable.kind_of?(Skyline::ReferableUri)
38
+ def dup
39
+ super.tap do |dup|
40
+ dup.referable = self.referable.dup if self.referable.kind_of?(Skyline::ReferableUri)
41
41
  end
42
42
  end
43
43
 
@@ -54,7 +54,6 @@ class Skyline::Page < Skyline::Article
54
54
  default_scope :order => "position"
55
55
 
56
56
  class << self
57
- extend ActiveSupport::Memoizable
58
57
 
59
58
  def right_prefix
60
59
  "page"
@@ -158,9 +157,8 @@ class Skyline::Page < Skyline::Article
158
157
  end
159
158
 
160
159
  def nesting
161
- self.root? ? [self] : self.parent.nesting + [self]
160
+ @nesting ||= self.root? ? [self] : self.parent.nesting + [self]
162
161
  end
163
- memoize :nesting
164
162
 
165
163
  def parents
166
164
  self.nesting.dup[0..-2]
@@ -16,9 +16,11 @@ class Skyline::Publication < Skyline::ArticleVersion
16
16
  raise ArgumentError, "variant_attributes must be an Hash" unless variant_attributes.kind_of?(Hash)
17
17
  raise ArgumentError, "variant_attributes['name'] expected" unless variant_attributes.include?('name')
18
18
 
19
- variant = self.clone_to_class(self.article.variants)
19
+ variant = self.dup_to_class(self.article.variants)
20
20
  variant.attributes = variant_attributes
21
21
  variant.variant_id = nil
22
+ variant.created_at = Time.now
23
+ variant.updated_at = Time.now
22
24
  variant.save
23
25
  variant
24
26
  end
@@ -2,7 +2,7 @@
2
2
  class Skyline::RefObject < ActiveRecord::Base
3
3
  belongs_to :referable, :polymorphic => true, :autosave => true
4
4
 
5
- set_table_name :skyline_ref_objects
5
+ self.table_name = "skyline_ref_objects"
6
6
 
7
7
  serialize :options
8
8
 
@@ -1,6 +1,6 @@
1
1
  # @private
2
2
  class Skyline::ReferableUri < ActiveRecord::Base
3
- set_table_name :skyline_referable_uris
3
+ self.table_name = "skyline_referable_uris"
4
4
 
5
5
  def url
6
6
  self.uri
@@ -1,6 +1,6 @@
1
1
  # @private
2
2
  class Skyline::Right < ActiveRecord::Base
3
- set_table_name :skyline_rights
3
+ self.table_name = "skyline_rights"
4
4
 
5
5
  has_and_belongs_to_many :roles, :class_name => "Skyline::Role", :join_table => "skyline_rights_skyline_roles"
6
6
  end
@@ -1,6 +1,6 @@
1
1
  # @private
2
2
  class Skyline::Role < ActiveRecord::Base
3
- set_table_name :skyline_roles
3
+ self.table_name = "skyline_roles"
4
4
 
5
5
  has_many :grants, :class_name => "Skyline::Grant"
6
6
  has_many :users, :class_name => "Skyline::User", :through => :grants
@@ -1,6 +1,6 @@
1
1
  # @private
2
2
  class Skyline::Section < ActiveRecord::Base
3
- set_table_name :skyline_sections
3
+ self.table_name = "skyline_sections"
4
4
 
5
5
  belongs_to :variant, :class_name => "Skyline::Variant"
6
6
  belongs_to :article_version, :class_name => "Skyline::ArticleVersion"
@@ -12,16 +12,16 @@ class Skyline::Section < ActiveRecord::Base
12
12
 
13
13
  default_scope :order => "position ASC"
14
14
 
15
- def build_sectionable(sectionable_attributes)
16
- params = sectionable_attributes.dup
17
- raise ArgumentError, "Missing class parameter when building sectionable" unless params["class"]
18
- klass = params.delete("class")
19
- self.sectionable = klass.constantize.new(params)
15
+ def build_sectionable(*params, &block)
16
+ attrs = params.first.dup
17
+ raise ArgumentError, "Missing class parameter when building sectionable" unless attrs["class"]
18
+ klass = attrs.delete("class")
19
+ self.sectionable = klass.constantize.new(attrs)
20
20
  end
21
21
 
22
- def clone
23
- super.tap do |clone|
24
- clone.sectionable = self.sectionable.clone
22
+ def dup
23
+ super.tap do |dup|
24
+ dup.sectionable = dup.sectionable.dup
25
25
  end
26
26
  end
27
27
 
@@ -31,9 +31,9 @@ class Skyline::Sections::ContentCollectionSection < ActiveRecord::Base
31
31
  self.content_class.published.with_tags(self.tags).scoped(:limit => self.number)
32
32
  end
33
33
 
34
- def clone
35
- super.tap do |clone|
36
- clone.associated_tags = self.associated_tags.collect{|associated_tag| associated_tag.clone}
34
+ def dup
35
+ super.tap do |dup|
36
+ dup.associated_tags = self.associated_tags.collect{|associated_tag| associated_tag.dup}
37
37
  end
38
38
  end
39
39
  end
@@ -9,9 +9,9 @@ class Skyline::Sections::LinkSection < ActiveRecord::Base
9
9
 
10
10
  accepts_nested_attributes_for :links, :allow_destroy => true
11
11
 
12
- def clone
13
- super.tap do |clone|
14
- clone.links = self.links.collect{|link| link.clone}
12
+ def dup
13
+ super.tap do |dup|
14
+ dup.links = self.links.collect{|link| link.dup}
15
15
  end
16
16
  end
17
17
 
@@ -27,6 +27,8 @@ class Skyline::Sections::MediaSection < ActiveRecord::Base
27
27
  width = self[:width].to_i
28
28
  height = self[:height].to_i
29
29
  if self.media.present?
30
+ width = media.width if self[:width].blank?
31
+ height = media.height if self[:height].blank?
30
32
  proportional = self.media.proportional_dimension(width,height)
31
33
  if proportional
32
34
  width,height = proportional
@@ -2,7 +2,6 @@ require 'fileutils'
2
2
 
3
3
  # @private
4
4
  class Skyline::Sections::RssSection < ActiveRecord::Base
5
- extend ActiveSupport::Memoizable
6
5
  extend UrlValidation
7
6
  include Skyline::Sections::Interface
8
7
 
@@ -31,15 +30,10 @@ class Skyline::Sections::RssSection < ActiveRecord::Base
31
30
  self.rss_feed ? self.rss_feed[:items] : []
32
31
  end
33
32
 
34
- protected
35
- def cache_file
36
- File.join(self.class.cache_path, "#{self.id}.yml")
37
- end
38
- memoize :cache_file
39
-
40
33
  def rss_feed
34
+ return @rss_feed unless @rss_feed === nil
41
35
  if File.exists?(self.cache_file) && File.mtime(self.cache_file) > Time.now - self.cache_timeout
42
- YAML.load(File.read(self.cache_file))
36
+ @rss_feed = YAML.load(File.read(self.cache_file))
43
37
  else
44
38
  if feed = fetch_feed
45
39
  results = RSS::Parser.parse(feed, false)
@@ -66,20 +60,25 @@ class Skyline::Sections::RssSection < ActiveRecord::Base
66
60
  } if i < self.show_count
67
61
  end
68
62
  cache(feed)
69
- feed
63
+ @rss_feed = feed
70
64
  elsif File.exists?(self.cache_file)
71
65
  self.reset_mtime
72
- YAML.load(File.read(self.cache_file))
66
+ @rss_feed = YAML.load(File.read(self.cache_file))
73
67
  else
74
- false
68
+ @rss_feed = false
75
69
  end
76
70
  end
71
+ @rss_feed
77
72
  # rescue
78
73
  # logger.error "[RssSection] Failed to parse feed!"
79
74
  # false
80
75
  end
81
- memoize :rss_feed
82
76
 
77
+ protected
78
+ def cache_file
79
+ @cache_file ||= File.join(self.class.cache_path, "#{self.id}.yml")
80
+ end
81
+
83
82
  def fetch_feed
84
83
  logger.debug "[RssSection] Fetching feed #{self.url}"
85
84
  feed = open(self.url).read
@@ -1,5 +1,5 @@
1
1
  class Skyline::Tag < ActiveRecord::Base
2
- set_table_name :skyline_tags
2
+ self.table_name = "skyline_tags"
3
3
 
4
4
  cattr_accessor :taggable_models
5
5
 
@@ -1,202 +1,213 @@
1
- class Skyline::User < ActiveRecord::Base
2
- include Skyline::Authentication::User
3
-
4
- set_table_name :skyline_users
5
- has_many :grants, :class_name => "Skyline::Grant", :dependent => :delete_all
6
- has_many :roles, :class_name => "Skyline::Role", :through => :grants
7
- has_many :rights, :class_name => "Skyline::Right",
8
- :finder_sql => proc{ "SELECT DISTINCT r.* FROM skyline_rights AS r, skyline_grants AS g JOIN skyline_rights_skyline_roles AS rr ON rr.role_id = g.role_id WHERE g.user_id = #{id} AND r.id = rr.right_id" }
9
-
10
- # This must be set to change the password (by the user himself)
11
- attr_accessor :current_password
12
-
13
- # This can be set by the admin to change the PW
14
- attr_reader :force_password
15
- attr_protected :force_password
16
-
17
- # This can be set by the admin to force checking of the current password etc
18
- attr_accessor :editing_myself
19
-
20
-
21
- # Validations
22
- validate :valid_current_password, :if => :editing_myself, :on => :update
23
-
24
- validates_presence_of :password, :if => :password_changed?
25
- validates_presence_of :password_confirmation, :message => :confirmation, :if => :password_changed?, :on => :update
26
- validates_confirmation_of :password, :message => :confirmation, :if => :password_changed?
27
-
28
- validates_confirmation_of :force_password, :message => :confirmation, :if => lambda{|user| user.force_password.present?}
29
-
30
- validates_presence_of :email
31
- validate :valid_email_address
32
- validates_uniqueness_of :email, :unless => :is_destroyed
33
-
34
- validate :grants_didnt_change, :if => :editing_myself
35
-
36
- before_validation :reset_password_on_empty_current_password, :on => :update
37
- before_save :set_forced_password,:encrypt_password
38
-
39
- default_scope :order => "email ASC"
40
-
41
- accepts_nested_attributes_for :grants
42
-
43
-
44
- class << self
45
-
46
- # Authenticates a user with email and password
47
- #
48
- # ==== Returns
49
- # User:: The user if authentication passed
50
- # --
51
- def authenticate(email,password)
52
- user = self.first(:conditions => {:email => email.to_s.downcase, :password => encrypt(password.to_s), :is_destroyed => false})
53
- user && user || false
54
- end
55
-
56
- def find_by_identification(identification)
57
- self.find_by_id(identification)
58
- end
59
-
60
- def encrypt(pw)
61
- Digest::SHA1.hexdigest(pw)
62
- end
63
-
64
- def extract_valid_email_address(email)
65
- if email.kind_of? Mail::Address
66
- return email.address
67
- else
68
- begin
69
- address = Mail::Address.new(email.to_s)
70
- rescue
71
- return false
72
- end
73
- end
74
- if address && address.respond_to?(:domain) && address.domain
75
- return address.address
76
- else
77
- return false
78
- end
79
- end
80
-
81
- def per_page; 30; end
82
-
83
- end
84
-
85
- def identification
86
- self.id
87
- end
88
-
89
- # Check if a user has a specific right
90
- #
91
- # ==== Parameters
92
- # right_or_class<String,Symbol,~right_prefix>::
93
- # Can be a string or a symbol to check a specific right, or you can pass
94
- # an object that responds to #right_prefix, right_prefix must return a string
95
- # suffix<String,Symbol>,::
96
- # A suffix to append to the right, mostly usefull in combination with an object
97
- #
98
- # ==== Returns
99
- #<Boolean>:: true if the right exists in one of the roles fo the user
100
- # --
101
- def allow?(right_or_class,suffix=nil)
102
- if right_or_class.respond_to?(:right_prefix)
103
- right_or_class = right_or_class.right_prefix
104
- end
105
-
106
- right = [right_or_class,suffix].compact.map(&:to_s).join("_")
107
-
108
- @rights ||= self.rights.map{|r| r.name }.uniq
109
- @rights.include?(right)
110
- end
111
-
112
- # Check if this users password matches.
113
- def correct_password?(password)
114
- if self.password_changed?
115
- self.password == password.to_s
116
- else
117
- self.password == self.class.encrypt(password.to_s)
118
- end
119
- end
120
-
121
- def current_password=(v)
122
- changed_attributes.delete("password") if v.blank?
123
- @current_password = v
124
- end
125
-
126
- # Generate a new password, set it and return it.
127
- #
128
- # ==== Returns
129
- # String:: The newly set password
130
- # --
131
- def generate_new_password!
132
- pw = SimplePassword.new(8).to_s
133
- self.force_password!(pw)
134
- end
135
-
136
- # Forcefully set a password without any validations
137
- # Directly saves the object.
138
- # --
139
- def force_password!(pw)
140
- self.update_attribute(:password, pw)
141
- pw
142
- end
143
-
144
- def force_password=(pw)
145
- @force_password = pw if pw.present?
146
- end
147
-
148
- # Display the name or e-mailaddress of the user for
149
- # display purposes.
150
- def display_name
151
- self.name.present? ? self.name : self.email
152
- end
153
-
154
- # Don't really destroy the object, just
155
- # set the is_destroyed? flag.
156
- def destroy_without_callbacks
157
- unless new_record?
158
- self.update_attributes(:is_destroyed => true)
159
- end
160
-
161
- freeze
162
- end
163
-
164
- def viewable_roles
165
- if self.system?
166
- Skyline::Role.all
167
- else
168
- Skyline::Role.all(:conditions => {:system => false})
169
- end
170
- end
171
-
172
-
173
-
174
- protected
175
-
176
- def set_forced_password
177
- self.password = @force_password if @force_password.present?
178
- end
179
-
180
- def encrypt_password
181
- return unless self.password_changed?
182
- self.password = self.class.encrypt(self.password.to_s)
183
- end
184
-
185
- def valid_current_password
186
- unless self.current_password && self.password_was == self.class.encrypt(self.current_password)
187
- self.errors.add(:current_password, :mismatch)
188
- end
189
- end
190
-
191
- def reset_password_on_empty_current_password
192
- changed_attributes.delete("password") if self.current_password.blank?
193
- end
194
-
195
- def valid_email_address
196
- return self.errors.add(:email,:invalid) if !Skyline::User.extract_valid_email_address(self.email)
197
- end
198
-
199
- def grants_didnt_change
200
- self.errors.add :grants, :changed if self.grants.detect{|g| g.changed?}
201
- end
202
- end
1
+ class Skyline::User < ActiveRecord::Base
2
+ include Skyline::Authentication::User
3
+
4
+ self.table_name = "skyline_users"
5
+ has_many :grants, :class_name => "Skyline::Grant", :dependent => :delete_all
6
+ has_many :roles, :class_name => "Skyline::Role", :through => :grants
7
+ has_many :rights, :class_name => "Skyline::Right",
8
+ :finder_sql => proc{ "SELECT DISTINCT r.* FROM skyline_rights AS r, skyline_grants AS g JOIN skyline_rights_skyline_roles AS rr ON rr.role_id = g.role_id WHERE g.user_id = #{id} AND r.id = rr.right_id" }
9
+
10
+ # This must be set to change the password (by the user himself)
11
+ attr_accessor :current_password
12
+
13
+ # This can be set by the admin to change the PW
14
+ attr_reader :force_password
15
+ attr_protected :force_password
16
+
17
+ # This can be set by the admin to force checking of the current password etc
18
+ attr_accessor :editing_myself
19
+
20
+ attr_accessor :skip_email_validation
21
+
22
+ # Validations
23
+ validate :valid_current_password, :if => :editing_myself, :on => :update
24
+
25
+ validates_presence_of :password, :if => :password_changed?
26
+ validates_presence_of :password_confirmation, :message => :confirmation, :if => :password_changed?, :on => :update
27
+ validates_confirmation_of :password, :message => :confirmation, :if => :password_changed?
28
+
29
+ validates_confirmation_of :force_password, :message => :confirmation, :if => lambda{|user| user.force_password.present?}
30
+
31
+ validates_presence_of :email
32
+ validate :valid_email_address
33
+ validates_uniqueness_of :email, :unless => :skip_email_validation
34
+
35
+ validate :grants_didnt_change, :if => :editing_myself
36
+
37
+ before_validation :reset_password_on_empty_current_password, :on => :update
38
+ before_save :set_forced_password,:encrypt_password
39
+
40
+ default_scope :order => "email ASC"
41
+
42
+ accepts_nested_attributes_for :grants
43
+
44
+
45
+ class << self
46
+
47
+ # Authenticates a user with email and password
48
+ #
49
+ # ==== Returns
50
+ # User:: The user if authentication passed
51
+ # --
52
+ def authenticate(email,password)
53
+ user = self.first(:conditions => {:email => email.to_s.downcase, :password => encrypt(password.to_s), :is_destroyed => false})
54
+ user && user || false
55
+ end
56
+
57
+ def find_by_identification(identification)
58
+ self.find_by_id(identification)
59
+ end
60
+
61
+ def encrypt(pw)
62
+ Digest::SHA1.hexdigest(pw)
63
+ end
64
+
65
+ def extract_valid_email_address(email)
66
+ if email.kind_of? Mail::Address
67
+ return email.address
68
+ else
69
+ begin
70
+ address = Mail::Address.new(email.to_s)
71
+ rescue
72
+ return false
73
+ end
74
+ end
75
+ if address && address.respond_to?(:domain) && address.domain
76
+ return address.address
77
+ else
78
+ return false
79
+ end
80
+ end
81
+
82
+ def per_page; 30; end
83
+
84
+ end
85
+
86
+ def identification
87
+ self.id
88
+ end
89
+
90
+ # Check if a user has a specific right
91
+ #
92
+ # ==== Parameters
93
+ # right_or_class<String,Symbol,~right_prefix>::
94
+ # Can be a string or a symbol to check a specific right, or you can pass
95
+ # an object that responds to #right_prefix, right_prefix must return a string
96
+ # suffix<String,Symbol>,::
97
+ # A suffix to append to the right, mostly usefull in combination with an object
98
+ #
99
+ # ==== Returns
100
+ #<Boolean>:: true if the right exists in one of the roles fo the user
101
+ # --
102
+ def allow?(right_or_class,suffix=nil)
103
+ if right_or_class.respond_to?(:right_prefix)
104
+ right_or_class = right_or_class.right_prefix
105
+ end
106
+
107
+ right = [right_or_class,suffix].compact.map(&:to_s).join("_")
108
+
109
+ @rights ||= self.rights.map{|r| r.name }.uniq
110
+ @rights.include?(right)
111
+ end
112
+
113
+ # Check if this users password matches.
114
+ def correct_password?(password)
115
+ if self.password_changed?
116
+ self.password == password.to_s
117
+ else
118
+ self.password == self.class.encrypt(password.to_s)
119
+ end
120
+ end
121
+
122
+ def current_password=(v)
123
+ changed_attributes.delete("password") if v.blank?
124
+ @current_password = v
125
+ end
126
+
127
+ # Generate a new password, set it and return it.
128
+ #
129
+ # ==== Returns
130
+ # String:: The newly set password
131
+ # --
132
+ def generate_new_password!
133
+ pw = SimplePassword.new(8).to_s
134
+ self.force_password!(pw)
135
+ end
136
+
137
+ # Forcefully set a password without any validations
138
+ # Directly saves the object.
139
+ # --
140
+ def force_password!(pw)
141
+ self.update_attribute(:password, pw)
142
+ pw
143
+ end
144
+
145
+ def force_password=(pw)
146
+ @force_password = pw if pw.present?
147
+ end
148
+
149
+ # Display the name or e-mailaddress of the user for
150
+ # display purposes.
151
+ def display_name
152
+ self.name.present? ? self.name : self.email
153
+ end
154
+
155
+ # Don't really destroy the object, just
156
+ # set the is_destroyed? flag.
157
+ def destroy
158
+ unless new_record?
159
+ self.update_attributes(:is_destroyed => true)
160
+ self.grants.collect {|g| g.destroy }
161
+ end
162
+
163
+ freeze
164
+ end
165
+
166
+ def viewable_roles
167
+ if self.system?
168
+ Skyline::Role.all
169
+ else
170
+ Skyline::Role.all(:conditions => {:system => false})
171
+ end
172
+ end
173
+
174
+ # Reactivate user with these attributes, if they are valid
175
+ def reactivate(attributes)
176
+ temp_user = Skyline::User.new(attributes)
177
+ temp_user.skip_email_validation = true
178
+ if temp_user.valid?
179
+ self.attributes = attributes
180
+ self.force_password! attributes[:password]
181
+ self.is_destroyed = false
182
+ end
183
+ end
184
+
185
+ protected
186
+
187
+ def set_forced_password
188
+ self.password = @force_password if @force_password.present?
189
+ end
190
+
191
+ def encrypt_password
192
+ return unless self.password_changed?
193
+ self.password = self.class.encrypt(self.password.to_s)
194
+ end
195
+
196
+ def valid_current_password
197
+ unless self.current_password && self.password_was == self.class.encrypt(self.current_password)
198
+ self.errors.add(:current_password, :mismatch)
199
+ end
200
+ end
201
+
202
+ def reset_password_on_empty_current_password
203
+ changed_attributes.delete("password") if self.current_password.blank?
204
+ end
205
+
206
+ def valid_email_address
207
+ return self.errors.add(:email,:invalid) if !Skyline::User.extract_valid_email_address(self.email)
208
+ end
209
+
210
+ def grants_didnt_change
211
+ self.errors.add :grants, :changed if self.grants.detect{|g| g.changed?}
212
+ end
213
+ end