LJSUP-17669: Login.bml form refactoring
[livejournal.git] / cgi-bin / LJ / TopEntries.pm
bloba3b1a6b14950e48a37395869ae3127b6eb305f0e
1 package LJ::TopEntries;
3 use strict;
4 use base qw(LJ::Widget);
5 use Carp qw(croak);
6 use LJ::ExtBlock;
7 use Storable qw//;
8 use LJ::RelationService;
10 =head
11 · Arts & Culture
12 · Books & Writing
13 · Computers & Technology
14 · Entertainment
15 · Fashion & Style
16 · Food & Drink
17 · Gaming
18 · Health & Fitness
19 · Hobbies & Crafts
20 · Home & Garden
21 · Life
22 · LiveJournal Culture
23 · News & Politics
24 · Regional
25 · Schools & Education
26 · Science & Nature
27 · Society & Culture
28 · Spirituality & Beliefs
29 =cut
31 my %domains = (
32 hmp_ontd => 'Homepage/ONTD',
33 hmp_spotlight => 'Homepage/Spotlight',
34 anythingdisney => 'AnythingDiz',
35 bullying_begone => 'bullying_begone',
36 map { ($_ => $_) } qw{
37 ontd-political
38 aramatheydidnt
39 craftgrrlontd-political
40 aramatheydidnt
41 craftgrrl
42 vaginapagina
43 ontd-cats
44 ontdgames
45 spookypoo
46 beauty101
47 lesbian
51 =head
52 culture => 'Arts & Culture',
53 entertainment => 'Entertainment',
54 books => 'Books & Writing',
55 computers => 'Computers & Technology',
56 fashion => 'Fashion & Style',
57 food => 'Food & Drink',
58 gaming => 'Gaming',
59 health => 'Health & Fitness',
60 life => 'Life',
61 lj_culture => 'LiveJournal Culture',
62 news => 'News & Politics',
63 regional => 'Regional',
64 schools => 'Schools & Education',
65 science => 'Science & Nature',
66 society => 'Society & Culture',
67 spirit => 'Spirituality & Beliefs',
69 =cut
71 # if domain key is equal to community name, than no need to insert this domain here
72 my %community_for_domain = (
73 hmp_ontd => 'ohnotheydidnt'
76 my @order = qw/
77 hmp_ontd
78 anythingdisney
79 bullying_begone
80 ontd-political
81 aramatheydidnt
82 craftgrrlontd-political
83 aramatheydidnt
84 craftgrrl
85 vaginapagina
86 ontd-cats
87 ontdgames
88 spookypoo
89 beauty101
90 lesbian
92 =head
93 culture
94 entertainment
95 books
96 computers
97 fashion
98 food
99 gaming
100 health
101 life
102 lj_culture
103 news
104 regional
105 schools
106 science
107 society
108 spirit
110 =cut
113 sub domain2name { my $class = shift; $domains{ +shift } }
115 sub domains {
116 my $class = shift;
117 my $u = shift;
119 return @order if LJ::check_priv($u, "siteadmin", "topentries");
121 my @result;
123 foreach my $candidate (@order) {
124 my $comm_name = $community_for_domain{$candidate};
125 $comm_name = $candidate unless $comm_name;
126 my $comm = LJ::load_user($comm_name);
127 my $allow_access = LJ::check_rel($comm, $u, 'S') ||
128 LJ::check_rel($comm, $u, 'A');
130 next unless $allow_access;
131 next unless $comm;
133 push @result, $candidate if $u and $u->can_manage($comm);
135 return @result;
138 sub new {
139 my $class = shift;
140 my %opts = @_;
142 my $domain = $opts{domain};
143 if (not $domain){
144 ## prev API to this class
145 ## it can be removed after #66 release
146 $domain = 'hmp_ontd';
148 Carp::confess("unknown domain: $domain")
149 unless exists $domains{$domain};
151 my $self = {domain => $domain};
152 $self->{remote} = $opts{remote} || LJ::get_remote();
153 $self->{timelimit} = $opts{timelimit} || 24 * 3600;
155 return bless $self, $class;
158 # key <---> hash. Key - a string with four numbers, hash - full info about post.
159 sub _key_from_hash {
160 my $self = shift;
161 my $h = shift;
162 return "$h->{'timestamp'}:$h->{'journalid'}:$h->{'jitemid'}:$h->{'userpicid'}";
165 sub _hash_from_key {
166 my $self = shift;
167 my %args = @_;
169 my $timestamp = $args{timestamp};
170 my $journalid = $args{journalid};
171 my $jitemid = $args{jitemid};
172 my $userpicid = $args{userpicid};
173 my $tags = $args{tags};
174 my $vertical_name = $args{vertical_name};
175 my $vertical_uri = $args{vertical_uri};
177 # my $key = shift;
178 # my ($timestamp, $journalid, $jitemid, $userpicid) = @$key;
180 return undef unless $journalid && $jitemid && $userpicid;
182 my $entry = LJ::Entry->new($journalid, jitemid => $jitemid);
184 return undef unless $entry;
186 my $poster = $entry->poster();
187 my $journal = LJ::load_userid($journalid);
189 return undef unless $poster && $journal;
191 # Get userpic from entry
192 my $userpic = LJ::Userpic->new($poster, $userpicid);
194 return undef unless $userpic && $userpic->valid();
196 my $subject = $entry->subject_text();
197 my $subject_trimed = LJ::html_trim_4gadgets($subject, 70, '');
198 $subject_trimed .= '...' if $subject_trimed ne $subject;
200 return
202 posterid => $poster->{'userid'},
203 journalid => $journalid,
204 jitemid => $jitemid,
205 userpicid => $userpicid,
207 subj => $subject_trimed,
208 text => LJ::html_trim_4gadgets($entry->event_text(), 50, $entry->url()),
209 #revtime => $entry->prop('revtime'),
210 url => $entry->url(),
211 time => $entry->logtime_unix(),
212 userpic => $userpic->url(),
214 ## Do not store results of ljuser_display() anywhere
215 ## becouse they depend on $remote user.
216 ## this field should be generated on fly.
217 # poster => $poster->ljuser_display(),
219 timestamp => $timestamp,
221 comments => $entry->reply_count,
222 comments_url=> $entry->url(anchor => 'comments'),
224 logtime => $entry->logtime_unix,
225 tags => $tags,
226 vertical_name => $vertical_name,
227 vertical_uri => $vertical_uri,
229 key => "$journalid:$jitemid",
233 # Clean list before store: remove old elements.
234 sub _clean_list {
235 my $self = shift;
236 my %opts = @_;
238 my @list = sort {$b->{'timestamp'} <=> $a->{'timestamp'}} @{$self->{'featured_posts'}};
240 return @list if $self->{'min_entries'} >= scalar @list; # We already has a minimum.
242 # Remove old entries - stay at least 'min_entries' recent and all within 24h from now.
243 my $time_edge = time() - $self->{'timelimit'};
244 my $count = $self->{'min_entries'};
245 @list = grep { ($count-- > 0) || ($time_edge - $_->{'timestamp'} < 0) } @list;
247 return @list;
250 sub _sort_list {
251 my $self = shift;
252 my %opts = @_;
253 my @list =
254 sort {$b->{'timestamp'} <=> $a->{'timestamp'}}
255 grep { $_ && !($_->{'revtime'} && $_->{'revtime'} > $_->{'timestamp'}) } # Sanity check
256 @{$self->{'featured_posts'}};
258 return @list if $opts{'raw'};
260 # Remove old entries - stay at least 'min_entries' recent and all within 24h from now.
261 my $time_edge = time() - 24 * 3600;
262 my $count = $self->{'min_entries'};
263 @list = grep { ($count-- > 0) || ($time_edge - $_->{'timestamp'} < 0) } @list;
265 # Remove elements below 'max_entries'.
266 $count = scalar @list - $self->{'max_entries'};
268 return @list if $count <= 0;
270 while ($count--) {
271 splice @list, int(rand(scalar @list)), 1;
274 return @list;
277 # store all from blessed hash to journal property.
278 sub _store_featured_posts {
279 my $self = shift;
280 my %opts = @_;
282 # my $prop = $self->{'min_entries'} . ':' . $self->{'max_entries'} . ':0:0|' .
283 # join('|', map { $self->_key_from_hash($_) } $self->_clean_list(%opts));
284 #$prop =~ s/\|$//;
287 my @spots = $self->_clean_list(%opts);
288 my $struct = {
289 min_entries => $self->{min_entries},
290 max_entries => $self->{max_entries},
291 timelimit => $self->{timelimit},
293 spots => \@spots,
295 my $data = Storable::nfreeze($struct);
298 my $domain = $self->{domain};
299 LJ::ExtBlock->create_or_replace("spts_$domain" => $data);
301 $self->{_post_loaded} = 0;
304 # load all from property to blessed hash.
305 sub _load_featured_posts {
306 my $self = shift;
307 my %opts = @_;
309 return if $self->{_post_loaded};
311 my $domain = $self->{domain};
312 my $ext_block = LJ::ExtBlock->load_by_id("spts_$domain", {skip_local_cache => 1});
313 my $prop_val = $ext_block ? $ext_block->blocktext : '';
314 =head
315 $prop_val = '3:5:0:0' unless $prop_val;
317 my @entities = map { [ split /:/ ] } split(/\|/, $prop_val);
319 my ($min_entries, $max_entries, undef, undef) = @{shift @entities};
321 $self->{'min_entries'} = $min_entries;
322 $self->{'max_entries'} = $max_entries;
323 $self->{'featured_posts'} = [ map { $self->_hash_from_key($_) } @entities ];
324 =cut
325 if ($prop_val){
326 my $struct = eval { Storable::thaw($prop_val) };
327 $self->{min_entries} = $struct->{min_entries};
328 $self->{max_entries} = $struct->{max_entries};
329 $self->{featured_posts} = $struct->{spots} || [];
330 $self->{timelimit} = $struct->{timelimit};
332 my $users = {};
333 $users = LJ::load_userids( map {$_->{posterid} } @{ $self->{featured_posts} } )
334 if ref $self->{featured_posts} eq 'ARRAY';
336 ## Update comments couter
337 foreach my $spot (@{ $self->{featured_posts} }){
338 my $entry = LJ::Entry->new($spot->{journalid}, jitemid => $spot->{jitemid});
339 next unless $entry;
341 my $u = $users->{$spot->{posterid}};
342 next unless $u;
344 $spot->{comments} = $entry->reply_count();
345 $spot->{logtime} = $spot->{time} = $entry->logtime_unix();
347 $spot->{poster} = $u->ljuser_display();
350 $self->{min_entries} ||= 3;
351 $self->{max_entries} ||= 5;
352 $self->{timelimit} ||= 24*3600;
354 $self->{_post_loaded} = 1;
356 return $self->_sort_list(%opts);
359 # geters/seters
360 sub get_featured_posts {
361 my $self = shift;
362 my %opts = @_;
363 return $self->{'featured_posts'} ?
364 $self->_sort_list(%opts) : $self->_load_featured_posts(%opts);
367 sub min_entries {
368 my $self = shift;
370 $self->_load_featured_posts();
371 if ($_[0]) {
372 my $min_entries = shift;
373 if ($self->{'min_entries'} != $min_entries) {
374 $self->{'min_entries'} = $min_entries;
375 $self->_store_featured_posts();
379 return $self->{'min_entries'};
382 sub max_entries {
383 my $self = shift;
385 $self->_load_featured_posts();
386 if ($_[0]) {
387 my $max_entries = shift;
388 if ($self->{'max_entries'} != $max_entries) {
389 $self->{'max_entries'} = $max_entries;
390 $self->_store_featured_posts();
394 return $self->{'max_entries'};
398 sub remove_after {
399 my $self = shift;
400 $self->_load_featured_posts();
402 ## set new value
403 if (my $hours = shift @_){
404 $self->{timelimit} = $hours * 3600; ## keep in seconds
405 $self->_store_featured_posts();
407 return $self->{timelimit} / 3600;
410 # Add/del entries.
411 sub add_entry {
412 my $self = shift;
413 my %opts = @_;
415 my $entry = $opts{'entry'};
416 return 'wrong entry' unless $entry;
418 my $tags = $opts{tags};
419 my $vertical_name = $opts{vertical_name};
420 my $vertical_uri = $opts{vertical_uri};
422 my $timestamp = time();
424 my ($journalid, $jitemid, $poster, $userpic) =
425 ($entry->journalid(), $entry->jitemid(), $entry->poster(), $entry->userpic());
427 return 'wrong entry poster' unless $poster;
429 my $userpicid = $userpic ? $userpic->id() : ($poster->{'defaultpicid'} || 0);
431 $self->delete_entry(key => "$journalid:$jitemid");
433 $self->get_featured_posts(raw => 1, %opts); # make sure we has all fresh data
435 ## Fullfill with other data
436 my $post = $self->_hash_from_key(
437 timestamp => $timestamp,
438 journalid => $journalid,
439 jitemid => $jitemid,
440 userpicid => $userpicid,
441 tags => $tags,
442 vertical_name => $vertical_name,
443 vertical_uri => $vertical_uri,
446 if ($post) {
447 push @{$self->{'featured_posts'}}, $post;
448 $self->_store_featured_posts(%opts);
449 return '';
452 # all other error conditions checked before call _hash_from_key()
453 return 'userpic missed or does not valid';
456 sub delete_entry {
457 my $self = shift;
458 my %opts = @_;
460 return unless $opts{'key'} =~ /(\d+):(\d+)/;
462 my ($journalid, $jitemid) = ($1, $2);
463 return unless $journalid && $jitemid;
465 @{$self->{'featured_posts'}} = grep {
466 ! ( $_->{'journalid'} == $journalid && $_->{'jitemid'} == $jitemid )
467 } $self->get_featured_posts(raw => 1, %opts);
469 $self->_store_featured_posts(%opts);