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