1859 lines
60 KiB
Perl
1859 lines
60 KiB
Perl
# This code was forked from the LiveJournal project owned and operated
|
|
# by Live Journal, Inc. The code has been modified and expanded by
|
|
# Dreamwidth Studios, LLC. These files were originally licensed under
|
|
# the terms of the license supplied by Live Journal, Inc, which can
|
|
# currently be found at:
|
|
#
|
|
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
|
|
#
|
|
# In accordance with the original license, this code and all its
|
|
# modifications are provided under the GNU General Public License.
|
|
# A copy of that license can be found in the LICENSE file included as
|
|
# part of this distribution.
|
|
#
|
|
# LiveJournal comment object.
|
|
#
|
|
# Just framing right now, not much to see here!
|
|
#
|
|
|
|
package LJ::Comment;
|
|
|
|
use strict;
|
|
use Carp qw/ croak /;
|
|
|
|
use LJ::Entry;
|
|
use LJ::HTMLControls;
|
|
use LJ::Talk;
|
|
|
|
=head1 NAME
|
|
|
|
LJ::Comment
|
|
|
|
=head1 CLASS METHODS
|
|
|
|
=cut
|
|
|
|
# internal fields:
|
|
#
|
|
# journalid: journalid where the comment was
|
|
# posted, always present
|
|
# jtalkid: jtalkid identifying this comment
|
|
# within the journal_u, always present
|
|
#
|
|
# nodetype: single-char nodetype identifier, loaded if _loaded_row
|
|
# nodeid: nodeid to which this comment
|
|
# applies (often an entry itemid), loaded if _loaded_row
|
|
#
|
|
# parenttalkid: talkid of parent comment, loaded if _loaded_row
|
|
# posterid: userid of posting user lazily loaded at access
|
|
# datepost_unix: unixtime from the 'datepost' loaded if _loaded_row
|
|
# state: comment state identifier, loaded if _loaded_row
|
|
|
|
# body: text of comment, loaded if _loaded_text
|
|
# body_orig: text of comment w/o transcoding, present if unknown8bit
|
|
|
|
# subject: subject of comment, loaded if _loaded_text
|
|
# subject_orig subject of comment w/o transcoding, present if unknown8bit
|
|
|
|
# props: hashref of props, loaded if _loaded_props
|
|
# childids: arrayref of child ids loaded if _loaded_childids
|
|
|
|
# _loaded_text: loaded talktext2 row
|
|
# _loaded_row: loaded talk2 row
|
|
# _loaded_props: loaded props
|
|
# _loaded_childids: loaded childids
|
|
|
|
my %singletons = (); # journalid->jtalkid->singleton
|
|
|
|
# singletons still to be loaded
|
|
my %unloaded_singletons = ();
|
|
my %unloaded_text_singletons = ();
|
|
my %unloaded_prop_singletons = ();
|
|
|
|
sub reset_singletons {
|
|
%singletons = ();
|
|
%unloaded_singletons = ();
|
|
%unloaded_text_singletons = ();
|
|
%unloaded_prop_singletons = ();
|
|
}
|
|
|
|
# <LJFUNC>
|
|
# name: LJ::Comment::new
|
|
# class: comment
|
|
# des: Gets a comment given journal_u entry and jtalkid.
|
|
# args: uobj, opts?
|
|
# des-uobj: A user id or user object ($u) to load the comment for.
|
|
# des-opts: Hash of optional keypairs.
|
|
# jtalkid => talkid journal itemid (no anum)
|
|
# returns: A new LJ::Comment object. Returns undef on failure.
|
|
# </LJFUNC>
|
|
sub instance {
|
|
croak("wrong number of arguments")
|
|
unless scalar @_ == 4;
|
|
my ( $class, $uuserid, $which, $value ) = @_;
|
|
|
|
my $journalid = LJ::want_userid($uuserid)
|
|
or croak("invalid journalid parameter");
|
|
my $jtalkid = $which eq 'jtalkid' ? $value + 0 : ( $value + 0 >> 8 )
|
|
or croak("need to supply jtalkid or dtalkid");
|
|
|
|
# do we have a singleton for this comment?
|
|
$singletons{$journalid} ||= {};
|
|
return $singletons{$journalid}->{$jtalkid}
|
|
if $singletons{$journalid}->{$jtalkid};
|
|
|
|
# save the singleton if it doesn't exist
|
|
my $self = bless {
|
|
journalid => $journalid,
|
|
jtalkid => $jtalkid,
|
|
}, $class;
|
|
|
|
$unloaded_singletons{ $self->singletonkey } = $self;
|
|
$unloaded_text_singletons{ $self->singletonkey } = $self;
|
|
$unloaded_prop_singletons{ $self->singletonkey } = $self;
|
|
|
|
return $singletons{$journalid}->{$jtalkid} = $self;
|
|
}
|
|
*new = \&instance;
|
|
|
|
# class method. takes a ?thread= or ?replyto= URL
|
|
# to a comment, and returns that comment object
|
|
sub new_from_url {
|
|
my ( $class, $url ) = @_;
|
|
$url =~ s!#.*!!;
|
|
|
|
if ( $url =~ /(.+?)\?(?:thread|replyto)\=(\d+)/ ) {
|
|
my $entry = LJ::Entry->new_from_url($1);
|
|
return undef unless $entry;
|
|
return LJ::Comment->new( $entry->journal, dtalkid => $2 );
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
# <LJFUNC>
|
|
# name: LJ::Comment::create
|
|
# class: comment
|
|
# des: Create a new comment. Add them to DB.
|
|
# args:
|
|
# returns: A new LJ::Comment object. Returns undef on failure.
|
|
# </LJFUNC>
|
|
|
|
sub create {
|
|
my ( $class, %opts ) = @_;
|
|
|
|
my $need_captcha = delete( $opts{need_captcha} ) || 0;
|
|
my $err_ref = delete $opts{err_ref};
|
|
|
|
my $err = sub {
|
|
$$err_ref = {
|
|
code => $_[0],
|
|
msg => $_[1]
|
|
};
|
|
return undef;
|
|
};
|
|
|
|
# %talk_opts emulates parameters received from web form.
|
|
# Fill it with nessesary options.
|
|
my %talk_opts = map { $_ => delete $opts{$_} } qw(nodetype parenttalkid body subject props);
|
|
|
|
# poster and journal should be $u objects
|
|
my $journalu = delete $opts{journal};
|
|
return $err->( "bad_journal", "invalid journal for new comment: $journalu" )
|
|
unless LJ::isu($journalu);
|
|
|
|
my $posteru = delete $opts{poster};
|
|
return $err->( "bad_poster", "invalid poster for new comment: $posteru" )
|
|
unless LJ::isu($posteru);
|
|
|
|
# to prepare the comment, we need an LJ::Entry object, so go get one.
|
|
my $ditemid = delete $opts{ditemid};
|
|
return $err->( "no_entry", "No ditemid provided" ) unless $ditemid;
|
|
my $entry = LJ::Entry->new( $journalu, ditemid => $ditemid );
|
|
|
|
# Strictly parameters check. Do not allow any unused params to be passed in.
|
|
return $err->(
|
|
"bad_args", __PACKAGE__ . "->create: Unsupported params: " . join " " => keys %opts
|
|
) if %opts;
|
|
|
|
# Move props values to the talk_opts hash so it resembles the reply form
|
|
# fields, since that's what prepare_and_validate_comment expects.
|
|
foreach my $key ( keys %{ $talk_opts{props} } ) {
|
|
my $talk_key = "prop_$key";
|
|
|
|
$talk_opts{$talk_key} = delete $talk_opts{props}->{$key}
|
|
if not exists $talk_opts{$talk_key};
|
|
}
|
|
|
|
## init. this handles all the error-checking, as well.
|
|
my @errors = ();
|
|
my $comment = LJ::Talk::Post::prepare_and_validate_comment( \%talk_opts, $posteru, $entry,
|
|
\$need_captcha, \@errors );
|
|
|
|
# Special double-checking for max comments limit error before we return a
|
|
# generic error, because, it's VERY SPECIAL, I guess, and there's a faint
|
|
# possibility some consumer of LJ::Protocol Cares.
|
|
return $err->(
|
|
"too_many_comments", "Sorry, this entry already has the maximum number of comments allowed."
|
|
) if LJ::Talk::Post::over_maxcomments( $journalu, $entry->jitemid );
|
|
|
|
# OK, now bail for generic errors.
|
|
return $err->( "init_comment", join "\n" => @errors )
|
|
unless defined $comment;
|
|
|
|
## insertion
|
|
my ( $ok, $talkid_or_err ) = LJ::Talk::Post::post_comment($comment);
|
|
unless ($ok) {
|
|
return $err->( "post_comment", $talkid_or_err );
|
|
}
|
|
|
|
return LJ::Comment->new( $journalu, jtalkid => $talkid_or_err );
|
|
|
|
}
|
|
|
|
=head1 INSTANCE METHODS
|
|
|
|
=cut
|
|
|
|
sub absorb_row {
|
|
my ( $self, %row ) = @_;
|
|
|
|
$self->{$_} = $row{$_} foreach (qw(nodetype nodeid parenttalkid posterid datepost state));
|
|
$self->{_loaded_row} = 1;
|
|
delete $unloaded_singletons{ $self->singletonkey };
|
|
}
|
|
|
|
sub url {
|
|
my ( $self, $url_args ) = @_;
|
|
|
|
my $dtalkid = $self->dtalkid;
|
|
my $entry = $self->entry;
|
|
my $url = $entry->url;
|
|
|
|
return
|
|
"$url?thread=$dtalkid"
|
|
. ( $url_args ? "&$url_args" : "" )
|
|
. LJ::Talk::comment_anchor($dtalkid);
|
|
}
|
|
*thread_url = \&url;
|
|
|
|
=head2 C<< $self->threadroot_url >>
|
|
URL to the thread root. It would be unnecessarily expensive to look up the thread
|
|
root, since it is only rarely needed. So we set up a redirect then look up the
|
|
thread root only if the user clicks the link.
|
|
=cut
|
|
|
|
sub threadroot_url {
|
|
my ( $self, $url_args ) = @_;
|
|
my $dtalkid = $self->dtalkid;
|
|
my $jitemid = $self->entry->jitemid;
|
|
my $journal = $self->entry->journal->user;
|
|
|
|
return "$LJ::SITEROOT/go?redir_type=threadroot&journal=$journal&talkid=$dtalkid"
|
|
. ( $url_args ? "&$url_args" : "" );
|
|
}
|
|
|
|
sub reply_url {
|
|
my $self = $_[0];
|
|
|
|
my $dtalkid = $self->dtalkid;
|
|
my $entry = $self->entry;
|
|
my $url = $entry->url;
|
|
|
|
return "$url?replyto=$dtalkid";
|
|
}
|
|
|
|
sub parent_url {
|
|
my ( $self, $url_args ) = @_;
|
|
|
|
my $parent = $self->parent;
|
|
|
|
return undef unless $parent;
|
|
return $parent->url($url_args);
|
|
}
|
|
|
|
sub unscreen_url {
|
|
my $self = $_[0];
|
|
|
|
my $dtalkid = $self->dtalkid;
|
|
my $entry = $self->entry;
|
|
my $journal = $entry->journal->{user};
|
|
|
|
return "$LJ::SITEROOT/talkscreen" . "?mode=unscreen&journal=$journal" . "&talkid=$dtalkid";
|
|
}
|
|
|
|
sub delete_url {
|
|
my $self = $_[0];
|
|
|
|
my $dtalkid = $self->dtalkid;
|
|
my $entry = $self->entry;
|
|
my $journal = $entry->journal->{user};
|
|
|
|
return "$LJ::SITEROOT/delcomment" . "?journal=$journal&id=$dtalkid";
|
|
}
|
|
|
|
sub edit_url {
|
|
my $self = $_[0];
|
|
|
|
my $dtalkid = $self->dtalkid;
|
|
my $entry = $self->entry;
|
|
my $url = $entry->url;
|
|
|
|
return "$url?edit=$dtalkid";
|
|
}
|
|
|
|
# return LJ::User of journal comment is in
|
|
sub journal {
|
|
return LJ::load_userid( $_[0]->{journalid} );
|
|
}
|
|
|
|
sub journalid {
|
|
return $_[0]->{journalid};
|
|
}
|
|
|
|
sub singletonkey {
|
|
return $_[0]->{journalid} . "-" . $_[0]->{jtalkid};
|
|
}
|
|
|
|
# return LJ::Entry of entry comment is in, or undef if it's not
|
|
# a nodetype of L
|
|
sub entry {
|
|
my $self = $_[0];
|
|
|
|
return undef unless $self && $self->valid;
|
|
return LJ::Entry->new( $self->journal, jitemid => $self->nodeid );
|
|
}
|
|
|
|
sub jtalkid {
|
|
return $_[0]->{jtalkid};
|
|
}
|
|
|
|
sub dtalkid {
|
|
my $self = $_[0];
|
|
my $entry = $self->entry or return undef;
|
|
return ( $self->jtalkid * 256 ) + $entry->anum;
|
|
}
|
|
|
|
sub nodeid {
|
|
__PACKAGE__->preload_rows();
|
|
return $_[0]->{nodeid};
|
|
}
|
|
|
|
sub nodetype {
|
|
__PACKAGE__->preload_rows();
|
|
return $_[0]->{nodetype};
|
|
}
|
|
|
|
=head2 C<< $self->threadrootid >>
|
|
Gets the id of the topmost comment in the thread this comment is part of.
|
|
If you just want to create a link, do not call this directly. Instead, use
|
|
$self->threadroot_url.
|
|
=cut
|
|
|
|
sub threadrootid {
|
|
|
|
my ($self) = @_;
|
|
|
|
# if this has no parent, then this is the thread root
|
|
return $self->jtalkid unless $self->parenttalkid;
|
|
|
|
# if we have the information already, then just return it
|
|
return $self->{threadrootid} if $self->{threadrootid};
|
|
|
|
my $entry = $self->entry;
|
|
|
|
# if it is in memcache, then use the cached value
|
|
my $jid = $entry->journalid;
|
|
my $memkey = [ $jid, "talkroot:$jid:" . $self->jtalkid ];
|
|
|
|
my $cached_threadrootid = LJ::MemCache::get($memkey);
|
|
if ($cached_threadrootid) {
|
|
$self->{threadrootid} = $cached_threadrootid;
|
|
return $cached_threadrootid;
|
|
}
|
|
|
|
# not cached anywhere; let's look it up
|
|
|
|
# get all comments to post
|
|
my $comments = LJ::Talk::get_talk_data( $entry->journal, 'L', $entry->jitemid ) || {};
|
|
|
|
# see if our comment exists
|
|
return undef unless $comments->{ $self->jtalkid };
|
|
|
|
# walk up the tree
|
|
my $id = $self->jtalkid;
|
|
while ( $comments->{$id} && $comments->{$id}->{parenttalkid} ) {
|
|
|
|
# avoid (the unlikely chance of) an infinite loop
|
|
$id = delete $comments->{$id}->{parenttalkid};
|
|
}
|
|
|
|
# cache the value, for future lookup
|
|
$self->{threadrootid} = $id;
|
|
LJ::MemCache::set( $memkey, $id );
|
|
return $id;
|
|
}
|
|
|
|
sub parenttalkid {
|
|
__PACKAGE__->preload_rows();
|
|
return $_[0]->{parenttalkid};
|
|
}
|
|
|
|
# returns a LJ::Comment object for the parent
|
|
sub parent {
|
|
my $self = $_[0];
|
|
my $ptalkid = $self->parenttalkid or return undef;
|
|
|
|
return LJ::Comment->new( $self->journal, jtalkid => $ptalkid );
|
|
}
|
|
|
|
# returns an array of LJ::Comment objects with parentid == $self->jtalkid
|
|
sub children {
|
|
my $self = $_[0];
|
|
|
|
if ( $self->{_loaded_childids} ) {
|
|
my @children = ();
|
|
my $u = $self->journal;
|
|
if ( $self->{childids} && scalar @{ $self->{childids} } ) {
|
|
my @childids = @{ $self->{childids} };
|
|
foreach my $talkid (@childids) {
|
|
my $child = LJ::Comment->new( $u, jtalkid => $talkid );
|
|
push @children, $child;
|
|
}
|
|
}
|
|
return @children;
|
|
}
|
|
|
|
my $entry = $self->entry;
|
|
return grep { $_->{parenttalkid} == $self->{jtalkid} } $entry->comment_list;
|
|
|
|
# FIXME: It might be a good idea to check to see if the entry object had
|
|
# comments cached above, then fall back to a query to select a list
|
|
# from db or memcache
|
|
}
|
|
|
|
sub has_children {
|
|
return $_[0]->children ? 1 : 0;
|
|
}
|
|
|
|
sub has_nondeleted_children {
|
|
my $nondeleted_children = grep { !$_->is_deleted } $_[0]->children;
|
|
return $nondeleted_children ? 1 : 0;
|
|
}
|
|
|
|
# returns true if entry currently exists. (it's possible for a given
|
|
# $u, to make a fake jitemid and that'd be a valid skeleton LJ::Entry
|
|
# object, even though that jitemid hasn't been created yet, or was
|
|
# previously deleted)
|
|
sub valid {
|
|
my $self = $_[0];
|
|
my $u = $self->journal;
|
|
return 0 unless $u && $u->{clusterid};
|
|
__PACKAGE__->preload_rows();
|
|
return $self->{_loaded_row};
|
|
}
|
|
|
|
# when was this comment left?
|
|
sub unixtime {
|
|
__PACKAGE__->preload_rows();
|
|
return LJ::mysqldate_to_time( $_[0]->{datepost}, 0 );
|
|
}
|
|
|
|
# returns LJ::User object for the poster of this entry, or undef for anonymous
|
|
sub poster {
|
|
return LJ::load_userid( $_[0]->posterid );
|
|
}
|
|
|
|
sub posterid {
|
|
__PACKAGE__->preload_rows();
|
|
return $_[0]->{posterid};
|
|
}
|
|
|
|
sub all_singletons {
|
|
my $self = $_[0];
|
|
my @singletons;
|
|
push @singletons, values %{ $singletons{$_} } foreach keys %singletons;
|
|
return @singletons;
|
|
}
|
|
|
|
# class method:
|
|
sub preload_rows {
|
|
my @to_load = ();
|
|
push @to_load, map { [ $_->journal, $_->jtalkid ] } values %unloaded_singletons;
|
|
|
|
# already loaded?
|
|
return 1 unless @to_load;
|
|
|
|
# args: ([ journalid, jtalkid ], ...)
|
|
my @rows = LJ::Talk::get_talk2_row_multi(@to_load);
|
|
|
|
# make a mapping of journalid-jtalkid => $row
|
|
my %row_map = map { join( "-", $_->{journalid}, $_->{jtalkid} ) => $_ } @rows;
|
|
|
|
foreach my $obj ( values %unloaded_singletons ) {
|
|
my $u = $obj->journal;
|
|
|
|
my $row = $row_map{ join( "-", $u->id, $obj->jtalkid ) };
|
|
next unless $row;
|
|
|
|
# absorb row into the given LJ::Comment object
|
|
$obj->absorb_row(%$row);
|
|
}
|
|
|
|
%unloaded_singletons = ();
|
|
return 1;
|
|
}
|
|
|
|
# returns true if loaded, zero if not.
|
|
# also sets _loaded_text and subject and event.
|
|
sub _load_text {
|
|
my $self = $_[0];
|
|
return 1 if $self->{_loaded_text};
|
|
|
|
my $entry = $self->entry;
|
|
my $entryu = $entry->journal;
|
|
my $entry_uid = $entryu->id;
|
|
|
|
# find singletons which don't already have text loaded
|
|
my @to_load;
|
|
foreach my $c_obj ( values %unloaded_text_singletons ) {
|
|
if ( $c_obj->journalid == $entry_uid ) {
|
|
push @to_load, $c_obj;
|
|
}
|
|
}
|
|
|
|
my $ret = LJ::get_talktext2( $entryu, map { $_->jtalkid } @to_load );
|
|
return 0 unless $ret && ref $ret;
|
|
|
|
# iterate over comment objects we retrieved and set their subject/body/loaded members
|
|
foreach my $c_obj (@to_load) {
|
|
my $tt = $ret->{ $c_obj->jtalkid };
|
|
next unless ( $tt && ref $tt );
|
|
|
|
# raw subject and body
|
|
$c_obj->{subject} = $tt->[0];
|
|
$c_obj->{body} = $tt->[1];
|
|
|
|
if ( $c_obj->prop("unknown8bit") ) {
|
|
|
|
# save the old ones away, so we can get back at them if we really need to
|
|
$c_obj->{subject_orig} = $c_obj->{subject};
|
|
$c_obj->{body_orig} = $c_obj->{body};
|
|
|
|
# FIXME: really convert all the props? what if we binary-pack some in the future?
|
|
LJ::item_toutf8( $c_obj->journal, \$c_obj->{subject}, \$c_obj->{body},
|
|
$c_obj->{props} );
|
|
}
|
|
|
|
$c_obj->{_loaded_text} = 1;
|
|
delete $unloaded_text_singletons{ $self->singletonkey };
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub _set_text {
|
|
my ( $self, %opts ) = @_;
|
|
|
|
my $jtalkid = $self->jtalkid;
|
|
die "can't set text on unsaved comment"
|
|
unless $jtalkid;
|
|
|
|
my %doing = ();
|
|
my %original = ();
|
|
my %compressed = ();
|
|
|
|
foreach my $part (qw(subject body)) {
|
|
next unless exists $opts{$part};
|
|
|
|
$original{$part} = delete $opts{$part};
|
|
die "$part is not utf-8" unless LJ::is_utf8( $original{$part} );
|
|
|
|
$doing{$part}++;
|
|
$compressed{$part} = LJ::text_compress( $original{$part} );
|
|
}
|
|
|
|
croak "must set either body or subject" unless %doing;
|
|
|
|
# if the comment is unknown8bit, then we must be setting both subject and body,
|
|
# else we'll have one side utf-8 and the other side unknown, but no metadata
|
|
# capable of expressing "subject is unknown8bit, but not body".
|
|
if ( $self->prop('unknown8bit') ) {
|
|
die "Can't set text on unknown8bit comments unless both subject and body are specified"
|
|
unless $doing{subject} && $doing{body};
|
|
}
|
|
|
|
my $journalu = $self->journal;
|
|
my $journalid = $self->journalid;
|
|
|
|
# need to set new values in the database
|
|
my $set_sql = join( ", ", map { "$_=?" } grep { $doing{$_} } qw(subject body) );
|
|
my @set_vals = map { $compressed{$_} } grep { $doing{$_} } qw(subject body);
|
|
|
|
# update is okay here because we verified we have a jtalkid, presumably from this table
|
|
# -- compressed versions of the text here
|
|
$journalu->do( "UPDATE talktext2 SET $set_sql WHERE journalid=? AND jtalkid=?",
|
|
undef, @set_vals, $journalid, $jtalkid );
|
|
die $journalu->errstr if $journalu->err;
|
|
|
|
# need to also update memcache
|
|
# -- uncompressed versions here
|
|
my $memkey = join( ":", $journalu->clusterid, $journalid, $jtalkid );
|
|
foreach my $part (qw(subject body)) {
|
|
next unless $doing{$part};
|
|
LJ::MemCache::set( [ $journalid, "talk$part:$memkey" ], $original{$part} );
|
|
}
|
|
|
|
# got this far in setting text, and we know we used to be unknown8bit, except the text
|
|
# we just set was utf8, so clear the unknown8bit flag
|
|
if ( $self->prop('unknown8bit') ) {
|
|
|
|
# set to 0 instead of delete so we can find these records later
|
|
$self->set_prop( 'unknown8bit', '0' );
|
|
|
|
}
|
|
|
|
# if text is already loaded, then we can just set whatever we've modified in $self
|
|
if ( $doing{subject} && $doing{body} ) {
|
|
$self->{$_} = $original{$_} foreach qw(subject body);
|
|
$self->{_loaded_text} = 1;
|
|
}
|
|
else {
|
|
$self->{$_} = undef foreach qw(subject body);
|
|
$self->{_loaded_text} = 0;
|
|
$unloaded_text_singletons{ $self->singletonkey } = $self;
|
|
}
|
|
|
|
# otherwise _loaded_text=0 and we won't do any optimizations
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub set_subject {
|
|
my ( $self, $text ) = @_;
|
|
|
|
return $self->_set_text( subject => $text );
|
|
}
|
|
|
|
sub set_body {
|
|
my ( $self, $text ) = @_;
|
|
|
|
return $self->_set_text( body => $text );
|
|
}
|
|
|
|
sub set_subject_and_body {
|
|
my ( $self, $subject, $body ) = @_;
|
|
|
|
return $self->_set_text( subject => $subject, body => $body );
|
|
}
|
|
|
|
sub prop {
|
|
my ( $self, $prop ) = @_;
|
|
$self->_load_props unless $self->{_loaded_props};
|
|
return $self->{props}{$prop};
|
|
}
|
|
|
|
sub set_prop {
|
|
my ( $self, $prop, $val ) = @_;
|
|
|
|
return $self->set_props( $prop => $val );
|
|
}
|
|
|
|
# allows the caller to pass raw SQL to set a prop (e.g. UNIX_TIMESTAMP())
|
|
# do not use this if setting a value given by the user
|
|
sub set_prop_raw {
|
|
my ( $self, $prop, $val ) = @_;
|
|
|
|
return $self->set_props_raw( $prop => $val );
|
|
}
|
|
|
|
sub delete_prop {
|
|
my ( $self, $prop ) = @_;
|
|
|
|
return $self->set_props( $prop => undef );
|
|
}
|
|
|
|
sub props {
|
|
my ( $self, $prop ) = @_;
|
|
$self->_load_props unless $self->{_loaded_props};
|
|
return $self->{props} || {};
|
|
}
|
|
|
|
# class method: preloads the props on the provided list of Comment objects.
|
|
sub preload_props {
|
|
my ( $class, $journalid, @to_load ) = @_;
|
|
|
|
my $prop_ret = {};
|
|
LJ::load_talk_props2( $journalid, [ map { $_->jtalkid } @to_load ], $prop_ret );
|
|
|
|
# iterate over comment objects to load and fill in their props members
|
|
foreach my $c_obj (@to_load) {
|
|
$c_obj->{props} = $prop_ret->{ $c_obj->jtalkid } || {};
|
|
$c_obj->{_loaded_props} = 1;
|
|
delete $unloaded_prop_singletons{ $c_obj->singletonkey };
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub _load_props {
|
|
my $self = $_[0];
|
|
return 1 if $self->{_loaded_props};
|
|
|
|
my $journalid = $self->journalid;
|
|
|
|
# find singletons which don't already have props loaded
|
|
my @to_load;
|
|
foreach my $c_obj ( values %unloaded_prop_singletons ) {
|
|
if ( $c_obj->journalid == $journalid ) {
|
|
push @to_load, $c_obj;
|
|
}
|
|
}
|
|
|
|
my $prop_ret = {};
|
|
LJ::load_talk_props2( $journalid, [ map { $_->jtalkid } @to_load ], $prop_ret );
|
|
|
|
# iterate over comment objects to load and fill in their props members
|
|
foreach my $c_obj (@to_load) {
|
|
$c_obj->{props} = $prop_ret->{ $c_obj->jtalkid } || {};
|
|
$c_obj->{_loaded_props} = 1;
|
|
delete $unloaded_prop_singletons{ $c_obj->singletonkey };
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub set_props {
|
|
my ( $self, %props ) = @_;
|
|
|
|
# call this so that get_prop() calls below will be cached
|
|
LJ::load_props("talk");
|
|
|
|
my $set_raw = delete $props{_raw} ? 1 : 0;
|
|
|
|
my $journalid = $self->journalid;
|
|
my $journalu = $self->journal;
|
|
my $jtalkid = $self->jtalkid;
|
|
|
|
my @vals = ();
|
|
my @to_del = ();
|
|
my %tprops = ();
|
|
my @prop_vals = ();
|
|
foreach my $key ( keys %props ) {
|
|
my $p = LJ::get_prop( "talk", $key );
|
|
next unless $p;
|
|
|
|
my $val = $props{$key};
|
|
|
|
# build lists for inserts and deletes, also update $self
|
|
if ( defined $val ) {
|
|
if ($set_raw) {
|
|
push @vals, ( $journalid, $jtalkid, $p->{tpropid} );
|
|
push @prop_vals, $val;
|
|
$tprops{ $p->{tpropid} } = $key;
|
|
}
|
|
else {
|
|
push @vals, ( $journalid, $jtalkid, $p->{tpropid}, $val );
|
|
$self->{props}->{$key} = $props{$key};
|
|
}
|
|
}
|
|
else {
|
|
push @to_del, $p->{tpropid};
|
|
delete $self->{props}->{$key};
|
|
}
|
|
}
|
|
|
|
if (@vals) {
|
|
my $bind;
|
|
if ($set_raw) {
|
|
my @binds;
|
|
foreach my $prop_val (@prop_vals) {
|
|
push @binds, "(?,?,?,$prop_val)";
|
|
}
|
|
$bind = join( ",", @binds );
|
|
}
|
|
else {
|
|
$bind = join( ",", map { "(?,?,?,?)" } 1 .. ( @vals / 4 ) );
|
|
}
|
|
$journalu->do(
|
|
"REPLACE INTO talkprop2 (journalid, jtalkid, tpropid, value) " . "VALUES $bind",
|
|
undef, @vals );
|
|
die $journalu->errstr if $journalu->err;
|
|
|
|
# get the raw prop values back out of the database to store on the object
|
|
if ($set_raw) {
|
|
my $bind = join( ",", map { "?" } keys %tprops );
|
|
my $sth = $journalu->prepare(
|
|
"SELECT tpropid, value FROM talkprop2 WHERE journalid = ? AND jtalkid = ? AND tpropid IN ($bind)"
|
|
);
|
|
$sth->execute( $journalid, $jtalkid, keys %tprops );
|
|
|
|
while ( my $row = $sth->fetchrow_hashref ) {
|
|
my $tpropid = $row->{tpropid};
|
|
$self->{props}->{ $tprops{$tpropid} } = $row->{value};
|
|
}
|
|
}
|
|
|
|
if ($LJ::_T_COMMENT_SET_PROPS_INSERT) {
|
|
$LJ::_T_COMMENT_SET_PROPS_INSERT->();
|
|
}
|
|
}
|
|
|
|
if (@to_del) {
|
|
my $bind = join( ",", map { "?" } @to_del );
|
|
$journalu->do(
|
|
"DELETE FROM talkprop2 WHERE journalid=? AND jtalkid=? AND tpropid IN ($bind)",
|
|
undef, $journalid, $jtalkid, @to_del );
|
|
die $journalu->errstr if $journalu->err;
|
|
|
|
if ($LJ::_T_COMMENT_SET_PROPS_DELETE) {
|
|
$LJ::_T_COMMENT_SET_PROPS_DELETE->();
|
|
}
|
|
}
|
|
|
|
if ( @vals || @to_del ) {
|
|
LJ::MemCache::delete( [ $journalid, "talkprop:$journalid:$jtalkid" ] );
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub set_props_raw {
|
|
my ( $self, %props ) = @_;
|
|
|
|
return $self->set_props( %props, _raw => 1 );
|
|
}
|
|
|
|
# raw utf8 text, with no HTML cleaning
|
|
sub subject_raw {
|
|
my $self = $_[0];
|
|
$self->_load_text unless $self->{_loaded_text};
|
|
return $self->{subject};
|
|
}
|
|
|
|
# raw text as user sent us, without transcoding while correcting for unknown8bit
|
|
sub subject_orig {
|
|
my $self = $_[0];
|
|
$self->_load_text unless $self->{_loaded_text};
|
|
return $self->{subject_orig} || $self->{subject};
|
|
}
|
|
|
|
# raw utf8 text, with no HTML cleaning
|
|
sub body_raw {
|
|
my $self = $_[0];
|
|
$self->_load_text unless $self->{_loaded_text};
|
|
|
|
# die if we didn't load any body text
|
|
die "Couldn't load body text" unless $self->{_loaded_text};
|
|
|
|
return $self->{body};
|
|
}
|
|
|
|
# raw text as user sent us, without transcoding while correcting for unknown8bit
|
|
sub body_orig {
|
|
my $self = $_[0];
|
|
$self->_load_text unless $self->{_loaded_text};
|
|
return $self->{body_orig} || $self->{body};
|
|
}
|
|
|
|
# comment body, cleaned
|
|
sub body_html {
|
|
my ( $self, %extra_opts ) = @_;
|
|
|
|
my $opts;
|
|
$opts->{preformatted} = $self->prop('opt_preformatted');
|
|
$opts->{anon_comment} = LJ::Talk::treat_as_anon( $self->poster, $self->journal );
|
|
$opts->{nocss} = $opts->{anon_comment};
|
|
$opts->{editor} = $self->prop('editor');
|
|
$opts->{is_imported} = defined $self->prop('import_source') ? 1 : 0;
|
|
$opts->{datepost} = $self->{datepost}; # for format guessing
|
|
$opts->{journal} = $self->journal->user;
|
|
$opts->{ditemid} = $self->entry->ditemid;
|
|
|
|
my $body = $self->body_raw;
|
|
LJ::CleanHTML::clean_comment( \$body, $opts ) if $body;
|
|
return $body;
|
|
}
|
|
|
|
# comement body, but trimmed to $char_max
|
|
sub body_html_summary {
|
|
my ( $self, $char_max, %opts ) = @_;
|
|
return LJ::html_trim( $self->body_html(%opts), $char_max );
|
|
}
|
|
|
|
# comment body, plaintext
|
|
sub body_text {
|
|
my $self = $_[0];
|
|
my $body = $self->body_html;
|
|
return LJ::strip_html($body);
|
|
}
|
|
|
|
sub subject_html {
|
|
my $self = $_[0];
|
|
$self->_load_text unless $self->{_loaded_text};
|
|
return LJ::ehtml( $self->{subject} );
|
|
}
|
|
|
|
sub subject_text {
|
|
my $self = $_[0];
|
|
my $subject = $self->subject_raw;
|
|
return LJ::ehtml($subject);
|
|
}
|
|
|
|
sub state {
|
|
__PACKAGE__->preload_rows();
|
|
return $_[0]->{state} || '';
|
|
}
|
|
|
|
sub is_active {
|
|
return $_[0]->state eq 'A' ? 1 : 0;
|
|
}
|
|
|
|
sub is_screened {
|
|
return $_[0]->state eq 'S' ? 1 : 0;
|
|
}
|
|
|
|
sub is_deleted {
|
|
return $_[0]->state eq 'D' ? 1 : 0;
|
|
}
|
|
|
|
sub is_frozen {
|
|
return $_[0]->state eq 'F' ? 1 : 0;
|
|
}
|
|
|
|
sub viewable_by_others {
|
|
my ($self) = @_;
|
|
|
|
# Is the comment attached to a visible entry?
|
|
my $remote = LJ::get_remote();
|
|
return 0 unless $self->entry && $self->entry->visible_to($remote);
|
|
|
|
# If the entry is visible, the comment should be generally viewable unless
|
|
# the comment is deleted, screened, or posted by a suspended user.
|
|
return 0 if $self->is_deleted;
|
|
return 0 if $self->is_screened;
|
|
return 0 if $self->poster && $self->poster->is_suspended;
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub visible_to {
|
|
my ( $self, $u ) = @_;
|
|
|
|
return 0 unless LJ::isu($u);
|
|
return 0 unless $self->entry && $self->entry->visible_to($u);
|
|
|
|
my $posted_comment = $self->poster && $u->equals( $self->poster );
|
|
my $posted_entry = $self->entry->poster
|
|
&& $u->equals( $self->entry->poster );
|
|
my $posted_parent =
|
|
$self->parent
|
|
&& $self->parent->poster
|
|
&& $u->equals( $self->parent->poster );
|
|
my $posted_by_admin = $self->poster
|
|
&& $self->poster->can_manage( $self->journal );
|
|
|
|
# screened comment
|
|
return 0 if $self->is_screened && ! # allowed viewers:
|
|
(
|
|
$u->can_manage( $self->journal ) # owns the journal
|
|
|| $posted_comment || $posted_entry # owns the content
|
|
|| ( $posted_parent && $posted_by_admin )
|
|
);
|
|
|
|
# person this is in reply to,
|
|
# as long as this comment was by a moderator
|
|
|
|
# comments from suspended users aren't visible
|
|
return 0 if $self->poster && $self->poster->is_suspended;
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub remote_can_delete {
|
|
my $self = $_[0];
|
|
my $remote = LJ::User->remote;
|
|
return $self->user_can_delete($remote);
|
|
}
|
|
|
|
sub user_can_delete {
|
|
my ( $self, $targetu ) = @_;
|
|
return 0 unless LJ::isu($targetu);
|
|
|
|
my $journalu = $self->journal;
|
|
my $posteru = $self->poster;
|
|
my $poster = $posteru ? $posteru->{user} : undef;
|
|
|
|
return LJ::Talk::can_delete( $targetu, $journalu, $posteru, $poster );
|
|
}
|
|
|
|
sub remote_can_edit {
|
|
my ( $self, $errref ) = @_;
|
|
my $remote = LJ::get_remote();
|
|
return $self->user_can_edit( $remote, $errref );
|
|
}
|
|
|
|
sub user_can_edit {
|
|
my ( $self, $u, $errref ) = @_;
|
|
|
|
return 0 unless $u;
|
|
|
|
$$errref = LJ::Lang::ml('talk.error.cantedit.invalid');
|
|
return 0 unless $self && $self->valid;
|
|
|
|
# comment editing must be enabled and the user must have the cap
|
|
$$errref = LJ::Lang::ml('talk.error.cantedit');
|
|
return 0 unless LJ::is_enabled("edit_comments");
|
|
return 0 unless $u->can_edit_comments;
|
|
|
|
# entry cannot be suspended
|
|
return 0 if $self->entry->is_suspended;
|
|
|
|
# user must be the poster of the comment
|
|
unless ( $u->equals( $self->poster ) ) {
|
|
$$errref = LJ::Lang::ml('talk.error.cantedit.notyours');
|
|
return 0;
|
|
}
|
|
|
|
# user cannot be read-only
|
|
return 0 if $u->is_readonly;
|
|
|
|
my $journal = $self->journal;
|
|
|
|
# journal owner must have commenting enabled
|
|
if ( $journal->prop('opt_showtalklinks') eq "N" ) {
|
|
$$errref = LJ::Lang::ml('talk.error.cantedit.commentingdisabled');
|
|
return 0;
|
|
}
|
|
|
|
# user cannot be banned from commenting
|
|
if ( $journal->has_banned($u) ) {
|
|
$$errref = LJ::Lang::ml('talk.error.cantedit.banned');
|
|
return 0;
|
|
}
|
|
|
|
# user must be a friend if friends-only commenting is on
|
|
if ( $journal->prop('opt_whocanreply') eq "friends" && !$journal->trusts_or_has_member($u) ) {
|
|
$$errref = LJ::Lang::ml('talk.error.cantedit.notfriend');
|
|
return 0;
|
|
}
|
|
|
|
# comment cannot have any replies; deleted comments don't count
|
|
if ( $self->has_nondeleted_children ) {
|
|
$$errref = LJ::Lang::ml('talk.error.cantedit.haschildren');
|
|
return 0;
|
|
}
|
|
|
|
# comment cannot be deleted
|
|
if ( $self->is_deleted ) {
|
|
$$errref = LJ::Lang::ml('talk.error.cantedit.isdeleted');
|
|
return 0;
|
|
}
|
|
|
|
# comment cannot be frozen
|
|
if ( $self->is_frozen ) {
|
|
$$errref = LJ::Lang::ml('talk.error.cantedit.isfrozen');
|
|
return 0;
|
|
}
|
|
|
|
# comment must be visible to the user
|
|
unless ( $self->visible_to($u) ) {
|
|
$$errref = LJ::Lang::ml('talk.error.cantedit.notvisible');
|
|
return 0;
|
|
}
|
|
|
|
$$errref = "";
|
|
return 1;
|
|
}
|
|
|
|
sub mark_as_spam {
|
|
my $self = $_[0];
|
|
LJ::Talk::mark_comment_as_spam( $self->poster, $self->jtalkid );
|
|
}
|
|
|
|
# returns comment action buttons (screen, freeze, delete, etc...)
|
|
sub manage_buttons {
|
|
my $self = $_[0];
|
|
my $dtalkid = $self->dtalkid;
|
|
my $journal = $self->journal;
|
|
my $jargent = "journal=$journal->{'user'}&";
|
|
|
|
my $remote = LJ::get_remote() or return '';
|
|
|
|
my $managebtns = '';
|
|
|
|
return '' unless $self->entry->poster;
|
|
|
|
my $poster = $self->poster ? $self->poster->user : "";
|
|
|
|
# Hack to strip the protocol off the return from LJ::img, so the JS is happy
|
|
# see https://github.com/dreamwidth/dreamwidth/commit/317619ee029f39ecd206cec484e0bfb7fa7c4ef1
|
|
sub no_proto_img { my $var = LJ::img(@_); $var =~ s/^https?://; return $var; }
|
|
|
|
if ( $self->remote_can_edit ) {
|
|
$managebtns .=
|
|
"<a href='"
|
|
. $self->edit_url . "'>"
|
|
. no_proto_img( "editcomment", "", { align => 'absmiddle', hspace => 2 } ) . "</a>";
|
|
}
|
|
|
|
if ( LJ::Talk::can_delete( $remote, $self->journal, $self->entry->poster, $poster ) ) {
|
|
$managebtns .= "<a href='$LJ::SITEROOT/delcomment?${jargent}id=$dtalkid'>"
|
|
. no_proto_img( "btn_del", "", { align => 'absmiddle', hspace => 2 } ) . "</a>";
|
|
}
|
|
|
|
if ( LJ::Talk::can_freeze( $remote, $self->journal, $self->entry->poster, $poster ) ) {
|
|
unless ( $self->is_frozen ) {
|
|
$managebtns .=
|
|
"<a href='$LJ::SITEROOT/talkscreen?mode=freeze&${jargent}talkid=$dtalkid'>"
|
|
. no_proto_img( "btn_freeze", "", { align => 'absmiddle', hspace => 2 } ) . "</a>";
|
|
}
|
|
else {
|
|
$managebtns .=
|
|
"<a href='$LJ::SITEROOT/talkscreen?mode=unfreeze&${jargent}talkid=$dtalkid'>"
|
|
. no_proto_img( "btn_unfreeze", "", { align => 'absmiddle', hspace => 2 } )
|
|
. "</a>";
|
|
}
|
|
}
|
|
|
|
if ( LJ::Talk::can_screen( $remote, $self->journal, $self->entry->poster, $poster ) ) {
|
|
unless ( $self->is_screened ) {
|
|
$managebtns .=
|
|
"<a href='$LJ::SITEROOT/talkscreen?mode=screen&${jargent}talkid=$dtalkid'>"
|
|
. no_proto_img( "btn_scr", "", { align => 'absmiddle', hspace => 2 } ) . "</a>";
|
|
}
|
|
else {
|
|
$managebtns .=
|
|
"<a href='$LJ::SITEROOT/talkscreen?mode=unscreen&${jargent}talkid=$dtalkid'>"
|
|
. no_proto_img( "btn_unscr", "", { align => 'absmiddle', hspace => 2 } ) . "</a>";
|
|
}
|
|
}
|
|
|
|
return $managebtns;
|
|
}
|
|
|
|
# returns info for javascript comment management
|
|
# can be used as a class method if $journal is passed explicitly
|
|
sub info {
|
|
my ( $self, $journal ) = @_;
|
|
my $remote = LJ::get_remote();
|
|
$journal ||= $self->journal or return {};
|
|
|
|
return {
|
|
canAdmin => $remote && $remote->can_manage($journal),
|
|
canSpam => !LJ::sysban_check( 'spamreport', $journal->user ),
|
|
journal => $journal->user,
|
|
remote => $remote ? $remote->user : '',
|
|
};
|
|
}
|
|
|
|
sub thread_has_subscription {
|
|
my ( $comment, $remote, $u ) = @_;
|
|
|
|
my @unknown_tracking_status;
|
|
my $watched = 0;
|
|
|
|
while ( $comment && $comment->valid && $comment->parenttalkid ) {
|
|
|
|
# check cache
|
|
$comment->{_watchedby} ||= {};
|
|
my $thread_watched = $comment->{_watchedby}->{ $u->{userid} };
|
|
|
|
my $had_cached = defined $thread_watched;
|
|
|
|
unless ($had_cached) {
|
|
$thread_watched = $remote->has_subscription(
|
|
event => "JournalNewComment",
|
|
journal => $u,
|
|
arg2 => $comment->parenttalkid,
|
|
require_active => 1,
|
|
);
|
|
}
|
|
|
|
if ($thread_watched) {
|
|
$watched = 1;
|
|
|
|
# we had to go up a couple of levels before we could figure out
|
|
# whether we were watching or not
|
|
# so fix the status of intervening levels
|
|
foreach (@unknown_tracking_status) {
|
|
my $c = LJ::Comment->new( $u, dtalkid => $_ );
|
|
$c->{_watchedby}->{ $u->{userid} } = $thread_watched;
|
|
}
|
|
@unknown_tracking_status = ();
|
|
}
|
|
|
|
# cache in this comment object if it's being watched by this user
|
|
$comment->{_watchedby}->{ $u->{userid} } = $thread_watched;
|
|
|
|
# shortcircuit and stop going up the tree because:
|
|
last if $thread_watched; # current comment is watched
|
|
last if $had_cached; # we've been to this section of the ancestor tree already
|
|
|
|
push @unknown_tracking_status, $comment->dtalkid;
|
|
$comment = $comment->parent;
|
|
}
|
|
|
|
return $watched;
|
|
}
|
|
|
|
sub indent {
|
|
return LJ::Talk::Post::indent(@_);
|
|
}
|
|
|
|
sub blockquote {
|
|
return LJ::Talk::Post::blockquote(@_);
|
|
}
|
|
|
|
# used for comment email notification headers
|
|
sub email_messageid {
|
|
my $self = $_[0];
|
|
return "<" . join( "-", "comment", $self->journal->id, $self->dtalkid ) . "\@$LJ::DOMAIN>";
|
|
}
|
|
|
|
my @_ml_strings_en = (
|
|
'esn.journal_new_comment.subject', # 'Subject:',
|
|
'esn.journal_new_comment.message', # 'Message',
|
|
|
|
'esn.screened', # 'This comment was screened.',
|
|
'esn.you_must_unscreen', # 'You must respond to it or unscreen it before others can see it.',
|
|
'esn.here_you_can', # 'From here, you can:',
|
|
|
|
'esn.reply_at_webpage', # '[[openlink]]Reply[[closelink]] at the webpage',
|
|
'esn.unscreen_comment', # '[[openlink]]Unscreen the comment[[closelink]]',
|
|
'esn.edit_comment', # '[[openlink]]Edit the comment[[closelink]]',
|
|
'esn.delete_comment', # '[[openlink]]Delete the comment[[closelink]]',
|
|
'esn.view_comments', # '[[openlink]]View all comments[[closelink]] to this entry',
|
|
'esn.view_threadroot'
|
|
, # '[[openlink]]Go to the top of the thread[[closelink]] this comment is part of',
|
|
'esn.view_thread', # '[[openlink]]View the thread[[closelink]] beginning with this comment',
|
|
|
|
'esn.if_suport_form', # 'If your mail client supports it, you can also reply here:',
|
|
|
|
'esn.journal_new_comment.anonymous.comment', # 'Their reply was:',
|
|
'esn.journal_new_comment.anonymous.reply_to.anonymous_comment.to_your_post3'
|
|
, # 'Somebody replied to another comment somebody left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.anonymous.reply_to.user_comment.to_your_post3'
|
|
, # 'Somebody replied to another comment [[pwho]] left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.anonymous.reply_to.your_comment.to_post3'
|
|
, # 'Somebody replied to another comment you left in [[openlink]]a [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.anonymous.reply_to.your_comment.to_your_post3'
|
|
, # 'Somebody replied to another comment you left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.anonymous.reply_to.your_post3'
|
|
, # 'Somebody replied to [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]] in which you said:',
|
|
|
|
'esn.journal_new_comment.edit_reason',
|
|
'esn.journal_new_comment.user.comment', # 'Their reply was:',
|
|
'esn.journal_new_comment.user.edit_reply_to.anonymous_comment.to_your_post3'
|
|
, # '[[who]] edited a reply to another comment somebody left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.user.edit_reply_to.user_comment.to_your_post3'
|
|
, # '[[who]] edited a reply to another comment [[pwho]] left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.user.edit_reply_to.your_comment.to_post3'
|
|
, # '[[who]] edited a reply to another comment you left in [[openlink]]a [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.user.edit_reply_to.your_comment.to_your_post3'
|
|
, # '[[who]] edited a reply to another comment you left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.user.edit_reply_to.your_post3'
|
|
, # '[[who]] edited a reply to [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]] in which you said:',
|
|
'esn.journal_new_comment.user.new_comment', # 'Their new reply was:',
|
|
|
|
'esn.journal_new_comment.user.reply_to.anonymous_comment.to_your_post3'
|
|
, # '[[who]] replied to another comment somebody left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.user.reply_to.user_comment.to_your_post3'
|
|
, # '[[who]] replied to another comment [[pwho]] left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.user.reply_to.your_comment.to_post3'
|
|
, # '[[who]] replied to another comment you left in [[openlink]]a [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.user.reply_to.your_comment.to_your_post3'
|
|
, # '[[who]] replied to another comment you left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment they replied to was:',
|
|
'esn.journal_new_comment.user.reply_to.your_post3'
|
|
, # '[[who]] replied to [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]] in which you said:',
|
|
|
|
'esn.journal_new_comment.you.edit_reply_to.anonymous_comment.to_post3'
|
|
, # 'You edited a reply to another comment somebody left in [[openlink]]a [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.edit_reply_to.anonymous_comment.to_your_post3'
|
|
, # 'You edited a reply to another comment somebody left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.edit_reply_to.post3'
|
|
, # 'You edited a reply to [[openlink]]a [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]] in which [[pwho]] said:',
|
|
'esn.journal_new_comment.you.edit_reply_to.user_comment.to_post3'
|
|
, # 'You edited a reply to another comment [[pwho]] left in [[openlink]]a [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.edit_reply_to.user_comment.to_your_post3'
|
|
, # 'You edited a reply to another comment [[pwho]] left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.edit_reply_to.your_comment.to_post3'
|
|
, # 'You edited a reply to another comment you left in [[openlink]]a [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.edit_reply_to.your_comment.to_your_post3'
|
|
, # 'You edited a reply to another comment you left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.edit_reply_to.your_post3'
|
|
, # 'You edited a reply to [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]] in which you said:',
|
|
|
|
'esn.journal_new_comment.you.reply_to.anonymous_comment.to_post3'
|
|
, # 'You replied to another comment somebody left in [[openlink]]a [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.reply_to.anonymous_comment.to_your_post3'
|
|
, # 'You replied to another comment somebody left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.reply_to.post3'
|
|
, # 'You replied to [[openlink]]a [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]] in which [[pwho]] said:',
|
|
'esn.journal_new_comment.you.reply_to.user_comment.to_post3'
|
|
, # 'You replied to another comment [[pwho]] left in [[openlink]]a [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.reply_to.user_comment.to_your_post3'
|
|
, # 'You replied to another comment [[pwho]] left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.reply_to.your_comment.to_post3'
|
|
, # 'You replied to another comment you left in [[openlink]]a [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.reply_to.your_comment.to_your_post3'
|
|
, # 'You replied to another comment you left in [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]]. The comment you replied to was:',
|
|
'esn.journal_new_comment.you.reply_to.your_post3'
|
|
, # 'You replied to [[openlink]]your [[sitenameshort]] post[[postsubject]][[closelink]][[postsecurity]] in which you said:',
|
|
|
|
'esn.journal_new_comment.your.comment', # 'Your reply was:',
|
|
'esn.journal_new_comment.your.new_comment', # 'Your new reply was:',
|
|
);
|
|
|
|
# Implementation for both format_text_mail and format_html_mail.
|
|
sub _format_mail_both {
|
|
my ( $self, $targetu, $is_html ) = @_;
|
|
|
|
my $parent = $self->parent;
|
|
my $entry = $self->entry;
|
|
my $posteru = $self->poster;
|
|
my $edited = $self->is_edited;
|
|
|
|
my $who = ''; # Empty means anonymous
|
|
|
|
my ( $k_who, $k_what, $k_reply_edit );
|
|
if ($posteru) {
|
|
if ( $posteru->{name} eq $posteru->display_username ) {
|
|
if ($is_html) {
|
|
my $profile_url = $posteru->profile_url;
|
|
$who = " <a href=\"$profile_url\">" . $posteru->display_username . "</a>";
|
|
}
|
|
else {
|
|
$who = $posteru->display_username;
|
|
}
|
|
}
|
|
else {
|
|
if ($is_html) {
|
|
my $profile_url = $posteru->profile_url;
|
|
$who =
|
|
LJ::ehtml( $posteru->{name} )
|
|
. " (<a href=\"$profile_url\">"
|
|
. $posteru->display_username . "</a>)";
|
|
}
|
|
else {
|
|
$who = $posteru->{name} . " (" . $posteru->display_username . ")";
|
|
}
|
|
}
|
|
if ( $targetu->equals($posteru) ) {
|
|
if ($edited) {
|
|
|
|
# 'You edit your comment to...';
|
|
$k_who = 'you.edit_reply_to';
|
|
$k_reply_edit = 'your.new_comment';
|
|
}
|
|
else {
|
|
# 'You replied to...'
|
|
$k_who = 'you.reply_to';
|
|
$k_reply_edit = 'your.comment';
|
|
}
|
|
}
|
|
else {
|
|
if ($edited) {
|
|
|
|
# 'LJ-user ' . $posteru->{name} . ' edit reply to...';
|
|
$k_who = 'user.edit_reply_to';
|
|
$k_reply_edit = 'user.new_comment';
|
|
}
|
|
else {
|
|
# 'LJ-user ' . $posteru->{name} . ' replied to...';
|
|
$k_who = 'user.reply_to';
|
|
$k_reply_edit = 'user.comment';
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
# 'Somebody replied to';
|
|
$k_who = 'anonymous.reply_to';
|
|
$k_reply_edit = 'anonymous.comment';
|
|
}
|
|
|
|
# Parent post author. Empty string means 'You'.
|
|
my $parentu = $entry->journal;
|
|
my $pwho = ''; #author of the commented post/comment. If empty - it's you or anonymous
|
|
|
|
if ($is_html) {
|
|
if ( !$parent && !$parentu->equals($targetu) ) {
|
|
|
|
# comment to a post and e-mail is going to be sent to not-AUTHOR of the journal
|
|
my $p_profile_url = $entry->poster->profile_url;
|
|
|
|
# pwho - author of the post
|
|
# If the user's name hasn't been set (it's the same as display_username), then
|
|
# don't display both
|
|
if ( $entry->poster->{name} eq $entry->poster->display_username ) {
|
|
$pwho = "<a href=\"$p_profile_url\">" . $entry->poster->display_username . "</a>";
|
|
}
|
|
else {
|
|
$pwho =
|
|
LJ::ehtml( $entry->poster->{name} )
|
|
. " (<a href=\"$p_profile_url\">"
|
|
. $entry->poster->display_username . "</a>)";
|
|
}
|
|
}
|
|
elsif ($parent) {
|
|
my $threadu = $parent->poster;
|
|
if ( $threadu && !$threadu->equals($targetu) ) {
|
|
my $p_profile_url = $threadu->profile_url;
|
|
if ( $threadu->{name} eq $threadu->display_username ) {
|
|
$pwho = "<a href=\"$p_profile_url\">" . $threadu->display_username . "</a>";
|
|
}
|
|
else {
|
|
$pwho =
|
|
LJ::ehtml( $threadu->{name} )
|
|
. " (<a href=\"$p_profile_url\">"
|
|
. $threadu->display_username . "</a>)";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if ( !$parent && !$parentu->equals($targetu) ) {
|
|
if ( $entry->poster->{name} eq $entry->poster->display_username ) {
|
|
$pwho = $entry->poster->display_username;
|
|
}
|
|
else {
|
|
$pwho = $entry->poster->{name} . " (" . $entry->poster->display_username . ")";
|
|
}
|
|
}
|
|
elsif ($parent) {
|
|
my $threadu = $parent->poster;
|
|
if ( $threadu && !$threadu->equals($targetu) ) {
|
|
if ( $threadu->{name} eq $threadu->display_username ) {
|
|
$pwho = $threadu->display_username;
|
|
}
|
|
else {
|
|
$pwho = $threadu->{name} . " (" . $threadu->display_username . ")";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Parent post security. Only include if post is locked/filtered.
|
|
my $postsecurity = ''; # if empty, the post is public or private
|
|
|
|
if ( $self->entry->security eq 'usemask' ) {
|
|
$postsecurity = ' [locked]';
|
|
}
|
|
|
|
# ESN directed to comment poster
|
|
if ( $targetu->equals( $self->poster ) ) {
|
|
|
|
# ->parent returns undef/0 if parent is an entry.
|
|
if ($parent) {
|
|
if ($pwho) {
|
|
|
|
# '... a comment ' . $pwho . ' left in post.';
|
|
$k_what = 'user_comment';
|
|
}
|
|
else {
|
|
# '... a comment you left in post.';
|
|
if ( $parent->poster ) {
|
|
$k_what = 'your_comment';
|
|
}
|
|
else {
|
|
$k_what = 'anonymous_comment';
|
|
}
|
|
}
|
|
if ( $targetu->equals( $entry->journal ) ) {
|
|
$k_what .= '.to_your_post3';
|
|
}
|
|
else {
|
|
$k_what .= '.to_post3';
|
|
}
|
|
}
|
|
else {
|
|
if ($pwho) {
|
|
$k_what = 'post3';
|
|
}
|
|
else {
|
|
$k_what = 'your_post3';
|
|
}
|
|
}
|
|
|
|
# ESN directed to entry author
|
|
}
|
|
elsif ( $targetu->equals( $entry->journal ) ) {
|
|
if ($parent) {
|
|
if ($pwho) {
|
|
|
|
# '... another comment ' . $pwho . ' left in your post.';
|
|
$k_what = 'user_comment.to_your_post3';
|
|
}
|
|
else {
|
|
if ( $parent->poster ) {
|
|
$k_what = 'your_comment.to_your_post3';
|
|
}
|
|
else {
|
|
# '... another comment you left in your post.';
|
|
$k_what = 'anonymous_comment.to_your_post3';
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$k_what = 'your_post3';
|
|
}
|
|
|
|
# ESN directed to author parent comment or post
|
|
}
|
|
else {
|
|
if ($parent) {
|
|
if ( $parent->poster ) {
|
|
if ($pwho) {
|
|
$k_what = 'user_comment.to_post3';
|
|
}
|
|
else {
|
|
$k_what = 'your_comment.to_post3';
|
|
}
|
|
}
|
|
else {
|
|
# '... another comment you left in your post.';
|
|
$k_what = 'anonymous_comment.to_post3';
|
|
}
|
|
}
|
|
else {
|
|
if ($pwho) {
|
|
$k_what = 'post3';
|
|
}
|
|
else {
|
|
$k_what = 'your_post3';
|
|
}
|
|
}
|
|
}
|
|
|
|
# Precache text lines, using DEFAULT_LANG for $targetu
|
|
my $lang = $LJ::DEFAULT_LANG;
|
|
LJ::Lang::get_text_multi( $lang, undef, \@_ml_strings_en );
|
|
|
|
my $body = '';
|
|
$body = "<head><meta http-equiv=\"Content-Type\" content=\"text/html\" /></head><body>"
|
|
if $is_html;
|
|
|
|
my $vars = {
|
|
who => $who,
|
|
pwho => $pwho,
|
|
sitenameshort => $LJ::SITENAMESHORT,
|
|
postsecurity => $postsecurity
|
|
};
|
|
|
|
# make hyperlinks for post
|
|
my $talkurl = $entry->url;
|
|
if ($is_html) {
|
|
$vars->{openlink} = "<a href=\"$talkurl\">";
|
|
$vars->{closelink} = "</a>";
|
|
}
|
|
else {
|
|
$vars->{openlink} = '';
|
|
$vars->{closelink} = " ( $talkurl )";
|
|
}
|
|
|
|
my $subject = $is_html ? $entry->subject_html : $entry->subject_text;
|
|
$subject = " \"$subject\""
|
|
if ($subject);
|
|
|
|
$vars->{postsubject} = $subject;
|
|
|
|
my $ml_prefix = "esn.journal_new_comment.";
|
|
$k_who = $ml_prefix . $k_who;
|
|
$k_reply_edit = $ml_prefix . $k_reply_edit;
|
|
|
|
my $intro = LJ::Lang::get_text( $lang, $k_who . '.' . $k_what, undef, $vars );
|
|
|
|
if ($is_html) {
|
|
my $pichtml;
|
|
if ($posteru) {
|
|
my ( $pic, $pic_kw ) = $self->userpic;
|
|
|
|
if ( $pic && $pic->load_row ) {
|
|
$pichtml =
|
|
"<img src=\"$LJ::USERPIC_ROOT/$pic->{picid}/$pic->{userid}\" align='absmiddle' "
|
|
. "width='$pic->{width}' height='$pic->{height}' "
|
|
. "hspace='1' vspace='2' alt='"
|
|
. $pic->alttext($pic_kw) . "' /> ";
|
|
}
|
|
}
|
|
|
|
if ($pichtml) {
|
|
$body .=
|
|
"<table summary=''><tr valign='top'><td>$pichtml</td><td width='100%'>$intro</td></tr></table>\n";
|
|
}
|
|
else {
|
|
$body .=
|
|
"<table summary=''><tr valign='top'><td width='100%'>$intro</td></tr></table>\n";
|
|
}
|
|
|
|
$body .= blockquote( $parent ? $parent->body_html : $entry->event_html );
|
|
}
|
|
else {
|
|
$body .= $intro . "\n\n" . indent( $parent ? $parent->body_raw : $entry->event_raw, ">" );
|
|
}
|
|
|
|
# reason for editing, if applicable
|
|
if ($edited) {
|
|
my $reason = $self->edit_reason;
|
|
if ($is_html) {
|
|
$body .= "<br />"
|
|
. LJ::Lang::get_text(
|
|
$lang, "esn.journal_new_comment.edit_reason",
|
|
undef, { reason => LJ::ehtml($reason) }
|
|
)
|
|
. "<br />"
|
|
if $reason;
|
|
}
|
|
else {
|
|
$body .= "\n\n"
|
|
. LJ::Lang::get_text( $lang, "esn.journal_new_comment.edit_reason",
|
|
undef, { reason => $reason } )
|
|
if $reason;
|
|
}
|
|
}
|
|
|
|
$body .= "\n\n" . LJ::Lang::get_text( $lang, $k_reply_edit, undef, $vars ) . "\n\n";
|
|
|
|
if ($is_html) {
|
|
my $subjecticon = LJ::Talk::print_subjecticon_by_id( $self->prop('subjecticon') );
|
|
|
|
my $heading;
|
|
if ( $self->subject_raw ) {
|
|
$heading = "<b>"
|
|
. LJ::Lang::get_text( $lang, $ml_prefix . 'subject', undef ) . "</b> "
|
|
. $self->subject_html;
|
|
}
|
|
$heading .= $subjecticon;
|
|
$heading .= "<br />" if $heading;
|
|
|
|
# this needs to be one string so blockquote handles it properly.
|
|
|
|
if ( $self->admin_post ) {
|
|
$body .= '<br/>'
|
|
. LJ::Lang::get_text(
|
|
$lang, "esn.journal_new_entry.admin_post",
|
|
undef, { img => LJ::img('admin-post') }
|
|
) . '<br/>';
|
|
}
|
|
|
|
$body .= blockquote( "$heading" . $self->body_html );
|
|
|
|
$body .= "<br />";
|
|
}
|
|
else {
|
|
if ( my $subj = $self->subject_raw ) {
|
|
$body .= Text::Wrap::wrap(
|
|
" " . LJ::Lang::get_text( $lang, $ml_prefix . 'subject', undef ) . " ",
|
|
"", $subj )
|
|
. "\n\n";
|
|
}
|
|
|
|
if ( $self->admin_post ) {
|
|
$body .= LJ::Lang::get_text( $lang, "esn.journal_new_entry.admin_post.text" ) . "\n\n";
|
|
}
|
|
|
|
$body .= indent( $self->body_raw ) . "\n\n";
|
|
|
|
# Don't wrap options, only text.
|
|
$body = Text::Wrap::wrap( "", "", $body ) . "\n";
|
|
}
|
|
|
|
my $can_unscreen = $self->is_screened
|
|
&& LJ::Talk::can_unscreen( $targetu, $entry->journal, $entry->poster,
|
|
$posteru ? $posteru->{user} : undef );
|
|
|
|
if ( $self->is_screened ) {
|
|
$body .= LJ::Lang::get_text( $lang, 'esn.screened', undef ) . " ";
|
|
$body .= LJ::Lang::get_text( $lang, 'esn.you_must_unscreen', undef )
|
|
if $can_unscreen;
|
|
$body .= "\n";
|
|
}
|
|
|
|
my $commentsurl = $talkurl . "#comments";
|
|
|
|
$body .= LJ::Lang::get_text( $lang, 'esn.here_you_can', undef, $vars );
|
|
$body .= LJ::Event::format_options(
|
|
undef, $is_html, $lang, $vars,
|
|
{
|
|
'esn.reply_at_webpage' => [ 1, $self->reply_url ],
|
|
'esn.unscreen_comment' => [ $can_unscreen ? 2 : 0, $self->unscreen_url ],
|
|
'esn.edit_comment' => [ $self->user_can_edit($targetu) ? 3 : 0, $self->edit_url ],
|
|
'esn.delete_comment' => [ $self->user_can_delete($targetu) ? 4 : 0, $self->delete_url ],
|
|
'esn.view_comments' => [ 5, $commentsurl ],
|
|
'esn.view_threadroot' => [ $self->parenttalkid != 0 ? 6 : 0, $self->threadroot_url ],
|
|
'esn.view_thread' => [ 7, $self->thread_url ],
|
|
}
|
|
);
|
|
|
|
my $open_link = "";
|
|
my $close_link = "";
|
|
my $reset_link = "$LJ::SITEROOT/manage/emailpost";
|
|
if ($is_html) {
|
|
$open_link = qq{<a href="$reset_link">};
|
|
$close_link = q{</a>};
|
|
}
|
|
else {
|
|
$close_link = " ($reset_link)";
|
|
}
|
|
$body .= "\n"
|
|
. LJ::Lang::get_text( $lang, 'esn.reply_to_email2', undef,
|
|
{ openlink => $open_link, closelink => $close_link } )
|
|
. "\n";
|
|
|
|
$body .= "<br></body>\n" if $is_html;
|
|
|
|
return $body;
|
|
}
|
|
|
|
sub format_text_mail {
|
|
my ( $self, $targetu ) = @_;
|
|
croak "invalid targetu passed to format_text_mail"
|
|
unless LJ::isu($targetu);
|
|
|
|
return _format_mail_both( $self, $targetu, 0 );
|
|
}
|
|
|
|
sub format_html_mail {
|
|
my ( $self, $targetu ) = @_;
|
|
croak "invalid targetu passed to format_html_mail"
|
|
unless LJ::isu($targetu);
|
|
|
|
return _format_mail_both( $self, $targetu, 1 );
|
|
}
|
|
|
|
sub delete {
|
|
my $self = $_[0];
|
|
|
|
return LJ::Talk::delete_comment(
|
|
$self->journal,
|
|
$self->nodeid, # jitemid
|
|
$self->jtalkid,
|
|
$self->state
|
|
);
|
|
}
|
|
|
|
sub delete_thread {
|
|
my $self = $_[0];
|
|
|
|
return LJ::Talk::delete_thread(
|
|
$self->journal,
|
|
$self->nodeid, # jitemid
|
|
$self->jtalkid
|
|
);
|
|
}
|
|
|
|
=head2 C<< $cmt->userpic >>
|
|
|
|
Returns a LJ::Userpic object for the poster of the comment, or undef.
|
|
|
|
If called in a list context, returns ( LJ::Userpic object, keyword )
|
|
|
|
=cut
|
|
|
|
sub userpic {
|
|
my $self = $_[0];
|
|
|
|
my $up = $self->poster;
|
|
return unless $up;
|
|
|
|
# return the picture from keyword, if defined
|
|
# else return poster's default userpic
|
|
my $kw = $_[0]->userpic_kw;
|
|
my $pic = LJ::Userpic->new_from_keyword( $up, $kw ) || $up->userpic;
|
|
|
|
return wantarray ? ( $pic, $kw ) : $pic;
|
|
}
|
|
|
|
=head2 C<< $cmt->userpic_kw >>
|
|
|
|
Returns the userpic keyword used on this comment, or undef.
|
|
|
|
=cut
|
|
|
|
sub userpic_kw {
|
|
my $self = $_[0];
|
|
|
|
my $up = $self->poster;
|
|
return unless $up;
|
|
|
|
if ( $up->userpic_have_mapid ) {
|
|
my $mapid = $self->prop('picture_mapid');
|
|
|
|
return $up->get_keyword_from_mapid($mapid) if $mapid;
|
|
}
|
|
else {
|
|
return $self->prop('picture_keyword');
|
|
}
|
|
}
|
|
|
|
=head2 C<< $cmt->admin_post
|
|
|
|
Returns true if this comment is an official administrator comment.
|
|
|
|
=cut
|
|
|
|
sub admin_post {
|
|
my $self = $_[0];
|
|
|
|
return 0 unless $self->journal->is_community;
|
|
return 0
|
|
unless $self->poster && $self->poster->can_manage( $self->journal );
|
|
|
|
if ( exists $_[1] ) {
|
|
$_[0]->set_prop( 'admin_post', $_[1] ? 1 : 0 );
|
|
}
|
|
else {
|
|
return $_[0]->prop('admin_post') ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
sub poster_ip {
|
|
return $_[0]->prop("poster_ip");
|
|
}
|
|
|
|
# sets the new poster IP and returns the value that was set
|
|
sub set_poster_ip {
|
|
my $self = $_[0];
|
|
|
|
return "" unless LJ::is_web_context();
|
|
|
|
my $current_ip = $self->poster_ip;
|
|
|
|
my $new_ip = BML::get_remote_ip();
|
|
my $forwarded = BML::get_client_header('X-Forwarded-For');
|
|
$new_ip = "$forwarded, via $new_ip" if $forwarded && $forwarded ne $new_ip;
|
|
|
|
if ( !$current_ip || $new_ip eq $current_ip ) {
|
|
$self->set_prop( poster_ip => $new_ip );
|
|
return $new_ip;
|
|
}
|
|
|
|
if ( $current_ip =~ /\(originally ([\w\.]+)\)/ ) {
|
|
if ( $new_ip eq $1 ) {
|
|
$self->set_prop( poster_ip => $new_ip );
|
|
return $new_ip;
|
|
}
|
|
|
|
$new_ip = "$new_ip (originally $1)";
|
|
}
|
|
else {
|
|
$new_ip = "$new_ip (originally $current_ip)";
|
|
}
|
|
|
|
$self->set_prop( poster_ip => $new_ip );
|
|
return $new_ip;
|
|
}
|
|
|
|
sub edit_reason {
|
|
return $_[0]->prop("edit_reason");
|
|
}
|
|
|
|
sub edit_time {
|
|
return $_[0]->prop("edit_time");
|
|
}
|
|
|
|
sub is_edited {
|
|
return $_[0]->edit_time ? 1 : 0;
|
|
}
|
|
|
|
1;
|