905 lines
31 KiB
Perl
905 lines
31 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.
|
||
|
|
|
||
|
|
package LJ::User;
|
||
|
|
use strict;
|
||
|
|
no warnings 'uninitialized';
|
||
|
|
|
||
|
|
use Carp;
|
||
|
|
use DW::SiteScheme;
|
||
|
|
use DW::Template;
|
||
|
|
|
||
|
|
use LJ::S2;
|
||
|
|
use LJ::S2Theme;
|
||
|
|
use LJ::Customize;
|
||
|
|
|
||
|
|
########################################################################
|
||
|
|
### 24. Styles and S2-Related Functions
|
||
|
|
|
||
|
|
=head2 Styles and S2-Related Functions
|
||
|
|
=cut
|
||
|
|
|
||
|
|
sub display_journal_deleted {
|
||
|
|
my ( $u, $remote, %opts ) = @_;
|
||
|
|
return undef unless LJ::isu($u);
|
||
|
|
|
||
|
|
my $r = DW::Request->get;
|
||
|
|
$r->status(404);
|
||
|
|
|
||
|
|
my $extra = {};
|
||
|
|
if ( $opts{bml} ) {
|
||
|
|
$extra->{scope} = 'bml';
|
||
|
|
$extra->{scope_data} = $opts{bml};
|
||
|
|
}
|
||
|
|
elsif ( $opts{journal_opts} ) {
|
||
|
|
$extra->{scope} = 'journal';
|
||
|
|
$extra->{scope_data} = $opts{journal_opts};
|
||
|
|
}
|
||
|
|
|
||
|
|
#get information on who deleted the account.
|
||
|
|
my $deleter_name_html;
|
||
|
|
if ( $u->is_community ) {
|
||
|
|
my $userid = $u->userid;
|
||
|
|
my $logtime = $u->statusvisdate_unix;
|
||
|
|
my $dbcr = LJ::get_cluster_reader($u);
|
||
|
|
my ($deleter_id) = $dbcr->selectrow_array(
|
||
|
|
"SELECT remoteid FROM userlog" . " WHERE userid=? AND logtime=? LIMIT 1",
|
||
|
|
undef, $userid, $logtime );
|
||
|
|
my $deleter_name = LJ::get_username($deleter_id);
|
||
|
|
$deleter_name_html = $deleter_name ? LJ::ljuser($deleter_name) : 'Unknown';
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
#If this isn't a community, it can only have been deleted by the
|
||
|
|
# journal owner.
|
||
|
|
$deleter_name_html = LJ::ljuser($u);
|
||
|
|
}
|
||
|
|
|
||
|
|
#Information to pass to the "deleted account" template
|
||
|
|
my $data = {
|
||
|
|
reason => $u->prop('delete_reason'),
|
||
|
|
u => $u,
|
||
|
|
|
||
|
|
#Showing an earliest purge date of 29 days after deletion, not 30,
|
||
|
|
# to be safe with time zones.
|
||
|
|
purge_date => LJ::mysql_date( $u->statusvisdate_unix + ( 29 * 24 * 3600 ), 0 ),
|
||
|
|
|
||
|
|
deleter_name_html => $deleter_name_html,
|
||
|
|
u_name_html => LJ::ljuser($u),
|
||
|
|
|
||
|
|
is_comm => $u->is_community,
|
||
|
|
is_protected => LJ::User->is_protected_username( $u->user ),
|
||
|
|
};
|
||
|
|
|
||
|
|
if ($remote) {
|
||
|
|
$data = {
|
||
|
|
%$data,
|
||
|
|
|
||
|
|
logged_in => 1,
|
||
|
|
|
||
|
|
#booleans for comms
|
||
|
|
is_admin => $u->is_community
|
||
|
|
&& $remote->can_manage($u),
|
||
|
|
is_sole_admin => $u->is_community
|
||
|
|
&& $remote->can_manage($u)
|
||
|
|
&& scalar( $u->maintainer_userids ) == 1,
|
||
|
|
is_member_or_watcher => $u->is_community
|
||
|
|
&& ( $remote->member_of($u) || $remote->watches($u) ),
|
||
|
|
|
||
|
|
#booleans for personal journals
|
||
|
|
is_remote => $u->equals($remote),
|
||
|
|
has_relationship => $remote->watches($u) || $remote->trusts($u),
|
||
|
|
};
|
||
|
|
|
||
|
|
#construct relationship description & link
|
||
|
|
my $relationship_ml;
|
||
|
|
my @relationship_links;
|
||
|
|
if ( $u->is_community
|
||
|
|
&& !( $remote->can_manage($u) && scalar( $u->maintainer_userids ) == 1 ) )
|
||
|
|
{
|
||
|
|
#don't offer the last admin of a deleted community a link to leave it
|
||
|
|
my $watching = $remote->watches($u);
|
||
|
|
my $memberof = $remote->member_of($u);
|
||
|
|
|
||
|
|
if ( $watching && $memberof ) {
|
||
|
|
$relationship_ml = 'web.controlstrip.status.memberwatcher';
|
||
|
|
@relationship_links = (
|
||
|
|
{
|
||
|
|
ml => 'web.controlstrip.links.leavecomm',
|
||
|
|
url => "$LJ::SITEROOT/circle/$u->{user}/edit"
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
elsif ($watching) {
|
||
|
|
$relationship_ml = 'web.controlstrip.status.watcher';
|
||
|
|
@relationship_links = (
|
||
|
|
{
|
||
|
|
ml => 'web.controlstrip.links.removecomm',
|
||
|
|
url => "$LJ::SITEROOT/circle/$u->{user}/edit"
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
elsif ($memberof) {
|
||
|
|
$relationship_ml = 'web.controlstrip.status.member';
|
||
|
|
@relationship_links = (
|
||
|
|
{
|
||
|
|
ml => 'web.controlstrip.links.leavecomm',
|
||
|
|
url => "$LJ::SITEROOT/circle/$u->{user}/edit"
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( !$u->is_community && !$remote->equals($u) ) {
|
||
|
|
|
||
|
|
#Check that it isn't the deleted account's owner, otherwise we'd
|
||
|
|
#tell them that they had granted access to themselves!
|
||
|
|
my $trusts = $remote->trusts($u);
|
||
|
|
my $watches = $remote->watches($u);
|
||
|
|
|
||
|
|
if ( $trusts && $watches ) {
|
||
|
|
$relationship_ml = 'web.controlstrip.status.trust_watch';
|
||
|
|
@relationship_links = (
|
||
|
|
{
|
||
|
|
ml => 'web.controlstrip.links.modifycircle',
|
||
|
|
url => "$LJ::SITEROOT/circle/$u->{user}/edit"
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
elsif ($trusts) {
|
||
|
|
$relationship_ml = 'web.controlstrip.status.trusted';
|
||
|
|
@relationship_links = (
|
||
|
|
{
|
||
|
|
ml => 'web.controlstrip.links.modifycircle',
|
||
|
|
url => "$LJ::SITEROOT/circle/$u->{user}/edit"
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
elsif ($watches) {
|
||
|
|
$relationship_ml = 'web.controlstrip.status.watched';
|
||
|
|
@relationship_links = (
|
||
|
|
{
|
||
|
|
ml => 'web.controlstrip.links.modifycircle',
|
||
|
|
url => "$LJ::SITEROOT/circle/$u->{user}/edit"
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$data->{relationship_ml} = $relationship_ml if $relationship_ml;
|
||
|
|
$data->{relationship_links} = \@relationship_links if @relationship_links;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
return DW::Template->render_template_misc( "journal/deleted.tt", $data, $extra );
|
||
|
|
}
|
||
|
|
|
||
|
|
# returns undef on error, or otherwise arrayref of arrayrefs,
|
||
|
|
# each of format [ year, month, day, count ] for all days with
|
||
|
|
# non-zero count. examples:
|
||
|
|
# [ [ 2003, 6, 5, 3 ], [ 2003, 6, 8, 4 ], ... ]
|
||
|
|
#
|
||
|
|
sub get_daycounts {
|
||
|
|
my ( $u, $remote, $not_memcache ) = @_;
|
||
|
|
return undef unless LJ::isu($u);
|
||
|
|
my $uid = $u->id;
|
||
|
|
|
||
|
|
my $memkind = 'p'; # public only, changed below
|
||
|
|
my $secwhere = "AND security='public'";
|
||
|
|
my $viewall = 0;
|
||
|
|
|
||
|
|
if ( LJ::isu($remote) ) {
|
||
|
|
|
||
|
|
# do they have the viewall priv?
|
||
|
|
my $r = DW::Request->get;
|
||
|
|
my %getargs = %{ $r->get_args };
|
||
|
|
if ( defined $getargs{'viewall'} and $getargs{'viewall'} eq '1' ) {
|
||
|
|
$viewall = $remote->has_priv( 'canview', '*' );
|
||
|
|
LJ::statushistory_add( $u->userid, $remote->userid, "viewall", "archive" ) if $viewall;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $viewall || $remote->can_manage($u) ) {
|
||
|
|
$secwhere = ""; # see everything
|
||
|
|
$memkind = 'a'; # all
|
||
|
|
}
|
||
|
|
elsif ( $remote->is_individual ) {
|
||
|
|
my $gmask = $u->is_community ? $remote->member_of($u) : $u->trustmask($remote);
|
||
|
|
if ($gmask) {
|
||
|
|
$secwhere =
|
||
|
|
"AND (security='public' OR (security='usemask' AND allowmask & $gmask))";
|
||
|
|
$memkind = 'g' . $gmask; # friends case: allowmask == gmask == 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $memkey = [ $uid, "dayct2:$uid:$memkind" ];
|
||
|
|
unless ($not_memcache) {
|
||
|
|
my $list = LJ::MemCache::get($memkey);
|
||
|
|
if ($list) {
|
||
|
|
|
||
|
|
# this was an old version of the stored memcache value
|
||
|
|
# where the first argument was the list creation time
|
||
|
|
# so throw away the first argument
|
||
|
|
shift @$list unless ref $list->[0];
|
||
|
|
return $list;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $dbcr = LJ::get_cluster_def_reader($u) or return undef;
|
||
|
|
my $sth = $dbcr->prepare( "SELECT year, month, day, COUNT(*) "
|
||
|
|
. "FROM log2 WHERE journalid=? $secwhere GROUP BY 1, 2, 3" );
|
||
|
|
$sth->execute($uid);
|
||
|
|
my @days;
|
||
|
|
while ( my ( $y, $m, $d, $c ) = $sth->fetchrow_array ) {
|
||
|
|
|
||
|
|
# we force each number from string scalars (from DBI) to int scalars,
|
||
|
|
# so they store smaller in memcache
|
||
|
|
push @days, [ int($y), int($m), int($d), int($c) ];
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $memkind ne "g1" && $memkind =~ /^g\d+$/ ) {
|
||
|
|
|
||
|
|
# custom groups are cached for only 15 minutes
|
||
|
|
LJ::MemCache::set( $memkey, [@days], 15 * 60 );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# all other security levels are cached indefinitely
|
||
|
|
# because we clear them when there are updates
|
||
|
|
LJ::MemCache::set( $memkey, [@days] );
|
||
|
|
}
|
||
|
|
return \@days;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub meta_discovery_links {
|
||
|
|
my $u = shift;
|
||
|
|
my $journalbase = $u->journal_base;
|
||
|
|
|
||
|
|
my %opts = ref $_[0] ? %{ $_[0] } : @_;
|
||
|
|
|
||
|
|
my $ret = "";
|
||
|
|
|
||
|
|
# Automatic Discovery of RSS/Atom
|
||
|
|
if ( $opts{feeds} ) {
|
||
|
|
if ( $opts{tags} && @{ $opts{tags} || [] } ) {
|
||
|
|
my $taglist = join( ',', map( { LJ::eurl($_) } @{ $opts{tags} || [] } ) );
|
||
|
|
$ret .=
|
||
|
|
qq{<link rel="alternate" type="application/rss+xml" title="RSS: filtered by selected tags" href="$journalbase/data/rss?tag=$taglist" />\n};
|
||
|
|
$ret .=
|
||
|
|
qq{<link rel="alternate" type="application/atom+xml" title="Atom: filtered by selected tags" href="$journalbase/data/atom?tag=$taglist" />\n};
|
||
|
|
}
|
||
|
|
|
||
|
|
$ret .=
|
||
|
|
qq{<link rel="alternate" type="application/rss+xml" title="RSS: all entries" href="$journalbase/data/rss" />\n};
|
||
|
|
$ret .=
|
||
|
|
qq{<link rel="alternate" type="application/atom+xml" title="Atom: all entries" href="$journalbase/data/atom" />\n};
|
||
|
|
$ret .=
|
||
|
|
qq{<link rel="service" type="application/atomsvc+xml" title="AtomAPI service document" href="}
|
||
|
|
. $u->atom_service_document
|
||
|
|
. qq{" />\n};
|
||
|
|
}
|
||
|
|
|
||
|
|
# OpenID Server
|
||
|
|
$ret .= $u->openid_tags if $opts{openid};
|
||
|
|
|
||
|
|
return $ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub opt_ctxpopup {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
# if unset, default to on
|
||
|
|
my $prop = $u->raw_prop('opt_ctxpopup') || 'Y';
|
||
|
|
|
||
|
|
return $prop;
|
||
|
|
}
|
||
|
|
|
||
|
|
# should contextual hover be displayed for icons
|
||
|
|
sub opt_ctxpopup_icons {
|
||
|
|
return ( $_[0]->prop('opt_ctxpopup') eq "Y" || $_[0]->prop('opt_ctxpopup') eq "I" );
|
||
|
|
}
|
||
|
|
|
||
|
|
# should contextual hover be displayed for the graphical userhead
|
||
|
|
sub opt_ctxpopup_userhead {
|
||
|
|
return ( $_[0]->prop('opt_ctxpopup') eq "Y" || $_[0]->prop('opt_ctxpopup') eq "U" );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub opt_embedplaceholders {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
my $prop = $u->raw_prop('opt_embedplaceholders');
|
||
|
|
|
||
|
|
if ( defined $prop ) {
|
||
|
|
return $prop;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
my $imagelinks = $u->prop('opt_imagelinks');
|
||
|
|
return $imagelinks;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_default_style {
|
||
|
|
my $style = eval { LJ::Customize->verify_and_load_style( $_[0] ); };
|
||
|
|
warn $@ if $@;
|
||
|
|
|
||
|
|
return $style;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub show_control_strip {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
LJ::Hooks::run_hook( 'control_strip_propcheck', $u, 'show_control_strip' )
|
||
|
|
if LJ::is_enabled('control_strip_propcheck');
|
||
|
|
|
||
|
|
my $prop = $u->raw_prop('show_control_strip');
|
||
|
|
return 0 if $prop =~ /^off/;
|
||
|
|
|
||
|
|
return 'dark' if $prop eq 'forced';
|
||
|
|
|
||
|
|
return $prop;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub view_control_strip {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
LJ::Hooks::run_hook( 'control_strip_propcheck', $u, 'view_control_strip' )
|
||
|
|
if LJ::is_enabled('control_strip_propcheck');
|
||
|
|
|
||
|
|
my $prop = $u->raw_prop('view_control_strip');
|
||
|
|
return 0 if $prop =~ /^off/;
|
||
|
|
|
||
|
|
return 'dark' if $prop eq 'forced';
|
||
|
|
|
||
|
|
return $prop;
|
||
|
|
}
|
||
|
|
|
||
|
|
# BE VERY CAREFUL about the return values and arguments you pass to this
|
||
|
|
# method. please understand the security implications of this, and how to
|
||
|
|
# properly and safely use it.
|
||
|
|
#
|
||
|
|
sub view_priv_check {
|
||
|
|
my ( $remote, $u, $requested, $page, $itemid ) = @_;
|
||
|
|
|
||
|
|
# $requested is set to the 'viewall' GET argument. this should ONLY be on if the
|
||
|
|
# user is EXPLICITLY requesting to view something they can't see normally. most
|
||
|
|
# of the time this is off, so we can bail now.
|
||
|
|
return unless $requested;
|
||
|
|
|
||
|
|
# now check the rest of our arguments for validity
|
||
|
|
return unless LJ::isu($remote) && LJ::isu($u);
|
||
|
|
return if defined $page && $page !~ /^\w+$/;
|
||
|
|
return if defined $itemid && $itemid !~ /^\d+$/;
|
||
|
|
|
||
|
|
# viewsome = "this user can view suspended content"
|
||
|
|
my $viewsome = $remote->has_priv( canview => 'suspended' );
|
||
|
|
|
||
|
|
# viewall = "this user can view all content, even private"
|
||
|
|
my $viewall = $viewsome && $remote->has_priv( canview => '*' );
|
||
|
|
|
||
|
|
# make sure we log the content being viewed
|
||
|
|
if ( $viewsome && $page ) {
|
||
|
|
my $user = $u->user;
|
||
|
|
$user .= ", itemid: $itemid" if defined $itemid;
|
||
|
|
my $sv = $u->statusvis;
|
||
|
|
LJ::statushistory_add( $u->userid, $remote->userid, 'viewall',
|
||
|
|
"$page: $user, statusvis: $sv" );
|
||
|
|
}
|
||
|
|
|
||
|
|
return wantarray ? ( $viewall, $viewsome ) : $viewsome;
|
||
|
|
}
|
||
|
|
|
||
|
|
=head2 C<< $u->viewing_style( $view ) >>
|
||
|
|
Takes a user and a view argument and returns what that user's preferred
|
||
|
|
style is for a given view.
|
||
|
|
=cut
|
||
|
|
|
||
|
|
sub viewing_style {
|
||
|
|
my ( $u, $view ) = @_;
|
||
|
|
|
||
|
|
$view ||= 'entry';
|
||
|
|
|
||
|
|
my %style_types = ( O => "original", M => "mine", S => "site", L => "light" );
|
||
|
|
my %view_props = (
|
||
|
|
entry => 'opt_viewentrystyle',
|
||
|
|
reply => 'opt_viewentrystyle',
|
||
|
|
icons => 'opt_viewiconstyle',
|
||
|
|
);
|
||
|
|
|
||
|
|
my $prop = $view_props{$view} || 'opt_viewjournalstyle';
|
||
|
|
return 'original' unless defined $u->prop($prop);
|
||
|
|
return $style_types{ $u->prop($prop) } || 'original';
|
||
|
|
}
|
||
|
|
|
||
|
|
########################################################################
|
||
|
|
### End LJ::User functions
|
||
|
|
|
||
|
|
########################################################################
|
||
|
|
### Begin LJ functions
|
||
|
|
|
||
|
|
package LJ;
|
||
|
|
|
||
|
|
use Carp;
|
||
|
|
|
||
|
|
########################################################################
|
||
|
|
### 24. Styles and S2-Related Functions
|
||
|
|
|
||
|
|
=head2 Styles and S2-Related Functions (LJ)
|
||
|
|
=cut
|
||
|
|
|
||
|
|
# FIXME: Update to pull out S1 support.
|
||
|
|
# <LJFUNC>
|
||
|
|
# name: LJ::make_journal
|
||
|
|
# class:
|
||
|
|
# des:
|
||
|
|
# info:
|
||
|
|
# args: dbarg, user, view, remote, opts
|
||
|
|
# des-:
|
||
|
|
# returns:
|
||
|
|
# </LJFUNC>
|
||
|
|
sub make_journal {
|
||
|
|
my ( $user, $view, $remote, $opts ) = @_;
|
||
|
|
|
||
|
|
my $r = DW::Request->get;
|
||
|
|
my $geta = $r->get_args;
|
||
|
|
$opts->{getargs} = $geta;
|
||
|
|
|
||
|
|
my $u = $opts->{'u'} || LJ::load_user($user);
|
||
|
|
|
||
|
|
unless ($u) {
|
||
|
|
${ $opts->{handle_with_siteviews_ref} } = 1;
|
||
|
|
return DW::Template->render_template_misc(
|
||
|
|
"error/unknown-user.tt",
|
||
|
|
{ user => $user },
|
||
|
|
{ scope => 'journal', scope_data => $opts }
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
LJ::set_active_journal($u);
|
||
|
|
|
||
|
|
my ($styleid);
|
||
|
|
if ( $opts->{'styleid'} ) { # s1 styleid
|
||
|
|
confess 'S1 was removed, sorry.';
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
$view ||= "lastn"; # default view when none specified explicitly in URLs
|
||
|
|
if ( $LJ::viewinfo{$view}
|
||
|
|
|| $view eq "month"
|
||
|
|
|| $view eq "entry"
|
||
|
|
|| $view eq "reply" )
|
||
|
|
{
|
||
|
|
$styleid = -1
|
||
|
|
; # to get past the return, then checked later for -1 and fixed, once user is loaded.
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$opts->{'badargs'} = 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return unless $styleid;
|
||
|
|
|
||
|
|
$u->{'_journalbase'} = $u->journal_base( vhost => $opts->{'vhost'} );
|
||
|
|
|
||
|
|
my $eff_view = $LJ::viewinfo{$view}->{'styleof'} || $view;
|
||
|
|
|
||
|
|
my @needed_props = (
|
||
|
|
"stylesys", "s2_style",
|
||
|
|
"url", "urlname",
|
||
|
|
"opt_nctalklinks", "renamedto",
|
||
|
|
"opt_blockrobots", "opt_usesharedpic",
|
||
|
|
"icbm", "journaltitle",
|
||
|
|
"journalsubtitle", "adult_content",
|
||
|
|
"opt_viewjournalstyle", "opt_viewentrystyle"
|
||
|
|
);
|
||
|
|
|
||
|
|
# preload props the view creation code will need later (combine two selects)
|
||
|
|
if ( ref $LJ::viewinfo{$eff_view}->{'owner_props'} eq "ARRAY" ) {
|
||
|
|
push @needed_props, @{ $LJ::viewinfo{$eff_view}->{'owner_props'} };
|
||
|
|
}
|
||
|
|
|
||
|
|
$u->preload_props(@needed_props);
|
||
|
|
|
||
|
|
# if the remote is the user to be viewed, make sure the $remote
|
||
|
|
# hashref has the value of $u's opt_nctalklinks (though with
|
||
|
|
# LJ::load_user caching, this may be assigning between the same
|
||
|
|
# underlying hashref)
|
||
|
|
$remote->{opt_nctalklinks} = $u->{opt_nctalklinks}
|
||
|
|
if $remote && $remote->equals($u);
|
||
|
|
|
||
|
|
# What style are we shooting for, based on user preferences and get arguments?
|
||
|
|
my $stylearg = LJ::determine_viewing_style( $geta, $view, $remote );
|
||
|
|
my $stylesys = 1;
|
||
|
|
|
||
|
|
if ( $styleid == -1 ) {
|
||
|
|
|
||
|
|
my $get_styleinfo = sub {
|
||
|
|
|
||
|
|
# forced s2 style id (must be numeric)
|
||
|
|
if ( $geta->{s2id} && $geta->{s2id} =~ /^\d+$/ ) {
|
||
|
|
|
||
|
|
# get the owner of the requested style
|
||
|
|
my $style = LJ::S2::load_style( $geta->{s2id} );
|
||
|
|
my $owner = $style && $style->{userid} ? $style->{userid} : 0;
|
||
|
|
|
||
|
|
# remote can use s2id on this journal if:
|
||
|
|
# owner of the style is remote or managed by remote OR
|
||
|
|
# owner of the style has s2styles cap and remote is viewing owner's journal OR
|
||
|
|
# all layers in this style are public (public layer or is_public)
|
||
|
|
|
||
|
|
if ( $u->id == $owner && $u->get_cap("s2styles") ) {
|
||
|
|
$opts->{'style_u'} = LJ::load_userid($owner);
|
||
|
|
return ( 2, $geta->{'s2id'} );
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $remote && $remote->can_manage($owner) ) {
|
||
|
|
|
||
|
|
# check is owned style still available: paid user possible became plus...
|
||
|
|
my $lay_id = $style->{layer}->{layout};
|
||
|
|
my $theme_id = $style->{layer}->{theme};
|
||
|
|
my %lay_info;
|
||
|
|
LJ::S2::load_layer_info( \%lay_info,
|
||
|
|
[ $style->{layer}->{layout}, $style->{layer}->{theme} ] );
|
||
|
|
|
||
|
|
if ( LJ::S2::can_use_layer( $remote, $lay_info{$lay_id}->{redist_uniq} )
|
||
|
|
and LJ::S2::can_use_layer( $remote, $lay_info{$theme_id}->{redist_uniq} ) )
|
||
|
|
{
|
||
|
|
$opts->{'style_u'} = LJ::load_userid($owner);
|
||
|
|
return ( 2, $geta->{'s2id'} );
|
||
|
|
} # else this style not allowed by policy
|
||
|
|
}
|
||
|
|
|
||
|
|
return ( 2, $geta->{s2id} ) if LJ::S2::style_is_public($style);
|
||
|
|
}
|
||
|
|
|
||
|
|
# style=mine passed in GET or userprop to use mine?
|
||
|
|
if ( $remote && $stylearg eq 'mine' ) {
|
||
|
|
|
||
|
|
# get remote props and decide what style remote uses
|
||
|
|
$remote->preload_props( "stylesys", "s2_style" );
|
||
|
|
|
||
|
|
# remote using s2; make sure we pass down the $remote object as the style_u to
|
||
|
|
# indicate that they should use $remote to load the style instead of the regular $u
|
||
|
|
if ( $remote->{'stylesys'} == 2 && $remote->{'s2_style'} ) {
|
||
|
|
$opts->{'checkremote'} = 1;
|
||
|
|
$opts->{'style_u'} = $remote;
|
||
|
|
return ( 2, $remote->{'s2_style'} );
|
||
|
|
}
|
||
|
|
|
||
|
|
# return stylesys 2; will fall back on default style
|
||
|
|
$opts->{style_u} = $remote;
|
||
|
|
return ( 2, undef );
|
||
|
|
}
|
||
|
|
|
||
|
|
# resource URLs have the styleid in it
|
||
|
|
# unless they're a special style, like sitefeeds (which have no styleid)
|
||
|
|
# in which case, let them fall through. Something else will handle it
|
||
|
|
if ( $view eq "res" && $opts->{'pathextra'} =~ m!^/(\d+)/! && $1 ) {
|
||
|
|
return ( 2, $1 );
|
||
|
|
}
|
||
|
|
|
||
|
|
# feed accounts have a special style
|
||
|
|
if ( $u->is_syndicated && %$LJ::DEFAULT_FEED_STYLE ) {
|
||
|
|
return ( 2, "sitefeeds" );
|
||
|
|
}
|
||
|
|
|
||
|
|
my $forceflag = 0;
|
||
|
|
LJ::Hooks::run_hooks( "force_s1", $u, \$forceflag );
|
||
|
|
|
||
|
|
# if none of the above match, they fall through to here
|
||
|
|
if ( !$forceflag && $u->{'stylesys'} == 2 ) {
|
||
|
|
return ( 2, $u->{'s2_style'} );
|
||
|
|
}
|
||
|
|
|
||
|
|
# no special case, let it fall back on the default
|
||
|
|
return ( 2, undef );
|
||
|
|
};
|
||
|
|
|
||
|
|
( $stylesys, $styleid ) = $get_styleinfo->();
|
||
|
|
}
|
||
|
|
|
||
|
|
# transcode the tag filtering information into the tag getarg; this has to
|
||
|
|
# be done above the s1shortcomings section so that we can fall through to that
|
||
|
|
# style for lastn filtered by tags view
|
||
|
|
if ( $view eq 'lastn' && $opts->{pathextra} && $opts->{pathextra} =~ /^\/tag\/(.+)$/ ) {
|
||
|
|
$opts->{getargs}->{tag} = LJ::durl($1);
|
||
|
|
$opts->{pathextra} = undef;
|
||
|
|
}
|
||
|
|
|
||
|
|
# do the same for security filtering
|
||
|
|
elsif (( $view eq 'lastn' || $view eq 'read' )
|
||
|
|
&& $opts->{pathextra}
|
||
|
|
&& $opts->{pathextra} =~ /^\/security\/(.*)$/ )
|
||
|
|
{
|
||
|
|
$opts->{getargs}->{security} = LJ::durl($1);
|
||
|
|
$opts->{pathextra} = undef;
|
||
|
|
}
|
||
|
|
|
||
|
|
$r->note( journalid => $u->userid )
|
||
|
|
if $r;
|
||
|
|
|
||
|
|
my $error = sub {
|
||
|
|
my ( $file, $fileopts ) = @_;
|
||
|
|
$fileopts //= {};
|
||
|
|
|
||
|
|
${ $opts->{handle_with_siteviews_ref} } = 1;
|
||
|
|
return DW::Template->render_template_misc( $file, $fileopts,
|
||
|
|
{ scope => 'journal', scope_data => $opts } );
|
||
|
|
};
|
||
|
|
my $notice = sub {
|
||
|
|
return $error->( 'error/vhost.tt', { u => $u, msg => $_[0] } );
|
||
|
|
};
|
||
|
|
|
||
|
|
if ( $opts->{'vhost'} eq "users"
|
||
|
|
&& !$u->is_redirect
|
||
|
|
&& !LJ::get_cap( $u, "userdomain" ) )
|
||
|
|
{
|
||
|
|
return $notice->(
|
||
|
|
LJ::Lang::ml( 'error.vhost.nodomain1', { user_domain => $LJ::USER_DOMAIN } ) );
|
||
|
|
}
|
||
|
|
if ( $opts->{'vhost'} =~ /^other:/ ) {
|
||
|
|
return $notice->( LJ::Lang::ml('error.vhost.noalias1') );
|
||
|
|
}
|
||
|
|
if ( $opts->{'vhost'} eq "community" && $u->journaltype !~ /[CR]/ ) {
|
||
|
|
$opts->{'badargs'} = 1;
|
||
|
|
return; # 404 Not Found
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $view eq "network" && !LJ::get_cap( $u, "friendsfriendsview" ) ) {
|
||
|
|
my $errmsg = LJ::Lang::ml('cprod.friendsfriendsinline.text3.v1');
|
||
|
|
return $error->( 'error.tt', { message => $errmsg } );
|
||
|
|
}
|
||
|
|
|
||
|
|
# signal to LiveJournal.pm that we can't handle this
|
||
|
|
if ( $stylesys == 1 || $stylearg eq 'site' || $stylearg eq 'light' ) {
|
||
|
|
|
||
|
|
# If they specified ?format=light, it means they want a page easy
|
||
|
|
# to deal with text-only or on a mobile device. For now that means
|
||
|
|
# render it in the lynx site scheme.
|
||
|
|
DW::SiteScheme->set_for_request('lynx')
|
||
|
|
if $stylearg eq 'light';
|
||
|
|
|
||
|
|
# Render a system-owned S2 style that renders
|
||
|
|
# this content, then passes it to get treated as BML
|
||
|
|
$stylesys = 2;
|
||
|
|
$styleid = "siteviews";
|
||
|
|
}
|
||
|
|
|
||
|
|
# now, if there's a GET argument for tags, split those out
|
||
|
|
if ( exists $opts->{getargs}->{tag} ) {
|
||
|
|
my $tagfilter = $opts->{getargs}->{tag};
|
||
|
|
|
||
|
|
unless ($tagfilter) {
|
||
|
|
$opts->{redir} = $u->journal_base . "/tag/";
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
# error if disabled
|
||
|
|
return $error->( "error/tagview.tt", { errmsg => 'error.tag.disabled' } )
|
||
|
|
unless LJ::is_enabled('tags');
|
||
|
|
|
||
|
|
# throw an error if we're rendering in S1, but not for renamed accounts
|
||
|
|
if ( $stylesys == 1 && $view ne 'data' && !$u->is_redirect ) {
|
||
|
|
return $error->( "error/tagview.tt", { errmsg => 'error.tag.s1' } );
|
||
|
|
}
|
||
|
|
|
||
|
|
# overwrite any tags that exist
|
||
|
|
$opts->{tags} = [];
|
||
|
|
my $check_tagstring = LJ::Tags::is_valid_tagstring( $tagfilter, $opts->{tags},
|
||
|
|
{ omit_underscore_check => 1 } );
|
||
|
|
|
||
|
|
return $error->( "error/tagview.tt", { errmsg => 'error.tag.invalid' } )
|
||
|
|
unless $check_tagstring;
|
||
|
|
|
||
|
|
# get user's tags so we know what remote can see, and setup an inverse mapping
|
||
|
|
# from keyword to tag
|
||
|
|
$opts->{tagids} = [];
|
||
|
|
my $tags = LJ::Tags::get_usertags( $u, { remote => $remote } );
|
||
|
|
my %kwref = ( map { $tags->{$_}->{name} => $_ } keys %{ $tags || {} } );
|
||
|
|
|
||
|
|
foreach ( @{ $opts->{tags} } ) {
|
||
|
|
return $error->( "error/tagview.tt", { errmsg => 'error.tag.undef' } )
|
||
|
|
unless $kwref{$_};
|
||
|
|
|
||
|
|
push @{ $opts->{tagids} }, $kwref{$_};
|
||
|
|
}
|
||
|
|
|
||
|
|
my $tagmode = $opts->{getargs}->{mode} || '';
|
||
|
|
$opts->{tagmode} = $tagmode eq 'and' ? 'and' : 'or';
|
||
|
|
|
||
|
|
# also allow mode=all (equivalent to 'and')
|
||
|
|
$opts->{tagmode} = 'and' if $tagmode eq 'all';
|
||
|
|
}
|
||
|
|
|
||
|
|
# validate the security filter
|
||
|
|
if ( exists $opts->{getargs}->{security} ) {
|
||
|
|
my $securityfilter = $opts->{getargs}->{security};
|
||
|
|
|
||
|
|
my $canview_groups = (
|
||
|
|
$view eq "lastn" # viewing recent entries
|
||
|
|
# ... or your own read page (can't see locked entries on others' read page anyway)
|
||
|
|
|| ( $view eq "read" && $u->equals($remote) )
|
||
|
|
);
|
||
|
|
|
||
|
|
my $r = DW::Request->get;
|
||
|
|
my $security_err = sub {
|
||
|
|
my ( $args, %opts ) = @_;
|
||
|
|
|
||
|
|
my $status = $opts{status} || $r->NOT_FOUND;
|
||
|
|
|
||
|
|
my @levels;
|
||
|
|
my @groups;
|
||
|
|
|
||
|
|
# error message is an appropriate type to show the list
|
||
|
|
if ( $opts{show_list} && $canview_groups ) {
|
||
|
|
|
||
|
|
my $path = $view eq "read" ? "/read/security" : "/security";
|
||
|
|
@levels = (
|
||
|
|
{
|
||
|
|
link => LJ::create_url( "$path/public", viewing_style => 1 ),
|
||
|
|
name_ml => "label.security.public"
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
if ( $u->is_comm ) {
|
||
|
|
push @levels,
|
||
|
|
{
|
||
|
|
link => LJ::create_url( "$path/access", viewing_style => 1 ),
|
||
|
|
name_ml => "label.security.members"
|
||
|
|
}
|
||
|
|
if $remote && $remote->member_of($u);
|
||
|
|
|
||
|
|
push @levels,
|
||
|
|
{
|
||
|
|
link => LJ::create_url( "$path/private", viewing_style => 1 ),
|
||
|
|
name_ml => "label.security.maintainers"
|
||
|
|
}
|
||
|
|
if $remote && $remote->can_manage_other($u);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
push @levels,
|
||
|
|
{
|
||
|
|
link => LJ::create_url( "$path/access", viewing_style => 1 ),
|
||
|
|
name_ml => "label.security.accesslist"
|
||
|
|
}
|
||
|
|
if $u->trusts($remote);
|
||
|
|
|
||
|
|
push @levels,
|
||
|
|
{
|
||
|
|
link => LJ::create_url( "$path/private", viewing_style => 1 ),
|
||
|
|
name_ml => "label.security.private"
|
||
|
|
}
|
||
|
|
if $u->equals($remote);
|
||
|
|
}
|
||
|
|
|
||
|
|
$args->{levels} = \@levels;
|
||
|
|
|
||
|
|
@groups = map {
|
||
|
|
{
|
||
|
|
link => LJ::create_url( "$path/group:" . $_->{groupname} ),
|
||
|
|
name => $_->{groupname}
|
||
|
|
}
|
||
|
|
} $remote->trust_groups if $u->equals($remote);
|
||
|
|
$args->{groups} = \@groups;
|
||
|
|
}
|
||
|
|
|
||
|
|
${ $opts->{handle_with_siteviews_ref} } = 1;
|
||
|
|
my $ret = DW::Template->template_string(
|
||
|
|
"journal/security.tt",
|
||
|
|
$args,
|
||
|
|
{
|
||
|
|
status => $status,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
$opts->{siteviews_extra_content} = $args->{sections};
|
||
|
|
return $ret;
|
||
|
|
};
|
||
|
|
|
||
|
|
return $security_err->( { message => undef }, show_list => 1 )
|
||
|
|
unless $securityfilter;
|
||
|
|
|
||
|
|
return $security_err->( { message => "error.security.nocap2" }, status => $r->FORBIDDEN )
|
||
|
|
unless LJ::get_cap( $remote, "security_filter" )
|
||
|
|
|| LJ::get_cap( $u, "security_filter" );
|
||
|
|
|
||
|
|
return $security_err->( { message => "error.security.disabled2" } )
|
||
|
|
unless LJ::is_enabled("security_filter");
|
||
|
|
|
||
|
|
# throw an error if we're rendering in S1, but not for renamed accounts
|
||
|
|
return $security_err->( { message => "error.security.s1.2" } )
|
||
|
|
if $stylesys == 1 && $view ne 'data' && !$u->is_redirect;
|
||
|
|
|
||
|
|
# check the filter itself
|
||
|
|
if ( lc($securityfilter) eq 'friends' ) {
|
||
|
|
$opts->{securityfilter} = 'access';
|
||
|
|
}
|
||
|
|
elsif ( $securityfilter =~ /^(?:public|access|private)$/i ) {
|
||
|
|
$opts->{securityfilter} = lc($securityfilter);
|
||
|
|
|
||
|
|
# see if they want to filter by a custom group
|
||
|
|
}
|
||
|
|
elsif ( $securityfilter =~ /^group:(.+)$/i && $canview_groups ) {
|
||
|
|
my $tf = $u->trust_groups( name => $1 );
|
||
|
|
if (
|
||
|
|
$tf
|
||
|
|
&& ( $u->equals($remote)
|
||
|
|
|| $u->trustmask($remote) & ( 1 << $tf->{groupnum} ) )
|
||
|
|
)
|
||
|
|
{
|
||
|
|
# let them filter the results page by this group
|
||
|
|
$opts->{securityfilter} = $tf->{groupnum};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $security_err->( { message => "error.security.invalid2" }, show_list => 1 )
|
||
|
|
unless defined $opts->{securityfilter};
|
||
|
|
}
|
||
|
|
|
||
|
|
unless ( $geta->{'viewall'} && $remote && $remote->has_priv( "canview", "suspended" )
|
||
|
|
|| $opts->{'pathextra'} =~ m!/(\d+)/stylesheet$! )
|
||
|
|
{ # don't check style sheets
|
||
|
|
return $u->display_journal_deleted( $remote, journal_opts => $opts ) if $u->is_deleted;
|
||
|
|
|
||
|
|
return $error->( "error/suspended.tt", { u => $u, remote => $remote } ) if $u->is_suspended;
|
||
|
|
|
||
|
|
my $entry = $opts->{ljentry};
|
||
|
|
return $error->( "error/suspended-entry.tt", { u => $u } )
|
||
|
|
if $entry && $entry->is_suspended_for($remote);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $error->("error/purged.tt") if $u->is_expunged;
|
||
|
|
|
||
|
|
my %valid_identity_views = (
|
||
|
|
read => 1,
|
||
|
|
res => 1, # res is a resource, such as an external stylesheet
|
||
|
|
icons => 1,
|
||
|
|
);
|
||
|
|
|
||
|
|
if ( $u->is_identity && !$valid_identity_views{$view} ) {
|
||
|
|
return $error->( "error/openid-user.tt", { u => $u } );
|
||
|
|
}
|
||
|
|
|
||
|
|
$opts->{'view'} = $view;
|
||
|
|
|
||
|
|
# what charset we put in the HTML
|
||
|
|
$opts->{'saycharset'} ||= "utf-8";
|
||
|
|
|
||
|
|
if ( $view eq 'data' ) {
|
||
|
|
return LJ::Feed::make_feed( $r, $u, $remote, $opts );
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $stylesys == 2 ) {
|
||
|
|
eval { LJ::S2->can("dostuff") }; # force Class::Autouse
|
||
|
|
|
||
|
|
my $mj;
|
||
|
|
|
||
|
|
eval { $mj = LJ::S2::make_journal( $u, $styleid, $view, $remote, $opts ); };
|
||
|
|
if ($@) {
|
||
|
|
if ( $remote && $remote->show_raw_errors ) {
|
||
|
|
my $r = DW::Request->get;
|
||
|
|
$r->content_type("text/html");
|
||
|
|
$r->print("<b>[Error: $@]</b>");
|
||
|
|
warn $@;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
die $@;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $mj;
|
||
|
|
}
|
||
|
|
|
||
|
|
# if we get here, then we tried to run the old S1 path, so die and hope that
|
||
|
|
# somebody comes along to fix us :(
|
||
|
|
confess 'Tried to run S1 journal rendering path.';
|
||
|
|
}
|
||
|
|
|
||
|
|
1;
|