#!/usr/bin/perl # # DW::Controller::Customize::Advanced # # This controller is for /customize/options and the helper functions for that view. # # Authors: # R Hatch # # Copyright (c) 2016 by Dreamwidth Studios, LLC. # # This program is free software; you may redistribute it and/or modify it under # the same terms as Perl itself. For a copy of the license, please reference # 'perldoc perlartistic' or 'perldoc perlgpl'. # package DW::Controller::Customize::Advanced; use strict; use warnings; use Digest::MD5; use DW::Controller; use DW::Routing; use DW::Template; use DW::Logic::MenuNav; DW::Routing->register_string( '/customize/advanced', \&advanced_handler, app => 1 ); DW::Routing->register_string( '/customize/advanced/layerbrowse', \&layerbrowse_handler, app => 1 ); DW::Routing->register_string( '/customize/advanced/layers', \&layers_handler, app => 1 ); DW::Routing->register_string( '/customize/advanced/styles', \&styles_handler, app => 1 ); DW::Routing->register_string( '/customize/advanced/layersource', \&layersource_handler, app => 1 ); DW::Routing->register_string( '/customize/advanced/layeredit', \&layeredit_handler, app => 1 ); sub advanced_handler { my ( $ok, $rv ) = controller(); return $rv unless $ok; my $r = $rv->{r}; my $POST = $r->post_args; my $u = $rv->{u}; my $remote = $rv->{remote}; my $no_layer_edit = LJ::Hooks::run_hook( "no_theme_or_layer_edit", $u ); my $vars; $vars->{u} = $u; $vars->{remote} = $remote; $vars->{no_layer_edit} = $no_layer_edit; return DW::Template->render_template( 'customize/advanced/index.tt', $vars ); } sub layerbrowse_handler { my ( $ok, $rv ) = controller(); return $rv unless $ok; my $r = $rv->{r}; my $POST = $r->post_args; my $u = $rv->{u}; my $remote = $rv->{remote}; my $GET = $r->get_args; my $vars; $vars->{u} = $u; $vars->{remote} = $remote; $vars->{expand} = $GET->{'expand'}; my $pub = LJ::S2::get_public_layers(); my $id; if ( $GET->{'id'} ) { if ( $GET->{'id'} =~ /^\d+$/ ) { # numeric $id = $GET->{'id'}; } else { # redist_uniq $id = $pub->{ $GET->{'id'} }->{'s2lid'}; } } my $dbr = LJ::get_db_reader(); my %layerinfo; my @to_load = grep { /^\d+$/ } keys %$pub; LJ::S2::load_layer_info( \%layerinfo, \@to_load ); my @s2_keys = grep { /^\d+$/ && exists $pub->{$_}->{'b2lid'} && $pub->{$_}->{'b2lid'} == 0 } keys %$pub; $vars->{pub} = $pub; $vars->{s2_keys} = \@s2_keys; $vars->{childsort} = sub { my $unsortedchildren = shift; my @sortedchildren = sort { ( $layerinfo{$b}{type} cmp $layerinfo{$a}{type} ) || ( $layerinfo{$a}{name} cmp $layerinfo{$b}{name} ) } @$unsortedchildren; return \@sortedchildren; }; my $xlink = sub { my $r = shift; $r =~ s/\[class\[(\w+)\]\]/$1<\/a>/g; $r =~ s/\[method\[(.+?)\]\]/$1<\/a>/g; $r =~ s/\[function\[(.+?)\]\]/$1<\/a>/g; $r =~ s/\[member\[(.+?)\]\]/$1<\/a>/g; return $r; }; my $class; my $s2info; if ($id) { LJ::S2::load_layers($id); $s2info = S2::get_layer_all($id); $class = $s2info->{'class'} || {}; $vars->{s2info} = $s2info; $vars->{id} = $id; $vars->{class} = $class; # load layer info my $layer = defined $pub->{$id} ? $pub->{$id} : LJ::S2::load_layer($id); my $layerinfo = {}; LJ::S2::load_layer_info( $layerinfo, [$id] ); my $srcview = exists $layerinfo->{$id}->{'source_viewable'} ? $layerinfo->{$id}->{'source_viewable'} : undef; # do they have access? my $isadmin = !defined $pub->{$id} && $remote && $remote->has_priv( 'siteadmin', 'styleview' ); my $can_manage = $remote && $remote->can_manage( LJ::load_userid( $layer->{userid} ) ); $vars->{srcview} = $srcview; $vars->{isadmin} = $isadmin; $vars->{can_manage} = $can_manage; } my $xlink_args = sub { my $r = shift; return unless $r =~ /^(.+?\()(.*)\)$/; my ( $new, @args ) = ( $1, split( /\s*\,\s*/, $2 ) ); foreach (@args) { s/^(\w+)/defined $class->{$1} ? "[class[$1]]" : $1/eg; } $new .= join( ", ", @args ) . ")"; $r = $new; $xlink->($r); }; my $format_value; $format_value = sub { my $v = shift; if ( ref $v eq "HASH" ) { if ( $v->{'_type'} && $v->{'_type'} eq "Color" && $v->{'as_string'} =~ /^#\w\w\w\w\w\w$/ ) { my $ecolor = LJ::ehtml( $v->{'as_string'} ); $v = "  $ecolor"; } elsif ( defined $v->{'_type'} ) { $v = BML::ml( '.propformat.object', { 'type' => LJ::ehtml( $v->{'_type'} ) } ); } else { if ( scalar(%$v) ) { $v = "{}"; } else { $v = "{}"; } } } elsif ( ref $v eq "ARRAY" ) { if ( scalar(@$v) ) { $v = "[]"; } else { $v = "[]"; } } else { $v = $v ne '' ? LJ::ehtml($v) : "" . LJ::Lang::ml('.propformat.empty') . ""; } return $v; }; $vars->{xlink} = $xlink; $vars->{xlink_args} = $xlink_args; $vars->{layer_is_active} = sub { my $x = shift; LJ::Hooks::run_hook( "layer_is_active", $pub->{$x}->{uniq} ); }; $vars->{ehtml} = \&LJ::ehtml; $vars->{defined} = sub { return defined $_; }; $vars->{format_value} = $format_value; $vars->{layerinfo} = \%layerinfo; return DW::Template->render_template( 'customize/advanced/layerbrowse.tt', $vars ); } sub layers_handler { my ( $ok, $rv ) = controller( authas => 1, form_auth => 1 ); return $rv unless $ok; my $r = $rv->{r}; my $POST = $r->post_args; my $u = $rv->{u}; my $remote = $rv->{remote}; my $no_layer_edit = LJ::Hooks::run_hook( "no_theme_or_layer_edit", $remote ); my $GET = $r->get_args; my $authas = $u->user ne $remote->user ? "?authas=" . $u->user : ""; # id is optional my $id; $id = $POST->{'id'} if $POST->{'id'} && $POST->{'id'} =~ /^\d+$/; # this catches core_hidden if it's set $POST->{'parid'} ||= $POST->{'parid_hidden'}; my $pub = LJ::S2::get_public_layers(); my $ulay = LJ::S2::get_layers_of_user($u); my $vars; $vars->{u} = $u; $vars->{remote} = $remote; $vars->{no_layer_edit} = $no_layer_edit; $vars->{pub} = $pub; $vars->{ulay} = $ulay; $vars->{authas} = $authas; $vars->{authas_html} = $rv->{authas_html}; return error_ml('/customize/advanced/index.tt.error.advanced.editing.denied') if $no_layer_edit; # if we don't have a u, maybe they're an admin and can view stuff anyway? my $noactions = 0; my $viewall = $remote && $remote->has_priv( 'siteadmin', 'styleview' ); if ( $GET->{user} && $viewall ) { return error_ml('/customize/advanced/layers.tt.error.cantuseonsystem') if $GET->{user} eq 'system'; $noactions = 1; # don't let admins change anything } return error_ml('.error.cantbeauthenticated') unless $u; return error_ml( ( $remote->{user} eq $u->{user} ? '/customize/advanced/layers.tt.error.youcantuseadvanced' : '/customize/advanced/layers.tt.error.usercantuseadvanced' ), undef, { authas => $rv->{authas_html} } ) unless $u->can_create_s2_styles || $viewall; if ( $POST->{'action:create'} && !$noactions ) { return error_ml('/customize/advanced/layers.tt.error.maxlayers') if keys %$ulay >= $u->count_s2layersmax; my $type = $POST->{'type'} or return error_ml('/customize/advanced/layers.tt.error.nolayertypeselected'); my $parid = $POST->{'parid'} + 0 or return error_ml('/customize/advanced/layers.tt.error.badparentid'); return error_ml('/customize/advanced/layers.tt.error.invalidlayertype') unless $type =~ /^layout|theme|user|i18nc?$/; my $parent_type = ( $type eq "theme" || $type eq "i18n" || $type eq "user" ) ? "layout" : "core"; # parent ID is public layer if ( $pub->{$parid} ) { # of the wrong type return error_ml('/customize/advanced/layers.tt.error.badparentid') if $pub->{$parid}->{'type'} ne $parent_type; # parent ID is user layer, or completely invalid } else { return error_ml('.error.badparentid') if !$ulay->{$parid} || $ulay->{$parid}->{'type'} != $parent_type; } my $id = LJ::S2::create_layer( $u, $parid, $type ); return error_ml('/customize/advanced/layers.tt.error.cantcreatelayer') unless $id; my $lay = { 'userid' => $u->userid, 'type' => $type, 'b2lid' => $parid, 's2lid' => $id, }; # help user out a bit, creating the beginning of their layer. my $s2 = "layerinfo \"type\" = \"$type\";\n"; $s2 .= "layerinfo \"name\" = \"\";\n\n"; my $error; unless ( LJ::S2::layer_compile( $lay, \$error, { 's2ref' => \$s2 } ) ) { return error_ml( '/customize/advanced/layers.tt.error.cantsetuplayer', { 'errormsg' => $error } ); } # redirect so they can't refresh and create a new layer again return $r->redirect("$LJ::SITEROOT/customize/advanced/layers$authas"); } # delete if ( $POST->{'action:del'} && !$noactions ) { my $id = $POST->{'id'} + 0; my $lay = LJ::S2::load_layer($id); return error_ml('/customize/advanced/layers.tt.error.layerdoesntexist') unless $lay; return error_ml('/customize/advanced/layers.tt.error.notyourlayer') unless $lay->{userid} == $u->userid; LJ::S2::delete_layer( $u, $id ); return $r->redirect("$LJ::SITEROOT/customize/advanced/layers$authas"); } my %active_style = LJ::S2::get_style($u); # set up indices for the sort, because it's easier than the convoluted logic # of doing all this within the sort itself my @parentlayernames; my @layernames; my @weight; my %specialnamelayers; my @layerids = keys %$ulay; foreach my $layerid (@layerids) { my $parent = $ulay->{ $ulay->{$layerid}->{b2lid} } || $pub->{ $ulay->{$layerid}->{b2lid} }; push @parentlayernames, $parent->{name}; my $layername = $ulay->{$layerid}->{name}; push @layernames, $layername; my $weight = { "Auto-generated Customizations" => 1, # auto-generated "" => 2 # empty }->{$layername}; push @weight, $weight; $specialnamelayers{$layerid} = 1 if $weight; } my @sortedlayers = @layerids[ sort { # alphabetically by parent layer's name $parentlayernames[$a] cmp $parentlayernames[$b] # special case empty names and auto-generated customizations (push them to the bottom) || $weight[$a] cmp $weight[$b] # alphabetically by layer name (for regular layer names) || $layernames[$a] cmp $layernames[$b] # Auto-generated customizations then layers with no name sorted by layer id || $layerids[$a] <=> $layerids[$b] } 0 .. $#layerids ]; my @corelayers = map { $_, $pub->{$_}->{'majorversion'} } sort { $pub->{$b}->{'majorversion'} <=> $pub->{$a}->{'majorversion'} } grep { $pub->{$_}->{'b2lid'} == 0 && $pub->{$_}->{'type'} eq 'core' && /^\d+$/ } keys %$pub; my @layouts = ( '', '' ); push @layouts, map { $_, $pub->{$_}->{'name'} } sort { $pub->{$a}->{'name'} cmp $pub->{$b}->{'name'} || $a <=> $b } grep { $pub->{$_}->{'type'} eq 'layout' && /^\d+$/ } keys %$pub; if (%$ulay) { my @ulayouts = (); push @ulayouts, map { $_, LJ::Lang::ml( '/customize/advanced/layers.tt.createlayer.layoutspecific.select.userlayer', { 'name' => $ulay->{$_}->{'name'}, 'id' => $_ } ) } sort { $ulay->{$a}->{'name'} cmp $ulay->{$b}->{'name'} || $a <=> $b } grep { $ulay->{$_}->{'type'} eq 'layout' } keys %$ulay; push @layouts, ( '', '---', @ulayouts ) if @ulayouts; } $vars->{authas_html} = $rv->{authas_html}; $vars->{noactions} = $noactions; $vars->{layouts} = \@layouts; $vars->{corelayers} = \@corelayers; $vars->{sortedlayers} = \@sortedlayers; $vars->{active_style} = \%active_style; $vars->{specialnamelayers} = \%specialnamelayers; $vars->{ehtml} = \&LJ::ehtml; $vars->{ejs} = \&LJ::ejs; return DW::Template->render_template( 'customize/advanced/layers.tt', $vars ); } sub styles_handler { my ( $ok, $rv ) = controller( authas => 1, form_auth => 1 ); return $rv unless $ok; my $r = $rv->{r}; my $POST = $r->post_args; my $u = $rv->{u}; my $remote = $rv->{remote}; my $no_layer_edit = LJ::Hooks::run_hook( "no_theme_or_layer_edit", $remote ); my $GET = $r->get_args; my $authas = $u->user ne $remote->user ? "?authas=" . $u->user : ""; my $vars; $vars->{u} = $u; $vars->{remote} = $remote; $vars->{no_layer_edit} = $no_layer_edit; $vars->{authas_html} = $rv->{authas_html}; $vars->{post} = $POST; # if we don't have a u, maybe they're an admin and can view stuff anyway? my $noactions = 0; my $viewall = $remote && $remote->has_priv( 'siteadmin', 'styleview' ); if ( $GET->{user} && $viewall ) { return error_ml('/customize/advanced/styles.tt.error.cantuseonsystem') if $GET->{user} eq 'system'; $noactions = 1; # don't let admins change anything } return error_ml( ( $remote->{user} eq $u->{user} ? '/customize/advanced/styles.tt.error.youcantuseadvanced' : '/customize/advanced/styles.tt.error.usercantuseadvanced' ), undef, { authas => $rv->{authas_html} } ) unless $u->can_create_s2_styles || $viewall; return error_ml('/customize/advanced/index.tt.error.advanced.editing.denied') if $no_layer_edit; # extra arguments for get requests my $getextra = $u->user ne $remote->user ? "?authas=" . $u->user : ''; my $getextra_amp = $getextra ? "&authas=" . $u->user : ''; if ($noactions) { $getextra = "?user=" . $u->user; $getextra_amp = "&user=" . $u->user; } $vars->{getextra} = $getextra; $vars->{getextra_amp} = $getextra_amp; # style id to edit, if we have one # if we have this we're assumed to be in 'edit' mode my $id = $GET->{'id'} + 0; my $dbh = LJ::get_db_writer(); # variables declared here, but only filled in if $id my ( $core, $layout ); # scalars my ( $pub, $ulay, $style ); # hashrefs if ($id) { # load style $style = LJ::S2::load_style($id); return error_ml('/customize/advanced/styles.tt.error.stylenotfound') unless $style; # check that they own the style return error_ml('/customize/advanced/styles.tt.error.notyourstyle') unless $style->{userid} == $u->userid; # use selected style if ( $POST->{'action:usestyle'} && !$noactions ) { # save to db and update user object $u->set_prop( { stylesys => '2', s2_style => $id } ); LJ::Hooks::run_hooks( 'apply_theme', $u ); return $r->redirect("styles$getextra"); } # get public layers $pub = LJ::S2::get_public_layers(); $vars->{pub} = $pub; # get user layers $ulay = LJ::S2::get_layers_of_user($u); $vars->{ulay} = $ulay; # find effective layerids being used my %eff_layer = (); my @other_layers = (); foreach (qw(i18nc layout theme i18n user)) { my $lid = $POST->{$_} eq "_other" ? $POST->{"other_$_"} : $POST->{$_}; next unless $lid; $eff_layer{$_} = $lid; unless ( $ulay->{ $eff_layer{$_} } || $pub->{ $eff_layer{$_} } ) { push @other_layers, $lid; } } # core lid (can't use user core layer) $POST->{core} ||= $POST->{core_hidden}; $core = defined $POST->{core} ? $POST->{core} : $style->{layer}->{core}; my $highest_core; map { $highest_core = $_ if $pub->{$_}->{type} eq 'core' && /^\d+$/ && $pub->{$_}->{majorversion} > $pub->{$highest_core}->{majorversion} } keys %$pub; unless ($core) { $core = $highest_core; # update in POST to keep things in sync $POST->{core} = $highest_core; } $style->{layer}->{core} = $highest_core unless $style->{layer}->{core}; $vars->{core} = $core; # layout lid $layout = $POST->{'action:change'} ? $eff_layer{'layout'} : $style->{'layer'}->{'layout'}; # if we're changing core, clear everything if ( $POST->{'core'} && $style->{'layer'}->{'core'} && $POST->{'core'} != $style->{'layer'}->{'core'} ) { foreach (qw(i18nc layout theme i18n user)) { delete $eff_layer{$_}; } undef $layout; } # if we're changing layout, clear everything below if ( $eff_layer{'layout'} && $style->{'layer'}->{'layout'} && $eff_layer{'layout'} != $style->{'layer'}->{'layout'} ) { foreach (qw(theme i18n user)) { delete $eff_layer{$_}; } } ### process edit actions # delete if ( $POST->{'action:delete'} && !$noactions ) { LJ::S2::delete_user_style( $u, $id ); undef $id; # don't show form below return $r->redirect("styles$getextra"); } # save changes if ( ( $POST->{'action:change'} || $POST->{'action:savechanges'} ) && !$noactions ) { $vars->{post_action} = 'change'; # are they renaming their style? if ( $POST->{'stylename'} && $style->{'name'} && $POST->{'stylename'} ne $style->{'name'} ) { # update db my $styleid = $style->{'styleid'}; LJ::S2::rename_user_style( $u, $styleid, $POST->{stylename} ); # update style object $style->{'name'} = $POST->{'stylename'}; } # load layer info of any "other" layers my %other_info = (); if (@other_layers) { LJ::S2::load_layer_info( \%other_info, \@other_layers ); foreach (@other_layers) { return error_ml( '/customize/advanced/styles.tt.error.layernotfound', { 'layer' => $_ } ) unless exists $other_info{$_}; return error_ml( '/customize/advanced/styles.tt.error.layernotpublic', { 'layer' => $_ } ) unless $other_info{$_}->{'is_public'}; } } # error check layer modifications my $get_layername = sub { my $lid = shift; my $name; $name = $pub->{$lid}->{'name'} if $pub->{$lid}; $name ||= $ulay->{$lid}->{'name'} if $ulay->{$lid}; $name ||= ml( '.layerid', { 'id' => $lid } ); return $name; }; # check layer hierarchy my $error_check = sub { my ( $type, $parentid ) = @_; my $lid = $eff_layer{$type}; next if !$lid; my $layer = $ulay->{$lid} || $pub->{$lid} || LJ::S2::load_layer($lid); my $parentname = $get_layername->($parentid); my $layername = $get_layername->($lid); # is valid layer type? return error_ml( '/customize/advanced/styles.tt.error.invalidlayertype', { 'name' => "$layername", 'type' => $type } ) if $layer->{'type'} ne $type; # is a child? return error_ml( '/customize/advanced/styles.tt.error.layerhierarchymismatch', { 'layername' => "$layername", 'type' => $type, 'parentname' => "$parentname" } ) unless $layer->{'b2lid'} == $parentid; return undef; }; # check child layers of core foreach my $type (qw(i18nc layout)) { my $errmsg = $error_check->( $type, $core ); return error_ml($errmsg) if $errmsg; } # don't check sub-layout layers if there's no layout if ($layout) { # check child layers of selected layout foreach my $type (qw(theme i18n user)) { my $errmsg = $error_check->( $type, $layout ); return error_ml($errmsg) if $errmsg; } } # save in database my @layers = ( 'core' => $core ); push @layers, map { $_, $eff_layer{$_} } qw(i18nc layout i18n theme user); LJ::S2::set_style_layers( $u, $style->{'styleid'}, @layers ); # redirect if they clicked the bottom button return return $r->redirect("styles$getextra") if $POST->{'action:savechanges'}; } $vars->{id} = $id; $vars->{layout} = $layout; $vars->{style} = $style; } else { # load user styles my $ustyle = LJ::S2::load_user_styles($u); my @sortedustyles = sort { $ustyle->{$a} cmp $ustyle->{$b} || $a <=> $b } keys %$ustyle; $vars->{sortedustyles} = \@sortedustyles; $vars->{ustyle} = $ustyle; # process create action if ( ( $POST->{'action:create'} && $POST->{'stylename'} ) && !$noactions ) { return error_ml( '/customize/advanced/styles.tt.error.maxstyles2', { 'numstyles' => scalar( keys %$ustyle ), 'maxstyles' => $u->count_s2stylesmax } ) if scalar( keys %$ustyle ) >= $u->count_s2stylesmax; my $styleid = LJ::S2::create_style( $u, $POST->{'stylename'} ); return error_ml('/customize/advanced/styles.tt.error.cantcreatestyle') unless $styleid; return $r->redirect("styles?id=$styleid$getextra_amp"); } # load style currently in use $u->preload_props('s2_style'); } my $layerselect_sub = sub { my ( $type, $b2lid ) = @_; my @opts = (); my $lid = $POST->{'action:change'} ? $POST->{$type} : $style->{layer}->{$type}; $lid = $POST->{"other_$type"} if $lid eq "_other"; # greps, and sorts a list my $greplist = sub { my $ref = shift; return sort { $ref->{$a}->{'name'} cmp $ref->{$b}->{'name'} || $a <=> $b } grep { my $is_active = LJ::Hooks::run_hook( "layer_is_active", $ref->{$_}->{uniq} ); $ref->{$_}->{'type'} eq $type && $ref->{$_}->{'b2lid'} == $b2lid && ( !defined $is_active || $is_active ) && !( $pub->{$_} && $pub->{$_}->{is_internal} ) && # checking this directly here, as I don't care if the parent layers are internal /^\d+$/ } keys %$ref; }; # public layers my $name = $type eq 'core' ? 'majorversion' : 'name'; push @opts, map { $_, $pub->{$_}->{$name} } $greplist->($pub); # no user core layers return { 'lid' => $lid, 'opts' => \@opts } if $type eq 'core'; # user layers / using an internal layer % push @opts, ( '', '---' ); my $startsize = scalar(@opts); # add the current layer if it's internal and the user is using it. push @opts, ( $lid, LJ::Lang::ml( '/customize/advanced/styles.tt.stylelayers.select.layout.user', { 'layername' => $pub->{$lid}->{'name'}, 'id' => $lid } ) ) if $lid && $pub->{$lid} && $pub->{$lid}->{is_internal}; push @opts, map { $_, LJ::Lang::ml( '/customize/advanced/styles.tt.stylelayers.select.layout.user', { 'layername' => $ulay->{$_}->{'name'}, 'id' => $_ } ) } $greplist->($ulay); # if we didn't push anything above, remove dividing line % pop @opts, pop @opts unless scalar(@opts) > $startsize; # add option for other layerids push @opts, ( '_other', LJ::Lang::ml('/customize/advanced/styles.tt.stylelayers.select.layout.other') ); # add blank option to beginning of list unshift @opts, ( '', @opts ? '' : ' ' ); return { 'lid' => $lid, 'opts' => \@opts }; }; $vars->{layerselect_sub} = $layerselect_sub; $vars->{ehtml} = \&LJ::ehtml; return DW::Template->render_template( 'customize/advanced/styles.tt', $vars ); } sub layersource_handler { my ( $ok, $rv ) = controller(); return $rv unless $ok; my $r = $rv->{r}; my $POST = $r->post_args; my $GET = $r->get_args; my $u = $rv->{u}; my $remote = $rv->{remote}; my $no_layer_edit = LJ::Hooks::run_hook( "no_theme_or_layer_edit", $u ); my $pub = LJ::S2::get_public_layers(); my $dbr = LJ::get_db_reader(); my $id = $GET->{'id'}; return $r->redirect('layerbrowse') unless $id =~ /^\d+$/; my $lay = defined $pub->{$id} ? $pub->{$id} : LJ::S2::load_layer($id); return error_ml('/customize/advanced/layerbrowse.tt.error.layerdoesntexist') unless $lay; my $layerinfo = {}; LJ::S2::load_layer_info( $layerinfo, [$id] ); my $srcview = exists $layerinfo->{$id}->{'source_viewable'} ? $layerinfo->{$id}->{'source_viewable'} : undef; # authorized to view this layer? my $isadmin = !defined $pub->{$id} && $remote && $remote->has_priv( 'siteadmin', 'styleview' ); # public styles are pulled from the system account, so we don't # want to check privileges in case they're private styles return error_ml('/customize/advanced/layerbrowse.tt.error.cantviewlayer') unless defined $pub->{$id} && ( !defined $srcview || $srcview != 0 ) || $srcview == 1 || $isadmin || $remote && $remote->can_manage( LJ::load_userid( $lay->{userid} ) ); my $s2code = LJ::S2::load_layer_source($id); # get html version of the code? if ( $GET->{'fmt'} eq "html" ) { my $html; my ( $md5, $save_cache ); if ( $pub->{$id} ) { # let's see if we have it cached $md5 = Digest::MD5::md5_hex($s2code); my $cache = $dbr->selectrow_array("SELECT value FROM blobcache WHERE bckey='s2html-$id'"); if ( $cache =~ s/^\[$md5\]// ) { $html = $cache; } else { $save_cache = 1; } } unless ($html) { my $cr = new S2::Compiler; $cr->compile_source( { 'source' => \$s2code, 'output' => \$html, 'format' => "html", 'type' => $pub->{$id}->{'type'}, } ); } if ($save_cache) { my $dbh = LJ::get_db_writer(); $dbh->do( "REPLACE INTO blobcache (bckey, dateupdate, value) VALUES (?,NOW(),?)", undef, "s2html-$id", "[$md5]$html" ); } $r->print($html); return $r->OK; } # return text version $r->content_type("text/plain"); my $ua = $r->header_in("User-Agent"); if ( $ua && $ua =~ /\bMSIE\b/ ) { my $filename = "s2source-$id.txt"; $r->header_out( 'Content-Disposition' => "attachment; filename=$filename" ); } $r->print($s2code); return $r->OK; } sub layeredit_handler { my ( $ok, $rv ) = controller(); return $rv unless $ok; my $r = $rv->{r}; my $POST = $r->post_args; my $GET = $r->get_args; my $u = $rv->{u}; my $remote = $rv->{remote}; my $no_layer_edit = LJ::Hooks::run_hook( "no_theme_or_layer_edit", $u ); my $vars; # we need a valid id my $id; $id = $GET->{'id'} if $GET->{'id'} =~ /^\d+$/; return error_ml('/customize/advanced/layeredit.tt.error.nolayer') unless $id; return error_ml('/customize/advanced/index.tt.error.advanced.editing.denied') if $no_layer_edit; # load layer my $lay = LJ::S2::load_layer($id); return error_ml('/customize/advanced/layers.tt.error.layerdoesntexist') unless $lay; # if the b2lid of this layer has been remapped to a new layerid # then update the b2lid mapping for this layer my $b2lid = $lay->{b2lid}; if ( $b2lid && $LJ::S2LID_REMAP{$b2lid} ) { LJ::S2::b2lid_remap( $remote, $id, $b2lid ); $lay->{b2lid} = $LJ::S2LID_REMAP{$b2lid}; } # get u of user they are acting as $u = LJ::load_userid( $lay->{userid} ); # is authorized admin for this layer? return error_ml('/customize/advanced/layeredit.tt.error.layerunauthorized') unless $u && $remote->can_manage($u); # check priv and ownership return error_ml('/customize/advanced/layeredit.tt.error.stylesunauthorized') unless $u->can_create_s2_styles; # at this point, they are authorized, allow viewing & editing # get s2 code from db - use writer so we know it's up-to-date my $dbh = LJ::get_db_writer(); my $s2code = LJ::S2::load_layer_source( $lay->{s2lid} ); # we tried to compile something my $build; if ( $POST->{'action'} eq "compile" ) { return error_ml("error.invalidform") unless LJ::check_form_auth( $POST->{lj_form_auth} ); $build = "S2 Compiler Output at " . scalar(localtime) . "
\n"; my $error; $POST->{'s2code'} =~ tr/\r//d; # just in case unless ( LJ::S2::layer_compile( $lay, \$error, { 's2ref' => \$POST->{'s2code'} } ) ) { $error =~ s/LJ::S2,.+//s; $error =~ s!, .+?(src/s2|cgi-bin)/!, !g; $error =~ s/^Compile error: line (\d+), column (\d+)/Compile error:
line $1, column $2<\/a>/; $build .= "Error compiling layer:\n
$error
"; } else { $build .= "Compiled with no errors.\n"; } $r->print($build); return $r->OK; } # load layer info my $layinf = {}; LJ::S2::load_layer_info( $layinf, [$id] ); # find a title to display on this page my $type = $layinf->{$id}->{'type'}; my $name = $layinf->{$id}->{'name'}; # find name of parent layer if this is a child layer if ( !$name && $type =~ /^(user|theme|i18n)$/ ) { my $par = $lay->{'b2lid'} + 0; LJ::S2::load_layer_info( $layinf, [$par] ); $name = $layinf->{$par}->{'name'}; } # Only use the layer name if there is one and it's more than just whitespace my $title = "[$type] "; $title .= $name && $name =~ /[^\s]/ ? "$name [\#$id]" : "Layer \#$id"; $vars->{build} = "Loaded layer $id."; $vars->{title} = $title; $vars->{s2code} = $s2code; return DW::Template->render_template( 'customize/advanced/layeredit.tt', $vars, { no_sitescheme => 1 } ); } 1;