mourningdove/cgi-bin/LJ/Setting.pm

255 lines
7 KiB
Perl
Raw Permalink Normal View History

2026-05-24 01:03:05 +00:00
# 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::Setting;
use strict;
use warnings;
use Carp qw(croak);
use LJ::ModuleLoader;
# require all settings
LJ::ModuleLoader->require_subclasses("LJ::Setting");
LJ::ModuleLoader->require_subclasses("DW::Setting");
# ----------------------------------------------------------------------------
sub should_render { 1 }
sub is_conditional_setting { 0 }
sub disabled { 0 }
sub selected { 0 }
sub label { "" }
sub actionlink { "" }
sub helpurl { "" }
sub option { "" }
sub htmlcontrol { "" }
sub htmlcontrol_label { "" }
sub error_check {
my ( $class, $u, $args ) = @_;
my $val = $class->get_arg( $args, "foo" );
#unless ($val =~ /blah/) {
# $class->errors("foo" => "Invalid foo");
#}
die "No 'error_check' configured for settings module '$class'\n";
}
sub as_html {
my ( $class, $u, $errmap ) = @_;
return "No 'as_html' implemented for $class.";
}
sub save {
my ( $class, $u, $postargs, @classes ) = @_;
if ( $class ne __PACKAGE__ ) {
die "No 'save' implemented for '$class'\n";
}
else {
die "No classes given to save\n" unless @classes;
}
my %posted; # class -> key -> value
while ( my ( $k, $v ) = each %$postargs ) {
my ( $class, $key ) = class_from_key($k);
$posted{$class}{$key} = $v;
}
foreach my $setclass (@classes) {
my $args = $posted{$setclass} || {};
$setclass->save( $u, $args );
}
}
# ----------------------------------------------------------------------------
# Don't override:
# Internal method to do *proper* argument -> class/key mapping.
sub class_from_key {
my ($val) = @_;
my ( $class, $key ) = $val =~ /^((?:[a-zA-Z0-9]+__)+[a-zA-Z0-9]+)_([\w\[\]]+)$/;
$class =~ s/__/::/g if $class;
return ( $class, $key );
}
sub pkgkey {
my $class = shift;
$class =~ s/::/__/g;
return $class . "_";
}
sub errdiv {
my ( $class, $errs, $key ) = @_;
return "" unless $errs;
# $errs can be a hashref of { $class => LJ::Error::SettingSave::Foo } or a map of
# { $errfield => $errtxt }. this converts the former to latter.
if ( my $classerr = $errs->{$class} ) {
$errs = $classerr->field('map');
}
my $err = $errs->{$key} or return "";
# TODO: red is temporary. move to css.
return "<div style='color: red' class='ljinlinesettingerror'>$err</div>";
}
# don't override this.
sub errors {
my ( $class, %map ) = @_;
my $errclass = $class;
$errclass =~ s/^([a-zA-Z0-9]+)::Setting:://;
$errclass = "$1::Error::SettingSave::" . $errclass;
eval "\@${errclass}::ISA = ( 'LJ::Error::SettingSave' );";
my $eo = eval { $errclass->new( map => \%map ) };
$eo->throw;
}
# gets a key out of the $args hash, which can be either \%POST or a class-specific one
sub get_arg {
my ( $class, $args, $which ) = @_;
my $key = $class->pkgkey;
return $args->{"${key}$which"} || $args->{$which} || "";
}
# called like:
# LJ::Setting->error_map($u, \%POST, @multiple_setting_classnames)
# or:
# LJ::Setting::SpecificOption->error_map($u, \%POST)
# returns:
# undef if no errors found,
# LJ::SettingErrors object if any errors.
sub error_map {
my ( $class, $u, $post, @classes ) = @_;
if ( $class ne __PACKAGE__ ) {
croak("Can't call error_map on LJ::Setting subclass with \@classes set.") if @classes;
@classes = ($class);
}
my %errors;
foreach my $setclass (@classes) {
my $okay = eval { $setclass->error_check( $u, $post ); };
next if $okay;
$errors{$setclass} = $@;
}
return undef unless %errors;
return \%errors;
}
# save all of the settings that were changed
# $u: user whose settings we're changing
# $post: reference to %POST hash
# $all_settings: reference to array of all settings that are on this page
# returns any errors and the post args for each setting
sub save_all {
shift if $_[0] eq __PACKAGE__;
my ( $u, $post, $all_settings ) = @_;
my %posted; # class -> key -> value
my %returns;
while ( my ( $k, $v ) = each %$post ) {
my ( $class, $key ) = class_from_key($k);
next unless $class;
$posted{$class}{$key} = $v;
}
foreach my $class (@$all_settings) {
my $post_args = $posted{$class};
$post_args ||= {};
my $save_errors;
if ($post_args) {
my $sv = eval { $class->save( $u, $post_args ); };
if ( my $err = $@ ) {
$save_errors = $err->field('map') if ref $err;
}
}
$returns{$class}{save_errors} = $save_errors;
$returns{$class}{post_args} = $post_args;
}
return \%returns;
}
sub save_had_errors {
my $class = shift;
my $save_rv = shift;
return 0 unless ref $save_rv;
my @settings = @_; # optional, for specific settings
@settings = keys %$save_rv unless @settings;
foreach my $setting (@settings) {
my $errors = $save_rv->{$setting}->{save_errors} || {};
return 1 if %$errors;
}
return 0;
}
sub errors_from_save {
my $class = shift;
my $save_rv = shift;
return $save_rv->{$class}->{save_errors};
}
sub args_from_save {
my $class = shift;
my $save_rv = shift;
return $save_rv->{$class}->{post_args};
}
sub ml {
my ( $class, $code, $vars ) = @_;
# can pass in a string and check 2 places in order:
# 1) setting.foo.text => general .setting.foo.text (overridden by current page)
# 2) setting.foo.text => general setting.foo.text (defined in en(_LJ).dat)
# whether passed with or without a ".", eat that immediately
$code =~ s/^\.//;
# 1) try with a ., for current page override in 'general' domain
# 2) try without a ., for global version in 'general' domain
foreach my $curr_code ( ".$code", $code ) {
my $string = LJ::Lang::ml( $curr_code, $vars );
return "" if $string eq "_none";
return $string unless LJ::Lang::is_missing_string($string);
}
# return the class name if we didn't find anything
$class =~ /.+::(\w+)$/;
return $1;
}
package LJ::Error::SettingSave;
use base 'LJ::Error';
sub user_caused { 1 }
sub fields { qw(map); } # key -> english (keys are LJ::Setting:: subclass-defined)
sub as_string {
my $self = shift;
my $map = $self->field('map');
return join( ", ", map { $_ . '=' . $map->{$_} } sort keys %$map );
}
1;