Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 10
- Log:
Checking in HEAD from RForum's SVN of 22-Jul-2006, 8pm (revision 906).
- Author:
- adh
- Date:
- Sat Jul 22 20:02:44 +0100 2006
- Size:
- 6561 Bytes
1 | require 'digest/md5' |
2 | require_dependency 'user_permissions' |
3 | |
4 | class User < ActiveRecord::Base |
5 | has_many :post_votes, :foreign_key => 'voter_id' |
6 | has_many :topic_subscriptions, :dependent => true |
7 | has_many :posts |
8 | has_many :topic_reads, :dependent => true |
9 | serialize :additional_information, Hash |
10 | |
11 | def self.inheritance_column() 'role' end |
12 | |
13 | include ErrorRaising, RForum::Localization |
14 | |
15 | include UserPermissions |
16 | |
17 | attr_accessor :old_password, :new_password, :retyped_password |
18 | |
19 | # CLASS METHODS |
20 | |
21 | # Find a user by user name and password. |
22 | def self.find_by_login(name, password) |
23 | raise ArgumentError if name.nil? |
24 | raise ArgumentError if password.nil? |
25 | find_first ["name='%s' AND password='%s'", name, Digest::MD5.hexdigest(password)] |
26 | end |
27 | |
28 | def self.find_by_token(id, token) |
29 | raise ArgumentError if id.nil? |
30 | raise ArgumentError if token.nil? |
31 | user = find_first ["id='%s' AND security_token='%s'", id, token] |
32 | if user.nil? or user.token_expired? |
33 | return nil |
34 | else |
35 | return user |
36 | end |
37 | end |
38 | |
39 | # CALLBACKS |
40 | |
41 | def after_initialize |
42 | reset_password_fields |
43 | end |
44 | |
45 | def before_validation_on_create |
46 | %w(name email name firstname surname).each do |attr| |
47 | self[attr] = self[attr].to_s.strip.squeeze(' ').chomp |
48 | end |
49 | |
50 | self.surname.capitalize! |
51 | self.firstname.capitalize! |
52 | self.name.downcase! |
53 | self.email.downcase! |
54 | self.role = 'User' |
55 | @unencrypted_password = makepass |
56 | self.encrypt_password(@unencrypted_password) |
57 | end |
58 | |
59 | def validate_on_create |
60 | # Nickname cannot be changed, so we only need to validate it on create |
61 | unless self.name =~ /^[a-z0-9\-]{3,15}$/i |
62 | errors.add 'name', :user_name_invalid |
63 | end |
64 | # Already existing nick is not allowed |
65 | errors.add_on_duplicate 'name', :user_name_duplicate |
66 | end |
67 | |
68 | def validate |
69 | errors.add 'email', :user_email_invalid unless valid_email?(self.email) |
70 | errors.add 'firstname', :user_firstname_invalid unless self.firstname =~ /^.{2,20}$/i |
71 | errors.add 'surname', :user_surname_invalid unless self.surname =~ /^.{2,20}$/i |
72 | errors.add_on_duplicate 'email', :user_email_duplicate |
73 | |
74 | if (@new_password or self.password.nil?) and password.size < 3 |
75 | errors.add 'new_password', :user_password_invalid |
76 | end |
77 | |
78 | # nick cannot be changed |
79 | unless self.new_record? |
80 | old_record = User.find(self.id) |
81 | errors.add 'name', l(:user_cannot_change_nick) unless old_record.name == self.name |
82 | end |
83 | end |
84 | |
85 | # NORMAL METHODS |
86 | |
87 | def guest? |
88 | false |
89 | end |
90 | |
91 | # Generates a temporary security token that can be passed in a URL to |
92 | # authenticate this user without a password. Typical use - to put that |
93 | # URL in an email sent to the user who forgotten his password and |
94 | # needs to reset it. |
95 | # |
96 | # This method will return an already existing token, if it is not |
97 | # older than half the maximum token lifetime. This is to avoid |
98 | # situations where user somehow regenerates the token (by clicking on |
99 | # the same link twice, or by browser back button), and then tries to |
100 | # use the first token. I don't want to create a new table for |
101 | # temporary security tokens. |
102 | def generate_security_token |
103 | if self.security_token.nil? or self.token_expiry.nil? or |
104 | (Time.now.to_i + token_lifetime / 2) >= self.token_expiry.to_i |
105 | return new_security_token |
106 | else |
107 | return self.security_token |
108 | end |
109 | end |
110 | |
111 | def encrypt_password(new_password) |
112 | self['password'] = Digest::MD5.hexdigest(new_password) |
113 | end |
114 | |
115 | def guest_email |
116 | nil |
117 | end |
118 | |
119 | def guest_name |
120 | nil |
121 | end |
122 | |
123 | # Create a random password. |
124 | # TODO: rewrite |
125 | def makepass |
126 | chars = ("a".."z").to_a + (1..9).to_a |
127 | chars = chars.sort_by { rand } |
128 | s = chars[0..7].to_s |
129 | end |
130 | |
131 | # TODO test me |
132 | def last_read_time(topic) |
133 | t = TopicRead.find(:first, :conditions => "user_id = #{self.id} AND topic_id = #{topic.id}") |
134 | if t |
135 | t.updated_at |
136 | else |
137 | nil |
138 | end |
139 | end |
140 | |
141 | # Gets the time the topics were last read by this user |
142 | # TODO test me |
143 | def topic_read_times |
144 | topic_read_times_hash = Hash.new |
145 | reads = self.topic_reads.find_all.each { |read| |
146 | topic_read_times_hash[read.topic_id] = read.updated_at |
147 | } |
148 | topic_read_times_hash |
149 | end |
150 | |
151 | # Enter a new vote or update an old vote. |
152 | # TODO: test me |
153 | def vote_post(post, value) |
154 | raise ArgumentError unless post.is_a?(Post) |
155 | transaction do |
156 | vote = find_all_in_post_votes("post_id = #{post.id}").first || PostVote.new |
157 | vote.voter = self |
158 | vote.post = post |
159 | vote.value = value |
160 | vote.save |
161 | end |
162 | end |
163 | |
164 | # Update the last time a topic was read. |
165 | def update_read_time(topic) |
166 | topic_read = self.topic_reads.find_all("topic_id = #{topic.id}") |
167 | |
168 | if topic_read.empty? |
169 | # first time this topic is read by this user |
170 | topic_read = TopicRead.new('topic_id' => topic.id, 'user_id' => self.id) |
171 | else |
172 | topic_read = topic_read[0] |
173 | end |
174 | topic_read.save |
175 | return topic_read |
176 | end |
177 | |
178 | def reset_password_fields |
179 | @old_password = @new_password = @retyped_password = '' |
180 | end |
181 | |
182 | # Subscribe to a topic to get notifications on new posts. |
183 | def subscribe_topic(topic) |
184 | subscription = TopicSubscription.new |
185 | subscription.user = self |
186 | subscription.topic = topic |
187 | |
188 | if TopicSubscription.count("topic_id = #{topic.id} AND user_id = #{self.id}") > 0 |
189 | # subscription already exists |
190 | return false |
191 | else |
192 | subscription.save |
193 | return true |
194 | end |
195 | end |
196 | |
197 | def unsubscribe_topic(topic) |
198 | TopicSubscription.find_first("topic_id = #{topic.id} AND user_id = #{self.id}").destroy |
199 | end |
200 | |
201 | # Unencrypted password field is needed in the controller to send an email notification. |
202 | # It is populated on creation, and not written to the database, so only a freshly created |
203 | # user instance has it set. |
204 | def tell_and_forget_unencrypted_password |
205 | raise "Unencrypted password not available" if @unencrypted_password.nil? |
206 | p = @unencrypted_password |
207 | @unencrypted_password = nil |
208 | return p |
209 | end |
210 | |
211 | def admin? |
212 | false |
213 | end |
214 | |
215 | def token_expired? |
216 | self.security_token and self.token_expiry and (Time.now > self.token_expiry) |
217 | end |
218 | |
219 | def get_display_name |
220 | "#{self.firstname} #{self.surname}" |
221 | end |
222 | |
223 | private |
224 | def new_security_token |
225 | self['security_token'] = |
226 | Digest::MD5.hexdigest(self['password'] + (Time.now.to_i.to_s) + rand.to_s) |
227 | self.token_expiry = Time.at(Time.now.to_i + token_lifetime) |
228 | self.save |
229 | return self['security_token'] |
230 | end |
231 | |
232 | def token_lifetime |
233 | RForum::CONFIG[:security_token_life_hours] * 60 * 60 |
234 | end |
235 | |
236 | end |
237 |