4 use List
::Util
qw(shuffle);
9 return ( homepage
=> "Homepage",
10 (map { $_->name, $_->display_name } LJ
::Widget
::VerticalContentControl
->verticals_remote_can_moderate),
18 return scalar(grep { $_ eq $domain } $class->get_domains) ?
1 : 0;
21 # returns 'current' or 'old' depending on given start and end times
26 return $class->is_current(%times) ?
'current' : 'old';
29 # given a start and end time, returns if now is between those two times
34 return 0 unless $times{start
} && $times{end
};
37 return $times{start
} <= $now && $times{end
} >= $now;
51 # first, is it in our per-request cache?
52 my $questions = $LJ::QotD
::REQ_CACHE_QOTD
{$type};
53 return $questions if $questions;
55 my $memkey = $class->memcache_key($type);
56 my $memcache_data = LJ
::MemCache
::get
($memkey);
58 # fill the request cache since it was empty
59 $class->request_cache_set($type, $memcache_data);
61 return $memcache_data;
64 sub request_cache_set
{
69 $LJ::QotD
::REQ_CACHE_QOTD
{$type} = $val;
77 # first set in request cache
78 $class->request_cache_set($type, $val);
81 my $memkey = $class->memcache_key($type);
82 my $expire = 60*5; # 5 minutes
83 return LJ
::MemCache
::set
($memkey, $val, $expire);
91 delete $LJ::QotD
::REQ_CACHE_QOTD
{$type};
94 my $memkey = $class->memcache_key($type);
95 return LJ
::MemCache
::delete($memkey);
98 # returns the current active questions
99 sub load_current_questions
{
103 my $questions = $class->cache_get('current');
104 return _sort_cur_questions
(@
$questions) if $questions;
106 my $dbh = LJ
::get_db_writer
()
107 or die "no global database writer for QotD";
109 my $sth = $dbh->prepare(
110 "SELECT * FROM qotd WHERE time_start <= UNIX_TIMESTAMP() AND time_end >= UNIX_TIMESTAMP() AND active='Y' ORDER BY time_start desc"
115 while (my $row = $sth->fetchrow_hashref) {
119 @rows = _sort_cur_questions
(@rows);
120 $class->cache_set('current', \
@rows);
125 # returns the non-current active questions that
126 # have an end time more recent than a month ago
127 sub load_old_questions
{
131 my $questions = $class->cache_get('old');
132 return _sort_old_questions
(@
$questions) if $questions;
134 my $dbh = LJ
::get_db_writer
()
135 or die "no global database writer for QotD";
137 my $sth = $dbh->prepare(
138 "SELECT * FROM qotd WHERE time_end >= UNIX_TIMESTAMP()-86400*31 AND time_end < UNIX_TIMESTAMP() AND active='Y' ORDER BY time_end desc"
143 while (my $row = $sth->fetchrow_hashref) {
147 @rows = _sort_old_questions
(@rows);
148 $class->cache_set('old', \
@rows);
154 sub _sort_cur_questions
{
156 # sponsored should be first
158 map { delete $_->{is_special_num
}; $_ } # remove 'is_special_num' member
159 sort { $b->{is_special_num
} <=> $a->{is_special_num
} } # sort by is_special_num
160 map { $_->{is_special_num
} = $_->{is_special
} eq 'Y' ?
1 : 0; $_ } # is_special as num
165 sub _sort_old_questions
{
167 # sort questions by end day then by 'is_special' flag
170 # remove temporary fields
171 delete $_->{day_end
};
172 delete $_->{is_special_num
};
174 # mark this question loaded as 'old'
180 $b->{day_end
} <=> $a->{day_end
} # first by day
181 || $b->{is_special_num
} <=> $a->{is_special_num
} # then sponsored first
183 map { $_->{is_special_num
} = $_->{is_special
} eq 'Y' ?
1 : 0; $_ } # is_special as num
184 map { $_->{day_end
} = int $_->{time_end
} / 86400; $_ } # add DAY of question
190 sub filter_by_domain
{
197 foreach my $q (@questions) {
198 push @questions_ret, $q if $q->{domain
} eq $domain;
201 return @questions_ret;
204 sub filter_by_eff_class
{
209 my $eff_class = LJ
::run_hook
("qotd_get_eff_class", $u);
210 return @questions unless $eff_class;
213 if ($eff_class eq "logged_out") {
214 foreach my $q (@questions) {
215 push @questions_ret, $q if $q->{show_logged_out
} eq "Y";
218 my @classes = ( $eff_class );
219 my $class_mask = LJ
::mask_from_classes
(@classes);
220 foreach my $q (@questions) {
221 push @questions_ret, $q if ($q->{cap_mask
} & $class_mask) > 0;
225 return @questions_ret;
228 sub filter_by_country
{
233 # split the list into a list of questions with countries and a list of questions without countries
234 my @questions_with_countries;
235 my @questions_without_countries;
236 foreach my $question (@questions) {
237 if ($question->{countries
}) {
238 push @questions_with_countries, $question;
240 push @questions_without_countries, $question;
244 # get the user's country if defined, otherwise the country of the remote IP
246 $country = lc $u->country if $u;
247 $country = lc LJ
::country_of_remote_ip
() unless $country;
251 # get the questions that are targeted at the user's country
253 foreach my $question (@questions_with_countries) {
254 next unless grep { $_ eq $country } split(",", $question->{countries
});
255 push @questions_ret, $question;
259 return (@questions_ret, @questions_without_countries);
266 my $skip = defined $opts{skip
} ?
int($opts{skip
}) : 0;
267 my $domain = defined $opts{domain
} ?
lc $opts{domain
} : "homepage";
269 # if true, get all questions for this user from the last month
270 # overrides value of $skip
271 my $all = defined $opts{all
} ?
$opts{all
} : 0;
273 # direct the questions at the given $u, or remote if no $u given
274 my $u = $opts{user
} && LJ
::isu
($opts{user
}) ?
$opts{user
} : LJ
::get_remote
();
276 my @questions = ( $class->load_current_questions, $class->load_old_questions );
278 @questions = $class->filter_by_domain($u, $domain, @questions) unless $all;
279 @questions = $class->filter_by_eff_class($u, @questions);
280 @questions = $class->filter_by_country($u, @questions);
282 @questions = grep { ref $_ } @questions;
285 my @cur = grep { not $_->{old
} } @questions;
286 my @old = grep { $_->{old
} } @questions;
287 @questions = (_sort_cur_questions
(@cur), _sort_old_questions
(@old));
289 # just amount of suitable questions in queue
290 return scalar @questions if $opts{count
};
292 return @questions if $all or $opts{all_filtered
}; #
294 # is there any question?
295 return unless @questions;
297 # just one question...
298 my $index = $skip > 0 ?
$skip - 1 : 0;
299 return $questions[$index];
307 my $dbh = LJ
::get_db_writer
()
308 or die "Unable to store question: no global dbh";
310 my @classes = split(/\s*,\s*/, $vals{classes
});
311 $vals{cap_mask
} = LJ
::mask_from_classes
(@classes);
312 $vals{show_logged_out
} = $vals{show_logged_out
} ?
'Y' : 'N';
314 # update existing question
316 $dbh->do("UPDATE qotd SET time_start=?, time_end=?, active=?, subject=?, text=?, tags=?, " .
317 "from_user=?, img_url=?, extra_text=?, cap_mask=?, show_logged_out=?, countries=?, link_url=?, domain=?, impression_url=?, is_special=? WHERE qid=?",
318 undef, (map { $vals{$_} } qw(time_start time_end active subject text tags from_user img_url extra_text cap_mask show_logged_out countries link_url domain impression_url is_special qid)))
319 or die "Error updating qotd: " . $dbh->errstr;
321 # insert new question
323 $dbh->do("INSERT INTO qotd VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
324 undef, "null", (map { $vals{$_} } qw(time_start time_end active subject text tags from_user img_url extra_text cap_mask show_logged_out countries link_url domain impression_url is_special)))
325 or die "Error adding qotd: " . $dbh->errstr;
328 # insert/update question subject and text in translation system
329 my $qid = $vals{qid
} || $dbh->{mysql_insertid
};
330 my $ml_key = LJ
::Widget
::QotD
->ml_key("$qid.text");
331 LJ
::Widget
->ml_set_text($ml_key => $vals{text
});
332 $ml_key = LJ
::Widget
::QotD
->ml_key("$qid.subject");
333 LJ
::Widget
->ml_set_text($ml_key => $vals{subject
});
335 # insert/update extra text in translation system
336 $ml_key = LJ
::Widget
::QotD
->ml_key("$qid.extra_text");
337 if ($vals{extra_text
}) {
338 LJ
::Widget
->ml_set_text($ml_key => $vals{extra_text
});
340 my $string = LJ
::no_ml_cache
(sub { LJ
::Widget
->ml($ml_key) });
341 LJ
::Widget
->ml_remove_text($ml_key) unless LJ
::Widget
->ml_is_missing_string($string);
345 my $type = $class->get_type( start
=> $vals{time_start
}, end
=> $vals{time_end
} );
346 $class->cache_clear($type);
351 # returns all questions that started during the given month
352 sub get_all_questions_starting_during_month
{
354 my ($year, $month) = @_;
356 my $dbh = LJ
::get_db_writer
()
357 or die "Error: no global dbh";
359 my $time_start = DateTime
->new( year
=> $year, month
=> $month, time_zone
=> 'America/Los_Angeles' );
360 my $time_end = $time_start->clone;
361 $time_end = $time_end->add( months
=> 1 );
362 $time_end = $time_end->subtract( seconds
=> 1 ); # we want time_end to be the end of the last day of the month
364 my $sth = $dbh->prepare("SELECT * FROM qotd WHERE time_start >= ? AND time_start <= ?");
365 $sth->execute($time_start->epoch, $time_end->epoch)
366 or die "Error getting this month's questions: " . $dbh->errstr;
369 while (my $row = $sth->fetchrow_hashref) {
373 # sort questions in descending order by start time (newest first)
375 sort { $b->{time_start
} <=> $a->{time_start
} }
376 grep { ref $_ } @rows;
381 # returns all questions that are running during the given month
382 sub get_all_questions_running_during_month
{
384 my ($year, $month) = @_;
386 my $dbh = LJ
::get_db_writer
()
387 or die "Error: no global dbh";
389 my $time_start = DateTime
->new( year
=> $year, month
=> $month, time_zone
=> 'America/Los_Angeles' );
390 my $time_end = $time_start->clone;
391 $time_end = $time_end->add( months
=> 1 );
392 $time_end = $time_end->subtract( seconds
=> 1 ); # we want time_end to be the end of the last day of the month
394 my $time_start_epoch = $time_start->epoch;
395 my $time_end_epoch = $time_end->epoch;
397 my $sth = $dbh->prepare(
398 "SELECT * FROM qotd WHERE " .
399 # starts before the start of the month and ends after the start of the month
400 "(time_start <= ? AND time_end >= ?) OR " .
401 # starts before the end of the month and ends after the end of the month
402 "(time_start <= ? AND time_end >= ?) OR " .
403 # starts after the start of the month and ends before the end of the month
404 "(time_start >= ? AND time_end <= ?) OR " .
405 # starts before the start of the month and ends after the end of the month
406 "(time_start <= ? AND time_end >= ?)"
409 $time_start_epoch, $time_start_epoch, $time_end_epoch, $time_end_epoch, $time_start_epoch, $time_end_epoch, $time_start_epoch, $time_end_epoch
411 or die "Error getting this month's questions: " . $dbh->errstr;
414 while (my $row = $sth->fetchrow_hashref) {
418 # sort questions in descending order by start time (newest first)
420 sort { $b->{time_start
} <=> $a->{time_start
} }
421 grep { ref $_ } @rows;
426 # given an id for a question, returns the info for it
427 sub get_single_question
{
431 my $dbh = LJ
::get_db_writer
()
432 or die "Error: no global dbh";
434 my $sth = $dbh->prepare("SELECT * FROM qotd WHERE qid = ?");
436 or die "Error getting single question: " . $dbh->errstr;
438 return $sth->fetchrow_hashref;
441 # change the active status of the given question
442 sub change_active_status
{
447 my $to = delete $opts{to
};
448 croak
"invalid 'to' field" unless $to =~ /^(active|inactive)$/;
450 my $question = $class->get_single_question($qid)
451 or die "Invalid question: $qid";
453 my $dbh = LJ
::get_db_writer
()
454 or die "Error: no global dbh";
456 my $active_val = $to eq 'active' ?
'Y' : 'N';
457 my $rv = $dbh->do("UPDATE qotd SET active = ? WHERE qid = ?", undef, $active_val, $qid)
458 or die "Error updating active status of question: " . $dbh->errstr;
460 my $type = $class->get_type( start
=> $question->{time_start
}, end
=> $question->{time_end
} );
461 $class->cache_clear($type);
466 # given a comma-separated list of tags, remove the default tag(s) from the list
467 sub remove_default_tags
{
469 my $tag_list = shift;
471 my $tag = $class->entry_tag;
472 $tag_list =~ s/\s*${tag},?\s*//g;
477 # given a comma-separated list of tags, add the default tag(s) to the list
478 sub add_default_tags
{
480 my $tag_list = shift;
482 my $tag = $class->entry_tag;
485 return "$tag, " . $tag_list;
491 # tag given to QotD entries
492 sub entry_tag
{ "writer's block" }
494 # parse the given URL
495 # * replace '[[uniq]]' with a unique identifier
500 my $qid = $opts{qid
};
501 my $url = $opts{url
};
503 my $uniq = LJ
::pageview_unique_string
() . $qid;
504 $uniq = Digest
::SHA1
::sha1_hex
($uniq);
506 $url =~ s/\[\[uniq\]\]/$uniq/g;
515 return undef unless $qid;
517 my $qlength = LJ
::MemCache
::get
("qotd_count:$qid");
518 return $qlength if defined $qlength;
520 my $queue = LJ
::queue
("latest_qotd_$qid");
522 $qlength = $queue->length;
523 LJ
::MemCache
::set
("qotd_count:$qid", $qlength);
533 my $question = shift;
537 # Get some additinal info to draw controlls
540 # for OLD questions we should display the end day as day of question
541 # for CURRENT questions we display today as day of questsion.
542 $_->{day
} = $_->{old
}
543 ?
int ($_->{time_end
} / 86400)
544 : int (time / 86400);
548 $class->get_questions( user
=> $u, all_filtered
=> 1, domain
=> $domain );
550 $question->{day
} = $question->{old
}
551 ?
int ($question->{time_end
} / 86400)
552 : int (time / 86400);
555 grep { $_->{day
} eq $question->{day
} }
558 my $total = scalar @total_this_day;
560 # number of current question in this day questions
562 my @ar = @total_this_day;
563 while (my $q = shift @ar){
565 last if $q->{qid
} eq $question->{qid
};
569 my ($day, $month_num) = (gmtime( $question->{day
} * 86400 + 1))[3, 4];
570 my $month_short = LJ
::Lang
::month_short
($month_num + 1);
572 return ($month_short, $day, $num, $total);