# 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 "
$err
"; } # 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;