Committer: sbelyaev
LJSUP-9307: Delayed and sticky entries base impl.A branches/delayed_entries/cgi-bin/LJ/DelayedEntry.pm U branches/delayed_entries/cgi-bin/LJ/Entry.pm U branches/delayed_entries/cgi-bin/LJ/S2/RecentPage.pm U branches/delayed_entries/cgi-bin/LJ/Widget/EntryForm.pm U branches/delayed_entries/cgi-bin/bml/scheme/global.look U branches/delayed_entries/cgi-bin/ljprotocol.pl U branches/delayed_entries/htdocs/editjournal.bml U branches/delayed_entries/htdocs/edittags.bml U branches/delayed_entries/htdocs/update.bml
Added: branches/delayed_entries/cgi-bin/LJ/DelayedEntry.pm =================================================================== --- branches/delayed_entries/cgi-bin/LJ/DelayedEntry.pm (rev 0) +++ branches/delayed_entries/cgi-bin/LJ/DelayedEntry.pm 2011-08-19 10:10:27 UTC (rev 19792) @@ -0,0 +1,1437 @@ +package LJ::DelayedEntry; + +use strict; +use warnings; +require 'ljprotocol.pl'; +use DateTime; +use Storable; +use POSIX; +use Data::Dumper; +use LJ::User; + +sub create { + my ( $class, $req, $opts ) = @_; + + __assert( $opts ); + __assert( $opts->{journal} ); + __assert( $opts->{poster} ); + __assert( $req ); + + my $self = bless {}, $class; + + my $journal = $opts->{journal}; + my $poster = $opts->{poster}; + $req->{'event'} =~ s/\r\n/\n/g; # compact new-line endings to more comfort chars count near 65535 limit + + my $data_ser = __serialize($journal, $req); + + my $journalid = $journal->userid; + my $posterid = $poster->userid; + my $subject = $req->{subject}; + my $posttime = __get_datatime($req); + my $dbh = LJ::get_db_writer(); + + my $delayedid = LJ::alloc_user_counter( $journal, + 'Y', + undef); + + my $security = "public"; + my $uselogsec = 0; + + if ($req->{'security'} eq "usemask" || $req->{'security'} eq "private") { + $security = $req->{'security'}; + } + + if ($req->{'security'} eq "usemask") { + $uselogsec = 1; + } + + my $now = $dbh->selectrow_array("SELECT UNIX_TIMESTAMP()"); + + my $qsecurity = $dbh->quote($security); + my $qallowmask = $req->{'allowmask'}+0; + my $qposttime = $dbh->quote($posttime); + my $utime = $dbh->selectrow_array("SELECT UNIX_TIMESTAMP($qposttime)"); + + my $rlogtime = $LJ::EndOfTime - $now; + my $rposttime = $LJ::EndOfTime - $utime; + + my $taglist = __extract_tag_list(\$req->{props}->{taglist}); + + $journal->do("INSERT INTO delayedlog2 (journalid, delayedid, posterid, subject, " . + "logtime, posttime, security, allowmask, year, month, day, rlogtime, revptime) " . + "VALUES ($journalid, $delayedid, $posterid, ?, NOW(), $qposttime, ". + "$qsecurity, $qallowmask, ?, ?, ?, $rlogtime, $rposttime)", + undef, LJ::text_trim($req->{'subject'}, 30, 0), + $req->{year}, $req->{mon}, $req->{day} ); + + $journal->do("INSERT INTO delayedblob2 ". + "VALUES ($journalid, $delayedid, $data_ser)"); + + __kill_dayct2_cache($self->journal); + + $self->{journal} = $opts->{journal}; + $self->{posttime} = $opts->{posttime}; + $self->{poster} = $opts->{poster}; + $self->{data} = $req; + $self->{taglist} = $taglist; + $self->{delayed_id} = $delayedid; + return $self; +} + +sub delayedid { + my ($self) = @_; + return $self->{delayed_id}; +} + +sub data { + my ($self) = @_; + return $self->{data}; +} + +sub subject { + my ($self) = @_; + return $self->{data}->{subject}; +} + +sub event { + my ($self) = @_; + return $self->{data}->{event}; +} + +sub poster { + my ($self) = @_; + return $self->{poster}; +} + +sub posterid { + my ($self) = @_; + return $self->poster->userid; +} + +sub journal { + my ($self) = @_; + return $self->{journal}; +} + +sub journalid { + my ($self) = @_; + return $self->journal->userid; +} + +sub posttime { + my ($self) = @_; + return $self->{posttime}; +} + +sub alldatepart { + my ($self) = @_; + return $self->{alldatepart}; +} + +sub system_alldatepart { + my ($self) = @_; + return $self->{system_alldatepart}; +} + +sub is_sticky { + my ($self) = @_; + return 0 unless $self->data->{type}; + return $self->data->{type} eq 'sticky'; +} + +sub allowmask { + my ($self) = @_; + return $self->data->{allowmask}; +} + +sub security { + my ($self) = @_; + return $self->data->{security}; +} + +sub mood { + my ($self) = @_; + #warn "TODO: add mood"; + return 0; +} + +sub props { + my ($self) = @_; + return $self->data->{props}; +} + +sub is_future_date { + my ($req) = @_; + my $now = __get_now(); + my $request_time = __get_datatime($req); + + return $request_time ge $now; +} + +sub visible_to { + my ($self, $remote, $opts) = @_; + return $self->journal->equals($remote); +} + +# defined by the entry poster +sub adult_content { + my ($self) = @_; + return $self->prop('adult_content'); +} + +# defined by an admin +sub admin_content_flag { + my ($self) = @_; + return $self->prop('admin_content_flag'); +} + +# uses both poster- and admin-defined props to figure out the adult content level +sub adult_content_calculated { + my ($self) = @_; + + return "explicit" if $self->admin_content_flag eq "explicit_adult"; + return $self->adult_content; +} + +sub prop { + my ( $self, $prop_name ) = @_; + return $self->props->{$prop_name} || ''; +} + +sub url { + my ($self) = @_; + my $journal = $self->journal; + my $url = $journal->journal_base . "/d" . $self->delayedid . ".html"; + return $url; +} + +sub statusvis { + my ($self) = @_; + return $self->prop("statusvis") eq "S" ? "S" : "V"; +} + +sub is_visible { + my ($self) = @_; + return $self->statusvis eq "V" ? 1 : 0; +} + +sub is_suspended { + my ($self) = @_; + return $self->statusvis eq "S" ? 1 : 0; +} + +# same as is_suspended, except that it returns 0 if the given user can see the suspended entry +sub is_suspended_for { + my ( $self, $u ) = @_; + + return 0 unless $self->is_suspended; + return 0 if LJ::check_priv($u, 'canview', 'suspended'); + return 0 if LJ::isu($u) && $u->equals($self->poster); + return 1; +} + +sub should_show_suspend_msg_to { + my ( $self, $u ) = @_; + return $self->is_suspended && !$self->is_suspended_for($u) ? 1 : 0; +} + +sub is_delayed { + return 1; +} + +# no anum for delayed entries +sub correct_anum { + my ($self) = @_; + return 0; +} + +# no ditemid +sub ditemid { + my ($self) = @_; + return 0; +} + +# no jitemid +sub jitemid { + my ($self) = @_; + return 0; +} + +# returns a LJ::Userpic object for this post, or undef +# currently this is for the permalink view, not for the friends view +# context. TODO: add a context option for friends page, and perhaps +# respect $remote's userpic viewing preferences (community shows poster +# vs community's picture) +sub userpic { + my ($self) = @_; + + my $up = $self->poster; + + # try their entry-defined userpic keyword, then their custom + # mood, then their standard mood + my $key = $self->prop('picture_keyword') || + $self->prop('current_mood') || + LJ::mood_name($self->prop('current_moodid')); + + # return the picture from keyword, if defined + my $picid = LJ::get_picid_from_keyword($up, $key); + return LJ::Userpic->new($up, $picid) if $picid; + + # else return poster's default userpic + return $up->userpic; +} + +# for delayed entry always false +sub comments_shown { + my ($self) = @_; + return 0; +} + +sub posting_comments_allowed { + my ($self) = @_; + return 0; +} + +sub everyone_can_comment { + my ($self) = @_; + return 0; +} + +sub registered_can_comment { + my ($self) = @_; + return 0; +} + +sub friends_can_comment { + my ($self) = @_; + return 0; +} + +sub tag_map { + my ($self) = @_; + return {}; +} + +sub subject_html { + my ($self) = @_; + my $subject = $self->subject; + LJ::CleanHTML::clean_subject( \$subject ) if $subject; + return $subject; +} + +sub subject_text { + my ($self) = @_; + my $subject = $self->subject; + LJ::CleanHTML::clean_subject_all( \$subject ) if $subject; + return $subject; +} + +# instance method. returns HTML-cleaned/formatted version of the event +# optional $opt may be: +# undef: loads the opt_preformatted key and uses that for formatting options +# 1: treats entry as preformatted (no breaks applied) +# 0: treats entry as normal (newlines convert to HTML breaks) +# hashref: passed to LJ::CleanHTML::clean_event verbatim +sub event_html +{ + my ($self, $opts) = @_; + + if (! defined $opts) { + $opts = {}; + } elsif (! ref $opts) { + $opts = { preformatted => $opts }; + } + + unless ( exists $opts->{'preformatted'} ) { + $opts->{'preformatted'} = $self->prop('opt_preformatted'); + } + + my $remote = LJ::get_remote(); + my $suspend_msg = $self->should_show_suspend_msg_to($remote) ? 1 : 0; + $opts->{suspend_msg} = $suspend_msg; + $opts->{unsuspend_supportid} = $suspend_msg ? $self->prop("unsuspend_supportid") : 0; + + if($opts->{no_cut_expand}) { + $opts->{expand_cut} = 0; + $opts->{cuturl} = $self->prop('reposted_from') || $self->url . '?page=' . $opts->{page} . '&cut_expand=1'; + } elsif (!$opts->{cuturl}) { + $opts->{expand_cut} = 1; + $opts->{cuturl} = $self->prop('reposted_from') || $self->url; + } + + $opts->{journalid} = $self->journalid; + $opts->{posterid} = $self->posterid; + $opts->{entry_url} = $self->prop('reposted_from') || $self->url; + + my $event = $self->event; + LJ::CleanHTML::clean_event(\$event, $opts); + + #LJ::expand_embedded($self->journal, $self->ditemid, LJ::User->remote, \$event); + return $event; +} + +sub verticals_list { + my ($self) = @_; + + my $verticals_list = $self->prop("verticals_list"); + return () unless $verticals_list; + + my @verticals = split(/\s*,\s*/, $verticals_list); + return @verticals ? @verticals : (); +} + +sub eventtime_mysql { + my ($self) = @_; + return $self->{alldatepart}; +} + +sub logtime_mysql { + my ($self) = @_; + return $self->{system_alldatepart}; +} + +sub verticals_list_for_ad { + my ($self) = @_; + + my @verticals = $self->verticals_list; + my @verticals_for_ad; + if (@verticals) { + foreach my $vertname (@verticals) { + my $vertical = LJ::Vertical->load_by_name($vertname); + next unless $vertical; + + push @verticals_for_ad, $vertical->ad_name; + } + } + + # remove parent verticals if any of their subverticals are in the list + my %vertical_in_list = map { $_ => 1 } @verticals_for_ad; + foreach my $vertname (@verticals_for_ad) { + my $vertical = LJ::Vertical->load_by_name($vertname); + next unless $vertical; + + foreach my $child ($vertical->children) { + if ($vertical_in_list{$child->ad_name}) { + delete $vertical_in_list{$vertical->ad_name}; + } + } + } + @verticals_for_ad = keys %vertical_in_list; + + return @verticals_for_ad ? @verticals_for_ad : (); +} + +sub get_tags { + my ($self) = @_; + my $i = 1; + my $result; + foreach my $tag (@{$self->{taglist}}) { + $result->{$self->delayedid}->{$i} = $tag; + $i++; + } + + return $result; +} + +# raw utf8 text, with no HTML cleaning +sub subject_raw { + my ($self) = @_; + return $self->subject; +} + +sub event_raw { + my ($self) = @_; + return $self->event; +} + +sub is_public { + my ($self) = @_; + return 0; +} + +sub delete { + my ($self) = @_; + __assert( $self->{delayed_id} ); + __assert( $self->{journal} ); + + my $journal = $self->{journal}; + my $delayed_id = $self->{delayed_id}; + + $journal->do("DELETE FROM delayedlog2 " . + "WHERE delayedid = $delayed_id AND " . + "journalid = " . $journal->userid); + + $journal->do("DELETE FROM delayedblob2 " . + "WHERE delayedid = $delayed_id AND " . + "journalid = " . $journal->userid); + + __kill_dayct2_cache($journal); + + $self->{delayed_id} = undef; + $self->{journal} = undef; + $self->{poster} = undef; + $self->{data} = undef; +} + +sub comments_manageable_by { + my ($self, $remote) = @_; + return 0 unless $remote; + + my $u = $self->{journal}; + + return + $remote->userid == $u->userid || + $remote->userid == $self->posterid || + $remote->can_manage($u) || + $remote->can_sweep($u); +} + +sub should_block_robots { + my ($self) = @_; + return 1 if $self->journal->prop('opt_blockrobots'); + return 0 unless LJ::is_enabled("content_flag"); + + my $adult_content = $self->adult_content_calculated; + my $admin_flag = $self->admin_content_flag; + + return 1 if $LJ::CONTENT_FLAGS{$adult_content} && $LJ::CONTENT_FLAGS{$adult_content}->{block_robots}; + return 1 if $LJ::CONTENT_FLAGS{$admin_flag} && $LJ::CONTENT_FLAGS{$admin_flag}->{block_robots}; + return 0; +} + +sub update { + my ($self, $req) = @_; + __assert( $self->{delayed_id} ); + __assert( $self->{journal} ); + __assert( $self->{poster} ); + + $req->{timezone} = $req->{timezone} || $self->data->{timezone}; + + my $journalid = $self->journal->userid; + my $posterid = $self->poster->userid; + my $subject = $req->{subject}; + my $posttime = __get_datatime($req); + my $data_ser = __serialize($self->journal, $req); + my $delayedid = $self->{delayed_id}; + my $dbh = LJ::get_db_writer(); + + my $security = "public"; + my $uselogsec = 0; + if ($req->{'security'} eq "usemask" || $req->{'security'} eq "private") { + $security = $req->{'security'}; + } + if ($req->{'security'} eq "usemask") { + $uselogsec = 1; + } + + my $now = $dbh->selectrow_array("SELECT UNIX_TIMESTAMP()"); + + my $qsecurity = $dbh->quote($security); + my $qallowmask = $req->{'allowmask'}+0; + my $qposttime = $dbh->quote($posttime); + my $utime = $dbh->selectrow_array("SELECT UNIX_TIMESTAMP($qposttime)"); + + my $rlogtime = $LJ::EndOfTime - $now; + my $rposttime = $LJ::EndOfTime - $utime; + $self->{taglist} = __extract_tag_list(\$req->{props}->{taglist}); + + $req->{'event'} =~ s/\r\n/\n/g; # compact new-line endings to more comfort chars count near 65535 limit + + $self->journal->do("UPDATE delayedlog2 SET posterid=$posterid, " . + "subject=?, posttime=$qposttime, " . + "security=$qsecurity, allowmask=$qallowmask, " . + "year=?, month=?, day=?, " . + "rlogtime=$rlogtime, revptime=$rposttime " . + "WHERE journalid=$journalid AND delayedid=$delayedid", + undef, LJ::text_trim($req->{'subject'}, 30, 0), + $req->{year}, $req->{mon}, $req->{day} ); + + $self->journal->do( "UPDATE delayedblob2 SET request_stor=$data_ser" . + "WHERE journalid=$journalid AND delayedid=$delayedid"); + + __kill_dayct2_cache($self->journal); +} + +sub update_tags { + my ($self, $tags) = @_; + $self->props->{taglist} = $tags; + $self->{taglist} = __extract_tag_list(\$self->props->{taglist}); + $self->update($self->data); + return 1; +} + +sub get_log2_row { + my ($self, $opts) = @_; + + my $db = LJ::get_cluster_def_reader($self->journal); + return undef unless $db; + + my $sql = "SELECT posterid, posttime, logtime, security, allowmask " . + "FROM delayedlog2 WHERE journalid=? AND delayedid=?"; + + my $item = $db->selectrow_hashref($sql, undef, $self->journal->userid, $self->delayedid); + return undef unless $item; + $item->{'journalid'} = $self->journal->userid; + $item->{'delayedid'} = $self->delayedid; + + return $item; +} + +sub load_data { + my ($class, $dbcr, $opts) = @_; + __assert($opts->{journalid}); + __assert($opts->{delayed_id}); + __assert($opts->{posterid}); + + my $journalid = $opts->{posterid}; + my $delayedid = $opts->{delayed_id}; + + my $data_ser = $dbcr->selectrow_array( "SELECT request_stor " . + "FROM delayedblob2 " . + "WHERE journalid=$journalid AND " . + "delayedid = $delayedid" ); + + my $self = bless {}, $class; + $self->{journal} = LJ::want_user($opts->{journalid}); + $self->{data} = __deserialize($self->journal, $data_ser); + $self->{poster} = LJ::want_user($opts->{posterid}); + $self->{delayed_id} = $delayedid; + $self->{posttime} = __get_datatime($self->{data}); + + return $self; +} + + +sub get_entry_by_id { + my ($class, $journal, $delayedid, $settings) = @_; + __assert($journal); + __assert($delayedid); + + my $journalid = $journal->userid; + my $dateformat_type = $settings->{'dateformat'} || ''; + my $dbcr = LJ::get_cluster_def_reader($journal) + or die "get cluster for journal failed"; + + my $dateformat = "%a %W %b %M %y %Y %c %m %e %d %D %p %i %l %h %k %H"; + if ($dateformat_type eq "S2") { + $dateformat = "%Y %m %d %H %i %s %w"; # yyyy mm dd hh mm ss day_of_week + } + + my $userid = $settings->{userid} || 0; + $userid = LJ::get_remote()->userid unless $userid; + + my $secwhere = __delayed_entry_secwhere( $journal, + $journal->userid, + $userid ); + + my $opts = $dbcr->selectrow_arrayref("SELECT journalid, delayedid, posterid, " . + "DATE_FORMAT(posttime, \"$dateformat\") AS 'alldatepart', " . + "DATE_FORMAT(logtime, \"$dateformat\") AS 'system_alldatepart', " . + "posttime, logtime " . + "FROM delayedlog2 ". + "WHERE journalid=$journalid AND ". + "delayedid = $delayedid $secwhere"); + + return undef unless $opts; + + my $data_ser = $dbcr->selectrow_array("SELECT request_stor " . + "FROM delayedblob2 ". + "WHERE journalid=$journalid AND ". + "delayedid = $delayedid"); + my $self = bless {}, $class; + $self->{data} = __deserialize($journal, $data_ser); + $self->{journal} = $journal; + $self->{poster} = LJ::want_user($opts->[2]); + $self->{delayed_id} = $delayedid; + $self->{posttime} = __get_datatime($self->{data}); + $self->{alldatepart} = $opts->[3]; + $self->{system_alldatepart} = $opts->[4]; + $self->{taglist} = __extract_tag_list(\$self->{data}->{props}->{taglist}); + + __assert( $self->{poster} ); + __assert( $self->{journal} ); + + return $self; +} + +# returns a hashref: { title => '', description => '', image => $url } +sub extract_metadata { + my ($self) = @_; + my %meta; + + $meta{'title'} = LJ::Text->drop_html( $self->subject_raw ); + + $meta{'description'} = eval { + my $text = $self->event_raw; + $text = LJ::Text->drop_html($text); + $text = LJ::Text->truncate_to_word_with_ellipsis( 'str' => $text, 'bytes' => 300 ); + return $text; + }; + die "cannot get entry description: $@" unless defined $meta{'description'}; + + $meta{'image'} = eval { + my $text = $self->event_raw; + my $images = LJ::html_get_img_urls( \$text, 'exclude_site_imgs' => 1 ); + return $images->[0] if $images && @$images; + + my $userpic = $self->userpic; + return $userpic->url if $userpic; + + my $journal = $self->journal; + my ($userhead_url) = $journal->userhead; + + # for now, LJ::User::userhead may return a relative path, + # so let's fix this + unless ( $userhead_url =~ /^https?:\/\// ) { + $userhead_url = $LJ::IMGPREFIX . '/' . $userhead_url . "?v=3"; + } + return $userhead_url; + }; + die "cannot get entry image: $@" unless defined $meta{'image'}; + + return \%meta; +} + +sub get_entries_by_journal { + my ( $class, $journal, $skip, $elements_to_show, $userid ) = @_; + __assert($journal); + my $journalid = $journal->userid; + + my $dbcr = LJ::get_cluster_def_reader($journal) + or die "get cluster for journal failed"; + + my $sql_limit = ''; + if ($skip || $elements_to_show) { + $sql_limit = "LIMIT $skip, $elements_to_show"; + } + + $userid = LJ::get_remote()->userid unless $userid; + my $secwhere = __delayed_entry_secwhere( $journal, + $journal->userid, + $userid ); + + return $dbcr->selectcol_arrayref("SELECT delayedid " . + "FROM delayedlog2 WHERE journalid=$journalid $secwhere ". + "ORDER BY revptime $sql_limit"); +} + +sub get_daycount_query { + my ($class, $journal) = @_; + my $dbcr = LJ::get_cluster_def_reader($journal); + + my $remote = LJ::get_remote(); + my $secwhere = __delayed_entry_secwhere( $journal, + $journal->userid, + $remote->userid ); + + my $sth = $dbcr->prepare("SELECT year, month, day, COUNT(*) " . + "FROM delayedlog2 WHERE journalid=? $secwhere GROUP BY 1, 2, 3"); + $sth->execute($journal->userid); + return $sth; +} + +sub get_entries_for_day { + my ($class, $journal, $year, $month, $day, $dateformat) = @_; + my $entries = []; + + my $remote = LJ::get_remote(); + my $secwhere = __delayed_entry_secwhere( $journal, + $journal->userid, + $remote->userid ); + + my $dbcr = LJ::get_cluster_def_reader($journal) + or die "get cluster for journal failed"; + + $dbcr = $dbcr->prepare("SELECT l.delayedid, l.posterid, l.day, ". + "DATE_FORMAT(l.posttime, '$dateformat') AS 'alldatepart', ". + "l.security, l.allowmask ". + "FROM delayedlog2 l ". + "WHERE l.journalid=? AND l.year=? AND l.month=? AND day=?". + "$secwhere LIMIT 2000"); + $dbcr->execute($journal->userid, $year, $month, $day); + + my @items; + push @items, $_ while $_ = $dbcr->fetchrow_hashref; + return @items; +} + +sub getevents { + my ( $self, $req, $flags, $err, $res ) = @_; + + return 0 if $req->{itemid}; + $flags->{allow_anonymous} = 1; + return 0 unless LJ::Protocol::authenticate($req, $err, $flags); + + $flags->{'ignorecanuse'} = 1; # later we will check security levels, so allow some access to communities + return 0 unless LJ::Protocol::check_altusage($req, $err, $flags); + + my $u = $flags->{'u'}; + my $uowner = $flags->{'u_owner'} || $u; + + ### shared-journal support + my $userid = ($u ? $u->{'userid'} : 0); + my $ownerid = $flags->{'ownerid'}; + if( $req->{journalid} ){ + $ownerid = $req->{journalid}; + $uowner = LJ::load_userid( $req->{journalid} ); + } + + my $limit = $req->{howmany} || 0; + my $skip = $req->{'skip'} || 0; + if ($skip > 500) { $skip = 500; } + + my $dbcr = LJ::get_cluster_def_reader($uowner) + or die "get cluster for journal failed"; + + my $sql_limit = ''; + if ($skip || $limit) { + $sql_limit = "LIMIT $skip, $limit"; + } + + my $date_limit = ''; + if ($req->{year}) { + $date_limit .= "AND year = " . $req->{year} . " "; + } + + if ($req->{month}) { + $date_limit .= "AND month = " . $req->{month} . " "; + } + + if ($req->{day}) { + $date_limit .= "AND day = " . $req->{day} . " "; + } + + my $secwhere = __delayed_entry_secwhere( $uowner, + $ownerid, + $userid ); + + my $entriesids = $dbcr->selectcol_arrayref("SELECT delayedid " . + "FROM delayedlog2 WHERE journalid=$ownerid $secwhere $date_limit ". + "ORDER BY revptime $sql_limit"); + + my $i = 0; + my @results = []; + foreach my $delayedid (@$entriesids) { + my $entry_obj = LJ::DelayedEntry->get_entry_by_id( $u, + $delayedid, + { userid => $userid } ); + ++$i; + $res->{"events_${i}_itemid"} = 0; + $res->{"events_${i}_delayedid"} = $delayedid; + $res->{"events_${i}_poster"} = $entry_obj->poster; + $res->{"events_${i}_subject"} = $entry_obj->subject; + $res->{"events_${i}_event"} = $entry_obj->event; + $res->{"events_${i}_allowmask"} = $entry_obj->allowmask; + $res->{"events_${i}_security"} = $entry_obj->security; + $res->{"events_${i}_eventtime"} = $entry_obj->posttime; + $res->{"events_${i}_itemid"} = $entry_obj->jitemid; + $res->{"events_${i}_anum"} = $entry_obj->correct_anum; + $res->{"events_${i}_sticky"} = $entry_obj->is_sticky; + } + + $res->{'events_count'} = $i; + $res->{'success'} = 'OK'; + + return $i; +} + +sub get_entries_for_month { + my ($class, $journal, $year, $month, $dateformat) = @_; + my $entries = []; + + my $remote = LJ::get_remote(); + my $secwhere = __delayed_entry_secwhere( $journal, + $journal->userid, + $remote->userid ); + + my $dbcr = LJ::get_cluster_def_reader($journal) + or die "get cluster for journal failed"; + + $dbcr = $dbcr->prepare("SELECT l.delayedid, l.posterid, l.day, ". + "DATE_FORMAT(l.posttime, '$dateformat') AS 'alldatepart', ". + "l.security, l.allowmask ". + "FROM delayedlog2 l ". + "WHERE l.journalid=? AND l.year=? AND l.month=? ". + "$secwhere LIMIT 2000"); + $dbcr->execute($journal->userid, $year, $month); + + my @items; + push @items, $_ while $_ = $dbcr->fetchrow_hashref; + return @items; +} + +sub get_itemid_after2 { return get_itemid_near2(@_, "after"); } +sub get_itemid_before2 { return get_itemid_near2(@_, "before"); } + +sub get_itemid_near2 +{ + my ($self, $after_before) = @_; + my $u = $self->journal; + my $delayedid = $self->delayedid; + + my ($order, $cmp1, $cmp2, $cmp3, $cmp4); + if ($after_before eq "after") { + ($order, $cmp1, $cmp2, $cmp3, $cmp4) = ("DESC", "<=", ">", sub {$a->[0] <=> $b->[0]}, sub {$b->[1] <=> $a->[1]} ); + } elsif ($after_before eq "before") { + ($order, $cmp1, $cmp2, $cmp3, $cmp4) = ("ASC", ">=", "<", sub {$b->[0] <=> $a->[0]}, sub {$a->[1] <=> $b->[1]} ); + } else { + return 0; + } + + my $dbr = LJ::get_cluster_reader($u); + unless ($dbr){ + warn "Can't connect to cluster reader. Cluster: " . $u->clusterid; + return 0; + } + + my $jid = $self->journalid; + my $field = $u->{'journaltype'} eq "P" ? "revptime" : "rlogtime"; + + my $stime = $dbr->selectrow_array( "SELECT $field FROM delayedlog2 WHERE ". + "journalid=$jid AND delayedid=$delayedid"); + return 0 unless $stime; + + my $secwhere = "AND security='public'"; + my $remote = LJ::get_remote(); + + if ($remote) { + if ($remote->userid == $self->journalid) { + $secwhere = ""; # see everything + } elsif ($remote->{'journaltype'} eq 'P' || $remote->{'journaltype'} eq 'I') { + my $gmask = LJ::get_groupmask($u, $remote); + $secwhere = "AND (security='public' OR (security='usemask' AND allowmask & $gmask))" + if $gmask; + } + } + + ## + ## We need a next/prev record in journal before/after a given time + ## Since several records may have the same time (time is rounded to 1 minute), + ## we're ordering them by jitemid. So, the SQL we need is + ## SELECT * FROM log2 + ## WHERE journalid=? AND rlogtime>? AND jitmemid<? + ## ORDER BY rlogtime, jitemid DESC + ## LIMIT 1 + ## Alas, MySQL tries to do filesort for the query. + ## So, we sort by rlogtime only and fetch all (2, 10, 50) records + ## with the same rlogtime (we skip records if rlogtime is different from the first one). + ## If rlogtime of all fetched records is the same, increase the LIMIT and retry. + ## Then we sort them in Perl by jitemid and takes just one. + ## + my $result_ref; + foreach my $limit (2, 10, 50, 100) { + $result_ref = $dbr->selectall_arrayref( "SELECT delayedid, $field FROM delayedlog2 use index (rlogtime,revptime) ". + "WHERE journalid=? AND $field $cmp1 ? AND delayedid <> ? ". + $secwhere. " ". + "ORDER BY $field $order LIMIT $limit", + undef, $jid, $stime, $delayedid + ); + + my %hash_times = (); + map {$hash_times{$_->[1]} = 1} @$result_ref; + + # If we has one the only 'time' in $limit fetched rows, + # may be $limit cuts off our record. Increase the limit and repeat. + if (((scalar keys %hash_times) > 1) || (scalar @$result_ref) < $limit) { + my @result; + + # Remove results with the same time but the jitemid is too high or low + if ($after_before eq "after") { + @result = grep { $_->[1] != $stime || $_->[0] > $delayedid } @$result_ref; + } elsif ($after_before eq "before") { + @result = grep { $_->[1] != $stime || $_->[0] < $delayedid } @$result_ref; + } + + # Sort result by jitemid and get our id from a top. + @result = sort $cmp3 @result; + + # Sort result by revttime + @result = sort $cmp4 @result; + + my $id = $result[0]->[0]; + return 0 unless $id; + return $id; + } + } + return 0; +} + +sub can_delete_delayed_item { + my ($u, $usejournal_u) = @_; + return LJ::can_delete_journal_item($u, $usejournal_u); +} + +sub convert { + my ($self, $clusterid) = @_; + my $req = $self->{data}; + + my $journal = $self->journal; + my $journalid = $journal->userid; + my $poster = $self->poster; + my $posterid = $poster->userid; + + my $dbh = LJ::get_db_writer(); + my $dbcm = LJ::get_cluster_master($journal); + + my $ext = $req->{data_d_ext}; + + my $flags = $ext->{flags}; + my $event = $req->{event}; + my $eventtime = __get_datatime($req); + + my $security = "public"; + my $uselogsec = 0; + if ($req->{'security'} eq "usemask" || $req->{'security'} eq "private") { + $security = $req->{'security'}; + } + if ($req->{'security'} eq "usemask") { + $uselogsec = 1; + } + + my $qsecurity = $dbh->quote($security); + my $qallowmask = $req->{'allowmask'}+0; + my $qeventtime = $dbh->quote($eventtime); + my $now = $dbcm->selectrow_array("SELECT UNIX_TIMESTAMP()"); + my $anum = int(rand(256)); + my $jitemid = LJ::alloc_user_counter($journal, "L"); + my $rlogtime = $LJ::EndOfTime; + + # do processing of embedded polls (doesn't add to database, just + # does validity checking) + my @polls = (); + if (LJ::Poll->contains_new_poll(\$event)) + { + return "Your account type doesn't permit creating polls." + unless (LJ::get_cap($poster, "makepoll") + || ($journal->{'journaltype'} eq "C" + && LJ::get_cap($journal, "makepoll") + && LJ::can_manage_other($poster, $journal))); + + my $error = ""; + @polls = LJ::Poll->new_from_html(\$event, \$error, { + 'journalid' => $journalid, + 'posterid' => $posterid, + }); + return $error if $error; + } + + $req->{subject} = $req->{subject} || ''; + $req->{usejournal} = $req->{usejournal} || ''; + $req->{allowmask} = $req->{allowmask} || ''; + $req->{security} = $req->{security} || ''; + + my $dupsig = Digest::MD5::md5_hex(join('', map { $req->{$_} } + qw(subject event usejournal security allowmask))); + + my $lock_key = "post-$journalid"; + + # release our duplicate lock + my $release = sub { $dbcm->do("SELECT RELEASE_LOCK(?)", undef, $lock_key); }; + + # our own local version of fail that releases our lock first + my $fail = sub { $release->(); return fail(@_); }; + + my $res = {}; + my $res_done = 0; # set true by getlock when post was duplicate, or error getting lock + + my $getlock = sub { + my $r = $dbcm->selectrow_array("SELECT GET_LOCK(?, 2)", undef, $lock_key); + unless ($r) { + $res = undef; # a failure case has an undef result + $res_done = 1; # tell caller to bail out + return { error_message => "can't get lock", delete_entry => 0 } + } + my @parts = split(/:/, $poster->{'dupsig_post'}); + if ($parts[0] eq $dupsig) { + # duplicate! let's make the client think this was just the + # normal first response. + $res->{'itemid'} = $parts[1]; + $res->{'anum'} = $parts[2]; + + my $dup_entry = LJ::Entry->new($journal, jitemid => $res->{'itemid'}, anum => $res->{'anum'}); + $res->{'url'} = $dup_entry->url; + + $res_done = 1; + $release->(); + } + }; + + # bring in LJ::Entry with Class::Autouse + LJ::Entry->can("dostuff"); + LJ::replycount_do($journal, $jitemid, "init"); + + # remove comments and logprops on new entry ... see comment by this sub for clarification + LJ::Protocol::new_entry_cleanup_hack($poster, $jitemid) if $LJ::NEW_ENTRY_CLEANUP_HACK; + my $verb = $LJ::NEW_ENTRY_CLEANUP_HACK ? 'REPLACE' : 'INSERT'; + + my $dberr; + $journal->log2_do(\$dberr, "INSERT INTO log2 (journalid, jitemid, posterid, eventtime, logtime, security, ". + "allowmask, replycount, year, month, day, revttime, rlogtime, anum) ". + "VALUES ($journalid, $jitemid, $posterid, $qeventtime, FROM_UNIXTIME($now), $qsecurity, $qallowmask, ". + "0, $req->{'year'}, $req->{'mon'}, $req->{'day'}, $LJ::EndOfTime-". + "UNIX_TIMESTAMP($qeventtime), $rlogtime, $anum)"); + + return { error_message => $dberr, delete_entry => 0 } if $dberr; + + # post become 'sticky post' + if ( $req->{type} && $req->{type} eq 'sticky' ) { + $journal->set_sticky($jitemid); + } + + LJ::MemCache::incr([$journalid, "log2ct:$journalid"]); + LJ::memcache_kill($journalid, "dayct2"); + __kill_dayct2_cache(); + + # set userprops. + { + my %set_userprop; + + # keep track of itemid/anum for later potential duplicates + $set_userprop{"dupsig_post"} = "$dupsig:$jitemid:$anum"; + + # record the eventtime of the last update (for own journals only) + $set_userprop{"newesteventtime"} = $eventtime if $posterid == $journalid; + + $poster->set_prop(\%set_userprop); + } + + # end duplicate locking section + $release->(); + + my $ditemid = $jitemid * 256 + $anum; + + ### finish embedding stuff now that we have the itemid + { + ### this should NOT return an error, and we're mildly fucked by now + ### if it does (would have to delete the log row up there), so we're + ### not going to check it for now. + + my $error = ""; + foreach my $poll (@polls) { + $poll->save_to_db( + journalid => $journalid, + posterid => $posterid, + ditemid => $ditemid, + error => \$error, + ); + + my $pollid = $poll->pollid; + + $event =~ s/<lj-poll-placeholder>/<lj-poll-$pollid>/; + } + } + #### /embedding + + ### extract links for meme tracking + unless ($req->{'security'} eq "usemask" || + $req->{'security'} eq "private") + { + foreach my $url (LJ::get_urls($event)) { + LJ::record_meme($url, $posterid, $ditemid, $journalid); + } + } + + # record journal's disk usage + my $bytes = length($event) + length($req->{'subject'}); + $journal->dudata_set('L', $jitemid, $bytes); + + $journal->do("$verb INTO logtext2 (journalid, jitemid, subject, event) ". + "VALUES ($journalid, $jitemid, ?, ?)", undef, $req->{'subject'}, + LJ::text_compress($event)); + if ($journal->err) { + my $msg = $journal->errstr; + LJ::delete_entry($journal, $jitemid, undef, $anum); # roll-back + return { error_message => "logsec2:$msg", delete_entry => 0 }; + } + + LJ::MemCache::set( [$journalid, "logtext:$clusterid:$journalid:$jitemid"], + [ $req->{'subject'}, $event ]); + + # keep track of custom security stuff in other table. + if ($uselogsec) { + $journal->do("INSERT INTO logsec2 (journalid, jitemid, allowmask) ". + "VALUES ($journalid, $jitemid, $qallowmask)"); + if ($journal->err) { + my $msg = $journal->errstr; + LJ::delete_entry($journal, $jitemid, undef, $anum); # roll-back + return { error_message => "logsec2:$msg", delete_entry => 0 }; + } + } + + # Entry tags + if ($req->{props} && defined $req->{props}->{taglist}) { + # slightly misnamed, the taglist is/was normally a string, but now can also be an arrayref. + my $taginput = $req->{props}->{taglist}; + + my $logtag_opts = { + remote => $poster, + skipped_tags => [], # do all possible and report impossible + }; + + if (ref $taginput eq 'ARRAY') { + $logtag_opts->{set} = [@$taginput]; + $req->{props}->{taglist} = join(", ", @$taginput); + } else { + $logtag_opts->{set_string} = $taginput; + } + + my $rv = LJ::Tags::update_logtags($journal, $jitemid, $logtag_opts); + push @{$res->{warnings} ||= []}, LJ::Lang::ml('/update.bml.tags.skipped', { 'tags' => join(', ', @{$logtag_opts->{skipped_tags}}), + 'limit' => $journal->get_cap('tags_max') } ) + if @{$logtag_opts->{skipped_tags}}; + } + + ## copyright + if (LJ::is_enabled('default_copyright', $poster)) { + $req->{'props'}->{'copyright'} = $poster->prop('default_copyright') + unless defined $req->{'props'}->{'copyright'}; + $req->{'props'}->{'copyright'} = 'P' # second try + unless defined $req->{'props'}->{'copyright'}; + } else { + delete $req->{'props'}->{'copyright'}; + } + + ## give features + if (LJ::is_enabled('give_features')) { + $req->{'props'}->{'give_features'} = ($req->{'props'}->{'give_features'} eq 'enable') ? 1 : + ($req->{'props'}->{'give_features'} eq 'disable') ? 0 : + 1; # LJSUP-9142: All users should be able to use give button + } + + # meta-data + if (%{$req->{'props'}}) { + my $propset = {}; + foreach my $pname (keys %{$req->{'props'}}) { + next unless $req->{'props'}->{$pname}; + next if $pname eq "revnum" || $pname eq "revtime"; + my $p = LJ::get_prop("log", $pname); + next unless $p; + next unless $req->{'props'}->{$pname}; + $propset->{$pname} = $req->{'props'}->{$pname}; + } + my %logprops; + LJ::set_logprop($journal, $jitemid, $propset, \%logprops) if %$propset; + + # if set_logprop modified props above, we can set the memcache key + # to be the hashref of modified props, since this is a new post + LJ::MemCache::set([$journal->{'userid'}, "logprop:$journal->{'userid'}:$jitemid"], + \%logprops) if %logprops; + } + + $dbh->do("UPDATE userusage SET timeupdate=NOW(), lastitemid=$jitemid ". + "WHERE userid=$journalid") unless $flags->{'notimeupdate'}; + LJ::MemCache::set([$journalid, "tu:$journalid"], pack("N", time()), 30*60); + + # argh, this is all too ugly. need to unify more postpost stuff into async + $poster->invalidate_directory_record; + + # note this post in recentactions table + LJ::note_recent_action($journal, 'post'); + + # if the post was public, and the user has not opted out, try to insert into the random table; + # note we do INSERT INGORE since there will be lots of people posting every second, and that's + # the granularity we use + if ($security eq 'public' && LJ::u_equals($poster, $journal) && ! $poster->prop('latest_optout')) { + $poster->do("INSERT IGNORE INTO random_user_set (posttime, userid) VALUES (UNIX_TIMESTAMP(), ?)", + undef, $poster->{userid}); + } + + my @jobs; # jobs to add into TheSchwartz + + # notify weblogs.com of post if necessary + if (!$LJ::DISABLED{'weblogs_com'} && $poster->{'opt_weblogscom'} + && LJ::get_cap($poster, "weblogscom") && + $security eq "public" && !$req->{'props'}->{'opt_backdated'}) { + push @jobs, TheSchwartz::Job->new_from_array("LJ::Worker::Ping::WeblogsCom", { + 'user' => $poster->{'user'}, + 'title' => $poster->{'journaltitle'} || $poster->{'name'}, + 'url' => LJ::journal_base($poster) . "/", + }); + } + + my $entry = LJ::Entry->new($journal, jitemid => $jitemid, anum => $anum); + + # run local site-specific actions + LJ::run_hooks("postpost", { + 'itemid' => $jitemid, + 'anum' => $anum, + 'journal' => $journal, + 'poster' => $poster, + 'event' => $event, + 'eventtime' => $eventtime, + 'subject' => $req->{'subject'}, + 'security' => $security, + 'allowmask' => $qallowmask, + 'props' => $req->{'props'}, + 'entry' => $entry, + 'jobs' => \@jobs, # for hooks to push jobs onto + 'req' => $req, + 'res' => $res, + }); + + # cluster tracking + LJ::mark_user_active($poster, 'post'); + LJ::mark_user_active($journal, 'post') unless LJ::u_equals($poster, $journal); + + $res->{'itemid'} = $jitemid; # by request of mart + $res->{'anum'} = $anum; + $res->{'url'} = $entry->url; + + push @jobs, LJ::Event::JournalNewEntry->new($entry)->fire_job; + push @jobs, LJ::Event::UserNewEntry->new($entry)->fire_job if (!$LJ::DISABLED{'esn-userevents'} || $LJ::_T_FIRE_USERNEWENTRY); + push @jobs, LJ::EventLogRecord::NewEntry->new($entry)->fire_job; + + # PubSubHubbub Support + LJ::Feed::generate_hubbub_jobs($journal, \@jobs) unless $journal->is_syndicated; + + my $sclient = LJ::theschwartz(); + if ($sclient && @jobs) { + my @handles = $sclient->insert_jobs(@jobs); + # TODO: error on failure? depends on the job I suppose? property of the job? + } + + return { delete_entry => 1 }; +} + +sub __delayed_entry_secwhere { + my ( $uowner, $ownerid, $posterid ) = @_; + + my $poster = LJ::want_user($posterid); + return '' unless $poster->can_manage($uowner); + my $secmask = 0; + if ( $uowner && ($uowner->{'journaltype'} eq "P" || + $uowner->{'journaltype'} eq "I") && $posterid != $ownerid) { + $secmask = LJ::get_groupmask($ownerid, $posterid); + } + + # decide what level of security the remote user can see + # 'getevents' used in small count of places and we will not pass 'viewall' through their call chain + my $secwhere = ""; + if ($posterid == $ownerid) { + # no extra where restrictions... user can see all their own stuff + } elsif ($secmask) { + # can see public or things with them in the mask + # and own posts in non-sensitive communities + if ($LJ::JOURNALS_WITH_PROTECTED_CONTENT{ $uowner->{user} }) { + $secwhere = "AND (security='public' OR (security='usemask' AND allowmask & $secmask != 0))"; + } else { + $secwhere = "AND (security='public' OR (security='usemask' AND allowmask & $secmask != 0) OR posterid=$posterid)"; + } + } else { + # not a friend? only see public. + # and own posts in non-sensitive communities + + if ($LJ::JOURNALS_WITH_PROTECTED_CONTENT{ $uowner->{user} } || !$posterid) { + $secwhere = "AND (security='public')"; + } else{ + $secwhere = "AND (security='public' OR posterid=$posterid)"; + } + } + return $secwhere; +} + +sub __extract_tag_list { + my ($tags) = @_; + __assert($tags); + + return [] unless $$tags; + + my @tags_array = (); + my @pretags = split(/,/, $$tags ); + foreach my $pretag (@pretags) { + my $trimmed = LJ::trim($pretag); + + my $in = grep { $_ eq $trimmed } @tags_array; + if (!$in) { + push @tags_array, $trimmed; + } + } + + $$tags = join(",", @tags_array); + return \@tags_array; +} + +sub __kill_dayct2_cache { + my ($u) = @_; + my $uid = LJ::want_userid($u) or return undef; + + my $memkey = [$uid, "dayct2:$uid:p"]; + LJ::MemCache::delete($memkey); + + $memkey = [$uid, "dayct2:$uid:a"]; + LJ::MemCache::delete($memkey); + + $memkey = [$uid, "dayct2:$uid:g"]; + LJ::MemCache::delete($memkey); +} + +sub __serialize { + my ($journal, $req) = @_; + __assert($journal); + __assert($req); + + my $dbcm = LJ::get_cluster_master($journal); + + return $dbcm->quote(Storable::nfreeze($req)); + #return LJ::JSON->to_json( $data ); +} + +sub __deserialize { + my ($journal, $req) = @_; + __assert($journal); + __assert($req); + + #return LJ::JSON->from_json( $data ); + return Storable::thaw($req); +} + +sub __get_now { + my $dt = DateTime->now->set_time_zone('UTC'); + + if ($dt->is_dst) { + $dt->subtract( hours => 1 ); + } + + # make the proper date format + return sprintf("%04d-%02d-%02d %02d:%02d", $dt->year, + $dt->month, + $dt->day, + $dt->hour, + $dt->minute ); +} + +sub __get_datatime { + my ($req) = @_; + __assert($req); + __assert($req->{timezone}); + + my $dt = DateTime->new( year => $req->{'year'}, + month => $req->{'mon'}, + day => $req->{'day'}, + hour => $req->{'hour'}, + minute => $req->{'min'}, + time_zone => $req->{timezone}, ); + + if ($dt->is_dst) { + $dt->subtract( hours => 1 ); + } + + $dt->set_time_zone( 'UTC' ); + + # make the proper date format + return sprintf("%04d-%02d-%02d %02d:%02d", $dt->year, + $dt->month, + $dt->day, + $dt->hour, + $dt->minute ); +} + +sub __assert { + my ($statement) = @_; + unless ($statement) { + die "assertion failed!"; + } +} + +1; Modified: branches/delayed_entries/cgi-bin/LJ/Entry.pm =================================================================== --- branches/delayed_entries/cgi-bin/LJ/Entry.pm 2011-08-19 10:01:49 UTC (rev 19791) +++ branches/delayed_entries/cgi-bin/LJ/Entry.pm 2011-08-19 10:10:27 UTC (rev 19792) @@ -1463,6 +1463,11 @@ return $self->{jitemid} == $u->get_sticky_entry(); } +sub can_delete_journal_item { + my $class = shift; + return LJ::can_delete_journal_item(@_); +} + package LJ; use Class::Autouse qw ( Modified: branches/delayed_entries/cgi-bin/LJ/S2/RecentPage.pm =================================================================== --- branches/delayed_entries/cgi-bin/LJ/S2/RecentPage.pm 2011-08-19 10:01:49 UTC (rev 19791) +++ branches/delayed_entries/cgi-bin/LJ/S2/RecentPage.pm 2011-08-19 10:10:27 UTC (rev 19792) @@ -1,6 +1,7 @@ use strict; package LJ::S2; +use LJ::DelayedEntry; use LJ::UserApps; sub RecentPage @@ -70,7 +71,22 @@ $viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended'); } - + my $delayed_entries = []; + + + if ($u->equals($remote)) { + if ($u->has_sticky_entry && !$skip) { + $delayed_entries = LJ::DelayedEntry->get_entries_by_journal($u, $skip, $itemshow - 1); + } else { + $delayed_entries = LJ::DelayedEntry->get_entries_by_journal($u, $skip, $itemshow + 1); + } + } + + my $itemshow_usual = $itemshow - scalar(@$delayed_entries); + if ( $itemshow <= scalar(@$delayed_entries) ) { + $itemshow_usual = -1; + } + ## load the itemids my @itemids; my $err; @@ -81,7 +97,7 @@ 'viewsome' => $viewsome, 'userid' => $u->{'userid'}, 'remote' => $remote, - 'itemshow' => $itemshow + 1, + 'itemshow' => $itemshow_usual + 1, 'skip' => $skip, 'tagids' => $opts->{tagids}, 'tagmode' => $opts->{tagmode}, @@ -92,11 +108,17 @@ ? "logtime" : "", 'err' => \$err, 'poster' => $get->{'poster'} || '', - }); + 'show_sticky_on_top' => 1, + }) if ($itemshow_usual >= 0) ; + + my $is_prev_exist = scalar @items + scalar(@$delayed_entries) - $itemshow > 0 ? 1 : 0; + if ($is_prev_exist) { + pop @items; + if ( scalar(@$delayed_entries) > $itemshow ) { + pop @$delayed_entries; + } + } - my $is_prev_exist = scalar @items - $itemshow > 0 ? 1 : 0; - pop @items if $is_prev_exist; - die $err if $err; ### load the log properties @@ -126,7 +148,12 @@ my $tags = LJ::Tags::get_logtagsmulti($idsbyc); my $userlite_journal = UserLite($u); - + my $sticky_appended = 0; + + if (scalar(@$delayed_entries) > 0 && $itemshow_usual < 0) { + __append_delayed( $u, $delayed_entries, $p->{'entries'}); + } + ENTRY: foreach my $item (@items) { @@ -136,6 +163,12 @@ my $ditemid = $itemid * 256 + $item->{'anum'}; my $entry_obj = LJ::Entry->new($u, ditemid => $ditemid); + # append delayed entries + if ( !$entry_obj->is_sticky() && !$sticky_appended) { + __append_delayed( $u, $delayed_entries, $p->{'entries'}); + $sticky_appended = 1; + } + next ENTRY unless $entry_obj->visible_to($remote, {'viewall' => $viewall, 'viewsome' => $viewsome}); $entry_obj->handle_prefetched_props($logprops{$itemid}); @@ -247,10 +280,17 @@ 'userpic' => $userpic, 'permalink_url' => $permalink, }); - + push @{$p->{'entries'}}, $entry; LJ::run_hook('notify_event_displayed', $entry_obj); + } # end huge while loop + + # append delayed entries + if ( !$sticky_appended) { + __append_delayed( $u, $delayed_entries, $p->{'entries'}); + $sticky_appended = 1; + } # mark last entry as closing. $p->{'entries'}->[-1]->{'end_day'} = 1 if @{$p->{'entries'} || []}; @@ -281,7 +321,7 @@ # unless we didn't even load as many as we were expecting on this # page, then there are more (unless there are exactly the number shown # on the page, but who cares about that) - unless (scalar(@items) != $itemshow) { + unless (scalar(@items) + scalar(@$delayed_entries) != $itemshow) { $nav->{'backward_count'} = $itemshow; if ($skip == $maxskip) { my $date_slashes = $lastdate; # "yyyy mm dd"; @@ -304,4 +344,64 @@ return $p; } +sub __append_delayed { + my ( $u, $delayed, $entries) = @_; + + foreach my $delayedid (@$delayed) { + my $delayed_entry = LJ::DelayedEntry->get_entry_by_id( $u, + $delayedid, + { dateformat => 'S2' } ); + my $permalink = $delayed_entry->url; + my $readurl = $permalink; + my $posturl = $permalink; + + my $comments = CommentInfo({ + 'read_url' => $readurl, + 'post_url' => $posturl, + 'count' => 0, + 'maxcomments' => 0, + 'enabled' => $delayed_entry->comments_shown, + 'locked' => !$delayed_entry->posting_comments_allowed, + 'screened' => 0, + 'show_readlink' => 0, + 'show_postlink' => 0, + }); + + my $tags = $delayed_entry->get_tags; + $tags = $tags->{$delayed_entry->delayedid} if $tags; + + my @tags = (); + if ($tags) { + my @keys = keys %$tags; + foreach my $key (@keys) { + push @tags, Tag($delayed_entry->journal, $key => $tags->{$key}); + } + } + + + my $entry = Entry($delayed_entry->journal, { + 'subject' => $delayed_entry->subject, + 'text' => $delayed_entry->event, + 'dateparts' => $delayed_entry->alldatepart, + 'system_dateparts' => $delayed_entry->system_alldatepart, + 'security' => $delayed_entry->security || 0, + 'allowmask' => $delayed_entry->allowmask || 0, + 'journal' => UserLite($delayed_entry->journal), + 'poster' => UserLite($delayed_entry->poster), + 'comments' => $comments, + 'new_day' => 1, + 'end_day' => 0, # if true, set later + 'tags' => $tags, + 'userpic' => $delayed_entry->userpic, + 'permalink_url' => "d$delayedid.html", + 'sticky' => $delayed_entry->is_sticky, + 'delayedid' => $delayed_entry->delayedid, + }); + + push @$entries, $entry; + } +} + + 1; + Modified: branches/delayed_entries/cgi-bin/LJ/Widget/EntryForm.pm =================================================================== --- branches/delayed_entries/cgi-bin/LJ/Widget/EntryForm.pm 2011-08-19 10:01:49 UTC (rev 19791) +++ branches/delayed_entries/cgi-bin/LJ/Widget/EntryForm.pm 2011-08-19 10:10:27 UTC (rev 19792) @@ -722,33 +722,38 @@ my %blocks = ( 'sticky' => sub { + my $journalu = LJ::load_user($opts->{'usejournal'}) || $remote; my $is_checked = sub { + if ($opts->{sticky}) { + return 'checked' + } + if ($opts->{jitemid}) { - my $journalu = LJ::load_user($opts->{'usejournal'}) || $remote; my $sticky_entry = $journalu->get_sticky_entry(); if ( $sticky_entry eq $opts->{jitemid} ) { return 'checked' } } - if ($opts->{delayed_sticky}) { - return 'checked' - } }; - + + my $selected = $is_checked->(); my $sticky_check = LJ::html_check({ 'type' => "check", 'class' => 'sticky_type', 'value' => 'sticky', 'name' => 'type', 'id' => 'sticky_type', - 'selected' => $is_checked->(), + 'selected' => $selected, $opts->{'prop_opt_preformatted'} || $opts->{'event_format'}, 'label' => "", }); - + + my $sticky_exists = $journalu->has_sticky_entry && !$selected; + my $sticky_text = $sticky_exists ? $BML::ML{'entryform.sticky_replace.edit'} : + $BML::ML{'entryform.sticky.edit'}; return qq{$sticky_check <label for='prop_sticky' class='right options'> - $BML::ML{'entryform.sticky.edit'} + $sticky_text </label>}; }, 'tags' => sub { Modified: branches/delayed_entries/cgi-bin/bml/scheme/global.look =================================================================== --- branches/delayed_entries/cgi-bin/bml/scheme/global.look 2011-08-19 10:01:49 UTC (rev 19791) +++ branches/delayed_entries/cgi-bin/bml/scheme/global.look 2011-08-19 10:10:27 UTC (rev 19792) @@ -3,6 +3,8 @@ loginboxstyle=>{Ss}background: url(/img/userinfo.gif) no-repeat; background-color: #fff; background-position: 0px 1px; padding-left: 18px; color: #00C; font-weight: bold; commloginboxstyle=>{Ss}background: url(/img/community.gif) no-repeat; background-color: #fff; background-position: 0px 2px; padding-left: 19px; color: #00C; font-weight: bold; +TYPESTICKY=>{Ss}<img src="<?imgprefix?>/icon_sticky.gif" width=11 height=15 align=absmiddle> +TYPEDELAYED=>{Ss}<img src="<?imgprefix?>/icon_delayed.gif" width=11 height=15 align=absmiddle> SECURITYPRIVATE=>{Ss}<img src="<?imgprefix?>/icon_private.gif" width=16 height=16 align=absmiddle> SECURITYPROTECTED=>{Ss}<img src="<?imgprefix?>/icon_protected.gif" width=14 height=15 align=absmiddle> SECURITYGROUPS=>{Ss}<img src="<?imgprefix?>/icon_groups.gif" width=19 height=16 align=absmiddle> Modified: branches/delayed_entries/cgi-bin/ljprotocol.pl =================================================================== --- branches/delayed_entries/cgi-bin/ljprotocol.pl 2011-08-19 10:01:49 UTC (rev 19791) +++ branches/delayed_entries/cgi-bin/ljprotocol.pl 2011-08-19 10:10:27 UTC (rev 19792) @@ -16,6 +16,7 @@ LJ::Comment LJ::RateLimit LJ::EmbedModule + LJ::DelayedEntry ); use LJ::TimeUtil; @@ -81,6 +82,7 @@ "212" => [ E_PERM, "Message body is too long" ], "213" => [ E_PERM, "Message body is empty" ], "214" => [ E_PERM, "Message looks like spam" ], + "215" => [ E_PERM, "Timezone is not set"], # Access Errors @@ -1958,12 +1960,12 @@ } # are they trying to post back in time? - if ($posterid == $ownerid && $u->{'journaltype'} ne 'Y' && - !$ti... (truncated)