File Coverage

blib/lib/Rubric/User.pm
Criterion Covered Total %
statement 99 99 100.0
branch 33 36 91.6
condition 21 21 100.0
subroutine 17 17 100.0
pod 10 10 100.0
total 180 183 98.3


line stmt bran cond sub pod time code
1 12     12   9013 use strict;
  12         21  
  12         330  
2 12     12   50 use warnings;
  12         18  
  12         493  
3             # ABSTRACT: a Rubric user
4              
5             #pod =head1 DESCRIPTION
6             #pod
7             #pod This class provides an interface to Rubric users. It inherits from
8             #pod Rubric::DBI, which is a Class::DBI class.
9             #pod
10             #pod =cut
11              
12             use base qw(Rubric::DBI);
13 12     12   56 use Digest::MD5 qw(md5_hex);
  12         22  
  12         1366  
14 12     12   61 use Time::Piece;
  12         27  
  12         454  
15 12     12   443  
  12         8333  
  12         83  
16             __PACKAGE__->table('users');
17              
18             #pod =head1 COLUMNS
19             #pod
20             #pod username - the user's login name
21             #pod password - the hex md5sum of the user's password
22             #pod email - the user's email address
23             #pod created - the user's date of registration
24             #pod
25             #pod verification_code - the code sent to the user for verification
26             #pod NULL if verified
27             #pod
28             #pod =cut
29              
30             __PACKAGE__->columns(
31             All => qw(username password email created verification_code reset_code)
32             );
33              
34             #pod =head1 RELATIONSHIPS
35             #pod
36             #pod =head2 entries
37             #pod
38             #pod Every user has_many entries, which are Rubric::Entry objects. They can be
39             #pod retrieved with the C<entries> accessor, as usual.
40             #pod
41             #pod =cut
42              
43             __PACKAGE__->has_many(entries => 'Rubric::Entry' );
44              
45             #pod =head2 tags
46             #pod
47             #pod A user has as "his" tags all the tags that occur on his entries. There exist a
48             #pod number of accessors for his tag list.
49             #pod
50             #pod =head3 tags
51             #pod
52             #pod This returns an arrayref of all the user's (non-system) tags in their database
53             #pod colation order.
54             #pod
55             #pod =cut
56              
57             __PACKAGE__->set_sql(tags => <<'' );
58             SELECT DISTINCT tag
59             FROM entrytags
60             WHERE entry IN (SELECT id FROM entries WHERE username = ?)
61             AND tag NOT LIKE '@%%'
62             ORDER BY tag
63              
64             my ($self) = @_;
65             my $sth = $self->sql_tags;
66 1     1 1 14370 $sth->execute($self->username);
67 1         4 my $tags = $sth->fetchall_arrayref;
68 1         263 [ map { @$_ } @$tags ];
69 1         325 }
70 1         4  
  3         11  
71             #pod =head3 tags_counted
72             #pod
73             #pod This returns an arrayref of arrayrefs, each containing a tag name and the
74             #pod number of entries tagged with that tag. The pairs are sorted in colation order
75             #pod by tag name.
76             #pod
77             #pod =cut
78              
79             __PACKAGE__->set_sql(tags_counted => <<'' );
80             SELECT DISTINCT tag, COUNT(*) AS count
81             FROM entrytags
82             WHERE entry IN (SELECT id FROM entries WHERE username = ?)
83             AND tag NOT LIKE '@%%'
84             GROUP BY tag
85             ORDER BY tag
86              
87             my ($self) = @_;
88             my $sth = $self->sql_tags_counted;
89             $sth->execute($self->username);
90 13     13 1 37 my $tags = $sth->fetchall_arrayref;
91 13         59 return $tags;
92 13         3003 }
93 13         5204  
94 13         76 #pod =head3 related_tags(\@tags, \%context)
95             #pod
96             #pod This method returns a reference to an array of tags related to all the given
97             #pod tags. Tags are related if they occur together on entries.
98             #pod
99             #pod =cut
100              
101             my ($self, $tags, $context) = @_;
102             $tags = [ keys %$tags ] if ref $tags eq 'HASH';
103             return unless $tags and my @tags = @$tags;
104              
105 6     6 1 16661 # or an exception?
106 6 50       36 return [] if (grep { $_ eq '@private' } @$tags)
107 6 100 100     35 and (($context->{user}||'') ne $self->username);
108              
109             my $query = q|
110 4         25 SELECT DISTINCT tag FROM entrytags
111 4 100 100     9 WHERE entry IN (SELECT id FROM entries WHERE username = ?)
      100        
112             AND tag NOT IN (| . join(',',map { $self->db_Main->quote($_) } @tags) . q|)
113             AND tag NOT LIKE '@%'
114             AND | .
115             join ' AND ',
116 2         8 map { "entry IN (SELECT entry FROM entrytags WHERE tag=$_)" }
117             map { $self->db_Main->quote($_) }
118             @tags;
119              
120 2         74 $self->db_Main->selectcol_arrayref($query, undef, $self->username);
121 2         82 }
  2         104  
122              
123             #pod =head3 related_tags_counted(\@tags, \%context)
124 2         6 #pod
125             #pod This is the obvious conjunction of C<related_tags> and C<tags_counted>. It
126             #pod returns an arrayref of arrayrefs, each a pair of tag/occurance values.
127             #pod
128             #pod =cut
129              
130             my ($self, $tags, $context) = @_;
131             return unless $tags;
132             $tags = [ keys %$tags ] if ref $tags eq 'HASH';
133             return unless my @tags = @$tags;
134              
135 15     15 1 12441 # or an exception?
136 15 100       73 return [] if (grep { $_ eq '@private' } @$tags)
137 7 100       21 and (($context->{user}||'') ne $self->username);
138 7 100       22  
139             my $query = q|
140             SELECT DISTINCT tag, COUNT(*) AS count
141 6         33 FROM entrytags
142 6 100 100     13 WHERE entry IN (SELECT id FROM entries WHERE username = ?)
      100        
143             AND tag NOT IN (| . join(',',map { $self->db_Main->quote($_) } @tags) . q|)
144             AND tag NOT LIKE '@%'
145             AND | .
146             join ' AND ',
147             map { "entry IN (SELECT entry FROM entrytags WHERE tag=$_)" }
148 4         17 map { $self->db_Main->quote($_) }
149             @tags;
150              
151             $query .= " GROUP BY tag";
152 4         139  
153 4         88 $self->db_Main->selectall_arrayref($query, undef, $self->username);
  4         231  
154             }
155              
156 4         14 #pod =head1 INFLATIONS
157             #pod
158 4         11 #pod =head2 created
159             #pod
160             #pod The created column is stored as seconds since epoch, but inflated to
161             #pod Time::Piece objects.
162             #pod
163             #pod =cut
164              
165             __PACKAGE__->has_a(created => 'Time::Piece', deflate => 'epoch');
166              
167             __PACKAGE__->add_trigger(before_create => \&_create_times);
168              
169             my $self = shift;
170             $self->created(scalar gmtime) unless defined $self->{created};
171             }
172              
173             #pod =head1 METHODS
174             #pod
175 5     5   40083 #pod =head2 quick_entry(\%entry)
176 5 100       39 #pod
177             #pod This method creates or updates an entry for the user. The passed entry should
178             #pod include the following data:
179             #pod
180             #pod uri - the URI for the entry
181             #pod tags - the tags for the entry, as a space delimited string
182             #pod title - the title for the entry
183             #pod description - the description for the entry
184             #pod body - the body for the entry
185             #pod
186             #pod If an entry for the link exists, it is updated. Existing tags are replaced
187             #pod with the new tags. If no entry exists, the Rubric::Link is created if needed,
188             #pod and a new entry is then created.
189             #pod
190             #pod The Rubric::Entry object is returned.
191             #pod
192             #pod =cut
193              
194             my ($self, $entry) = @_;
195              
196             return unless $entry->{title};
197             $entry->{tags} = Rubric::Entry->tags_from_string($entry->{tags});
198              
199             my $link;
200             if ($entry->{uri}) {
201 13     13 1 111055 $link = eval { Rubric::Link->find_or_create({ uri => $entry->{uri} }) };
202             return unless $link;
203 13 100       47 }
204 12         83  
205             my $new_entry = $entry->{entryid}
206 12         2094 ? Rubric::Entry->retrieve($entry->{entryid})
207 12 100       39 : $link
208 8         15 ? Rubric::Entry->find_or_create({ link => $link, username => $self })
  8         65  
209 8 50       55784 : Rubric::Entry->create({ username => $self });
210              
211             $new_entry->link($link);
212             $new_entry->title($entry->{title});
213             $new_entry->description($entry->{description});
214 12 100       1152 $new_entry->body($entry->{body} || undef);
    100          
215             $new_entry->update;
216             $new_entry->set_new_tags($entry->{tags});
217              
218 12         181457 return $new_entry;
219 12         3991 }
220 12         2087  
221 12   100     2359 #pod =head2 verify($code)
222 12         2270 #pod
223 12         172569 #pod If the given code matches this user's C<verification_code>, the user will be
224             #pod verified; that is, his C<verification_code> will be undefined.
225 12         116119 #pod
226             #pod =cut
227              
228             my ($self, $code) = @_;
229              
230             return unless $self->verification_code;
231              
232             if ($code and $code eq $self->verification_code) {
233             $self->verification_code(undef);
234             $self->update;
235             return 1;
236 4     4 1 15545 }
237             return;
238 4 100       8 }
239              
240 3 100 100     259 #pod =head2 reset_password($code)
241 1         75 #pod
242 1         171 #pod If the given code matches this user's C<reset_code>, the user's password will be
243 1         9543 #pod reset via C<randomize_password> and his reset code will be undefined. If
244             #pod successful, the new password is returned. Otherwise, the routine returns
245 2         79 #pod false.
246             #pod
247             #pod =cut
248              
249             my ($self, $code) = @_;
250              
251             return unless $self->reset_code;
252              
253             if ($code and $code eq $self->reset_code) {
254             my $password = $self->randomize_password;
255             $self->reset_code(undef);
256             $self->update;
257             return $password;
258 3     3 1 9755 }
259             return;
260 3 50       7 }
261              
262 3 100 100     260 #pod =head2 randomize_password
263 1         73 #pod
264 1         5 #pod This method resets the user's password to a pseudo-random string and returns
265 1         174 #pod the new password.
266 1         10136 #pod
267             #pod =cut
268 2         78  
269             my $length = 15;
270             my @legal = ('a'..'z', 'A'..'Z', 0..9);
271             my $string = join '', map { @legal[rand @legal] } 1 .. $length;
272              
273             return wantarray ? (md5_hex($string), $string) : md5_hex($string);
274             }
275              
276             my ($self) = @_;
277             my ($pass_md5, $password) = $self->__random_string;
278            
279 3     3   4 $self->password($pass_md5);
280 3         22 $self->update;
281 3         6  
  45         88  
282             return $password;
283 3 100       22 }
284              
285             #pod =head2 randomize_reset_code
286             #pod
287 1     1 1 3 #pod This method resets the user's reset code to the md5sum of a pseudo-random
288 1         2 #pod string.
289             #pod
290 1         4 #pod =cut
291 1         272  
292             my ($self) = @_;
293 1         8752 my $reset_code = $self->__random_string;
294             $self->reset_code($reset_code);
295             $self->update;
296             }
297              
298             #pod =head2 randomize_verification_code
299             #pod
300             #pod This method resets the user's verification code to the md5sum of a
301             #pod pseudo-random string.
302             #pod
303             #pod =cut
304 1     1 1 507  
305 1         4 my ($self) = @_;
306 1         5 my $verification_code = $self->__random_string;
307 1         363 $self->verification_code($verification_code);
308             $self->update;
309             }
310              
311             1;
312              
313              
314             =pod
315              
316             =encoding UTF-8
317              
318 1     1 1 10485 =head1 NAME
319 1         3  
320 1         4 Rubric::User - a Rubric user
321 1         358  
322             =head1 VERSION
323              
324             version 0.157
325              
326             =head1 DESCRIPTION
327              
328             This class provides an interface to Rubric users. It inherits from
329             Rubric::DBI, which is a Class::DBI class.
330              
331             =head1 PERL VERSION
332              
333             This code is effectively abandonware. Although releases will sometimes be made
334             to update contact info or to fix packaging flaws, bug reports will mostly be
335             ignored. Feature requests are even more likely to be ignored. (If someone
336             takes up maintenance of this code, they will presumably remove this notice.)
337             This means that whatever version of perl is currently required is unlikely to
338             change -- but also that it might change at any new maintainer's whim.
339              
340             =head1 COLUMNS
341              
342             username - the user's login name
343             password - the hex md5sum of the user's password
344             email - the user's email address
345             created - the user's date of registration
346              
347             verification_code - the code sent to the user for verification
348             NULL if verified
349              
350             =head1 RELATIONSHIPS
351              
352             =head2 entries
353              
354             Every user has_many entries, which are Rubric::Entry objects. They can be
355             retrieved with the C<entries> accessor, as usual.
356              
357             =head2 tags
358              
359             A user has as "his" tags all the tags that occur on his entries. There exist a
360             number of accessors for his tag list.
361              
362             =head3 tags
363              
364             This returns an arrayref of all the user's (non-system) tags in their database
365             colation order.
366              
367             =head3 tags_counted
368              
369             This returns an arrayref of arrayrefs, each containing a tag name and the
370             number of entries tagged with that tag. The pairs are sorted in colation order
371             by tag name.
372              
373             =head3 related_tags(\@tags, \%context)
374              
375             This method returns a reference to an array of tags related to all the given
376             tags. Tags are related if they occur together on entries.
377              
378             =head3 related_tags_counted(\@tags, \%context)
379              
380             This is the obvious conjunction of C<related_tags> and C<tags_counted>. It
381             returns an arrayref of arrayrefs, each a pair of tag/occurance values.
382              
383             =head1 INFLATIONS
384              
385             =head2 created
386              
387             The created column is stored as seconds since epoch, but inflated to
388             Time::Piece objects.
389              
390             =head1 METHODS
391              
392             =head2 quick_entry(\%entry)
393              
394             This method creates or updates an entry for the user. The passed entry should
395             include the following data:
396              
397             uri - the URI for the entry
398             tags - the tags for the entry, as a space delimited string
399             title - the title for the entry
400             description - the description for the entry
401             body - the body for the entry
402              
403             If an entry for the link exists, it is updated. Existing tags are replaced
404             with the new tags. If no entry exists, the Rubric::Link is created if needed,
405             and a new entry is then created.
406              
407             The Rubric::Entry object is returned.
408              
409             =head2 verify($code)
410              
411             If the given code matches this user's C<verification_code>, the user will be
412             verified; that is, his C<verification_code> will be undefined.
413              
414             =head2 reset_password($code)
415              
416             If the given code matches this user's C<reset_code>, the user's password will be
417             reset via C<randomize_password> and his reset code will be undefined. If
418             successful, the new password is returned. Otherwise, the routine returns
419             false.
420              
421             =head2 randomize_password
422              
423             This method resets the user's password to a pseudo-random string and returns
424             the new password.
425              
426             =head2 randomize_reset_code
427              
428             This method resets the user's reset code to the md5sum of a pseudo-random
429             string.
430              
431             =head2 randomize_verification_code
432              
433             This method resets the user's verification code to the md5sum of a
434             pseudo-random string.
435              
436             =head1 AUTHOR
437              
438             Ricardo SIGNES <rjbs@semiotic.systems>
439              
440             =head1 COPYRIGHT AND LICENSE
441              
442             This software is copyright (c) 2004 by Ricardo SIGNES.
443              
444             This is free software; you can redistribute it and/or modify it under
445             the same terms as the Perl 5 programming language system itself.
446              
447             =cut