skylinecms 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
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