3 use base qw(BSE::UI::SiteuserCommon BSE::UI::Dispatch);
5 use BSE::Util::Tags qw(tag_error_img tag_hash tag_hash_plain tag_article);
7 use Constants qw($SHOP_FROM);
9 use BSE::SubscriptionTypes;
10 use BSE::SubscribedUsers;
12 use BSE::EmailRequests;
13 use BSE::Util::SQL qw/now_datetime/;
15 use BSE::CfgInfo qw(custom_class);
16 use BSE::WebUtil qw/refresh_to/;
17 use BSE::Util::Iterate;
18 use base 'BSE::UI::UserCommon';
21 our $VERSION = "1.015";
23 use constant MAX_UNACKED_CONF_MSGS => 3;
24 use constant MIN_UNACKED_CONF_GAP => 2 * 24 * 60 * 60;
28 show_logon => 'show_logon',
29 show_register => 'show_register',
30 register => 'register',
31 show_opts => 'show_opts',
37 download_file=>'download_file',
38 show_lost_password => 'show_lost_password',
39 lost_password => 'lost_password',
43 blacklist => 'blacklist',
46 setcookie => 'set_cookie',
47 nopassword => 'nopassword',
49 orderdetail => 'req_orderdetail',
50 orderdetaila => 'req_orderdetaila',
52 wishlist => 'req_wishlist',
53 downufile => 'req_downufile',
54 file_metadata => "req_file_metadata",
55 file_cmetadata => "req_file_cmetadata",
58 sub actions { \%actions }
60 sub action_prefix { '' }
62 sub default_action { 'userpage' }
64 my @donttouch = qw(id userId password email confirmed confirmSecret waitingForConfirmation disabled flags affiliate_name previousLogon);
65 my %donttouch = map { $_, $_ } @donttouch;
67 sub _refresh_userpage ($$) {
70 my $url = $cfg->entryErr('site', 'url') . "/cgi-bin/user.pl?userpage=1";
72 $url .= '&message='.escape_uri($msg);
77 # returns true if the userid cookie should be created
78 sub _should_make_user_cookie {
79 return BSE::Cfg->single->entry("basic", "make_userid_cookie", 1);
82 sub _send_user_cookie {
83 my ($self, $user) = @_;
85 $self->_should_make_user_cookie or return;
87 my $value = $user ? $user->userId : "";
89 BSE::Session->send_cookie
90 (BSE::Session->make_cookie(BSE::Cfg->single, userid => $value));
94 my ($self, $req, $message) = @_;
98 my $session = $req->session;
100 my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
103 return $self->req_nopassword($req);
106 $message ||= $cgi->param('message') || '';
107 if (my $msgid = $cgi->param('mid')) {
108 my $temp = $cfg->entry("messages", $msgid);
109 $message = $temp if $temp;
114 $message = $req->message($errors);
117 $message = escape_html($message);
121 $message = $req->message();
126 $req->dyn_user_tags(),
128 error_img => [ \&tag_error_img, $cfg, $errors ],
131 return $req->response('user/logon', \%acts);
138 description => "Logon name",
143 description => "Password",
149 my ($self, $req) = @_;
153 my $session = $req->session;
155 my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
158 return $self->req_nopassword($req);
160 my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
162 $req->validate(fields => \%logon_fields,
164 section => "Logon Fields");
166 my $userid = $cgi->param("userid");
167 my $password = $cgi->param("password");
168 unless (keys %errors) {
169 $user = SiteUsers->getBy(userId => $userid);
170 my $error = "INVALID";
171 unless ($user && $user->check_password($password, \$error)) {
172 if ($error eq "INVALID") {
173 $errors{_} = $msgs->(baduserpass=>"Invalid username or password");
176 $errors{_} = $msgs->(passwordload => "Error loading password module");
180 if (!keys %errors && $user->{disabled}) {
181 $errors{_} = $msgs->(disableduser=>"Account $userid has been disabled");
185 and return $self->req_show_logon($req, \%errors);
187 my %fields = $user->valid_fields($cfg);
188 my $custom = custom_class($cfg);
190 for my $field ($custom->siteuser_edit_required($req, $user)) {
191 $fields{$field}{required} = 1;
193 my %rules = $user->valid_rules($cfg);
195 $req->validate_hash(data => $user,
199 section => 'site user validation');
200 _validate_affiliate_name($cfg, $user->{affiliate_name}, \%errors, $msgs, $user);
202 delete $session->{userid};
203 $session->{partial_logon} = $user->id;
204 return $self->req_show_opts($req, undef, \%errors);
207 $session->{userid} = $user->id;
208 $user->{previousLogon} = $user->{lastLogon};
209 $user->{lastLogon} = now_datetime;
212 if ($custom->can('siteuser_login')) {
213 $custom->siteuser_login($session->{_session_id}, $session->{userid},
216 $self->_send_user_cookie($user);
218 _got_user_refresh($session, $cgi, $cfg);
221 sub _got_user_refresh {
222 my ($session, $cgi, $cfg) = @_;
224 my $baseurl = $cfg->entryVar('site', 'url');
225 my $securl = $cfg->entryVar('site', 'secureurl');
226 my $need_magic = $baseurl ne $securl;
228 my $debug = $cfg->entryBool('debug', 'logon_cookies', 0);
230 print STDERR "Logon Cookies Debug\n" if $debug;
232 # which host are we on?
233 # first get info about the 2 possible hosts
234 my ($baseprot, $basehost, $baseport) =
235 $baseurl =~ m!^(\w+)://([\w.-]+)(?::(\d+))?!;
236 $baseport ||= $baseprot eq 'http' ? 80 : 443;
237 print STDERR "Base: prot: $baseprot Host: $basehost Port: $baseport\n"
240 #my ($secprot, $sechost, $secport) =
241 # $securl =~ m!^(\w+)://([\w.-]+)(?::(\d+))?!;
243 # get info about the current host
244 my $port = $ENV{SERVER_PORT} || 80;
245 my $ishttps = exists $ENV{HTTPS} || exists $ENV{SSL_CIPHER};
246 print STDERR "\$ishttps: $ishttps\n" if $debug;
247 my $protocol = $ishttps ? 'https' : 'http';
249 if (lc $ENV{SERVER_NAME} ne lc $basehost
250 || lc $protocol ne $baseprot
251 || $baseport != $port) {
252 print STDERR "not on base host ('$ENV{SERVER_NAME}' cmp '$basehost' '$protocol cmp '$baseprot' $baseport cmp $port\n";
256 my $refresh = $cgi->param('r');
258 if ($session->{userid}) {
259 $refresh = "$ENV{SCRIPT_NAME}?userpage=1";
262 $refresh = "$ENV{SCRIPT_NAME}?show_logon=1";
266 my $url = $onbase ? $securl : $baseurl;
267 my $finalbase = $onbase ? $baseurl : $securl;
268 $refresh = $finalbase . $refresh unless $refresh =~ /^\w+:/;
269 print STDERR "Heading to $url to setcookie\n" if $debug;
270 $url .= "$ENV{SCRIPT_NAME}?setcookie=".$session->{_session_id};
271 $url .= "&r=".escape_uri($refresh);
275 refresh_to($refresh);
282 my ($self, $req) = @_;
286 my $session = $req->session;
288 my $debug = $cfg->entryBool('debug', 'logon_cookies', 0);
289 my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
290 my $cookie = $cgi->param('setcookie')
291 or return $self->req_show_logon($req,
292 $msgs->(nocookie=>"No cookie provided"));
293 print STDERR "Setting sessionid to $cookie for $ENV{HTTP_HOST}\n";
295 BSE::Session->change_cookie($session, $cfg, $cookie, \%newsession);
296 if (exists $session->{cart} && !exists $newsession{cart}) {
297 $newsession{cart} = $session->{cart};
298 $newsession{custom} = $session->{custom} if exists $session->{custom};
300 my $refresh = $cgi->param('r')
301 or return $self->req_show_logon($req,
302 $msgs->(norefresh=>"No refresh provided"));
303 my $userid = $newsession{userid};
306 $user = SiteUsers->getBy(userId => $userid);
308 $self->_send_user_cookie($user);
310 refresh_to($refresh);
316 my ($self, $req) = @_;
320 my $session = $req->session;
322 my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
325 return $self->req_nopassword($req);
328 my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
329 my $userid = $session->{userid}
330 or return $self->req_show_logon($req,
331 $msgs->(notloggedon=>"You aren't logged on"));
333 delete $session->{userid};
334 $session->{cart} = [];
335 $self->_send_user_cookie();
337 my $custom = custom_class($cfg);
338 if ($custom->can('siteuser_logout')) {
339 $custom->siteuser_logout($session->{_session_id}, $cfg);
342 _got_user_refresh($session, $cgi, $cfg);
347 sub tag_if_subscribed_register {
348 my ($cgi, $cfg, $subs, $rsub_index) = @_;
350 return 0 if $$rsub_index < 0 or $$rsub_index >= @$subs;
351 my $sub = $subs->[$$rsub_index];
352 if ($cgi->param('checkedsubs')) {
353 my @checked = $cgi->param('subscription');
354 return grep($sub->{id} == $_, @checked) != 0;
357 my $def = $cfg->entryBool('site users', 'subscribe_all', 0);
359 return $cfg->entryBool('site users', "subscribe_$sub->{id}", $def);
363 sub tag_if_required {
364 my ($cfg, $args) = @_;
366 return $cfg->entryBool('site users', "require_$args", 0);
369 sub req_show_register {
370 my ($self, $req, $message, $errors) = @_;
374 my $session = $req->session;
376 my $user_register = $cfg->entryBool('site users', 'user_register', 1);
377 my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
378 unless ($user_register) {
380 return $self->req_show_lost_password($req,
381 "Registration disabled");
384 return $self->req_show_logon($req,
385 "Registration disabled");
389 $message ||= $cgi->param('message');
390 if (defined $message) {
391 $message = escape_html($message);
395 my @keys = $cgi->param();
396 my %errors_copy = %$errors;
397 my @errors = grep defined, delete @errors_copy{@keys};
398 push @errors, values %errors_copy;
399 $message = join("<br />", map escape_html($_), @errors);
406 my @subs = grep $_->{visible}, BSE::SubscriptionTypes->all;
411 $req->dyn_user_tags(),
414 my $value = $cgi->param($_[0]);
415 defined $value or $value = '';
419 BSE::Util::Tags->make_iterator(\@subs, 'subscription', 'subscriptions',
422 [ \&tag_if_subscribed_register, $cgi, $cfg, \@subs, \$sub_index ],
424 [ \&tag_if_required, $cfg ],
425 error_img => [ \&tag_error_img, $cfg, $errors ],
428 my $template = 'user/register';
429 return $req->dyn_response($template, \%acts);
433 my ($self, $req, $name, $result) = @_;
435 defined $result or confess "Missing result parameter";
439 my $session = $req->session;
440 my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
443 $password = $cgi->param($name) if $name;
444 $password ||= $cgi->param('p');
445 my $uid = $cgi->param('u');
446 defined $uid && $uid =~ /^\d+$/ && defined $password
447 or do { refresh_to($ENV{SCRIPT}."?nopassword=1"); return };
449 my $user = SiteUsers->getByPkey($uid)
450 or do { refresh_to($ENV{SCRIPT}."?nopassword=1"); return };
452 $user->{password} eq $password
453 or do { refresh_to($ENV{SCRIPT}."?nopassword=1"); return };
458 if ($cfg->entryBool('custom', 'user_auth')) {
459 my $custom = custom_class($cfg);
461 return $custom->siteuser_auth($session, $cgi, $cfg);
464 my $user = $req->siteuser;
466 $$result = $self->req_show_logon($req);
469 if ($user->{disabled}) {
470 $$result = $self->req_show_logon($req, "Account disabled");
479 sub tag_ifSubscribedTo {
480 my ($user, $args) = @_;
482 require BSE::TB::Subscriptions;
483 my $sub = BSE::TB::Subscriptions->getBy(text_id=>$args)
486 $user->subscribed_to($sub);
490 my ($self, $req) = @_;
492 my $session = $req->session;
493 if ($session->{partial_logon}
494 && !$req->cfg->entryBool('custom', 'user_auth')) {
495 my $user = SiteUsers->getByPkey($session->{partial_logon})
505 my ($self, $req, $message, $errors) = @_;
509 my $session = $req->session;
511 my $partial_logon = 0;
512 my $user = $self->_partial_logon($req)
513 and ++$partial_logon;
515 if ($partial_logon) {
516 $cgi->param('t' => undef);
521 $user = $self->_get_user($req, 'show_opts', \$result)
525 my @subs = grep $_->{visible}, BSE::SubscriptionTypes->all;
526 my @usersubs = BSE::SubscribedUsers->getBy(userId=>$user->{id});
527 my %usersubs = map { $_->{subId}, $_ } @usersubs;
531 $message ||= $cgi->param('message');
532 if (defined $message) {
533 $message = escape_html($message);
537 $message = $req->message($errors);
543 require BSE::TB::OwnedFiles;
544 my @file_cats = BSE::TB::OwnedFiles->categories($cfg);
545 my %subbed = map { $_ => 1 } $user->subscribed_file_categories;
546 for my $cat (@file_cats) {
547 $cat->{subscribed} = exists $subbed{$cat->{id}} ? 1 : 0;
550 my $it = BSE::Util::Iterate->new;
554 $self->_common_tags($req, $user),
557 my $value = $cgi->param($_[0]);
558 defined $value or $value = $user->{$_[0]};
559 defined $value or $value = '';
563 BSE::Util::Tags->make_iterator(\@subs, 'subscription', 'subscriptions',
565 ifSubscribed=>sub { $usersubs{$subs[$sub_index]{id}} },
566 ifAnySubs => sub { @usersubs },
568 [ \&tag_if_required, $cfg ],
569 error_img => [ \&tag_error_img, $cfg, $errors ],
570 $self->_edit_tags($user, $cfg),
571 ifSubscribedTo => [ \&tag_ifSubscribedTo, $user ],
572 partial_logon => $partial_logon,
581 my $base = 'user/options';
583 return $req->dyn_response($base, \%acts);
587 my ($user, $errors, $email, $cgi, $msgs, $nopassword) = @_;
590 $errors->{email} = $msgs->(optsnoemail => "Please enter an email address");
592 elsif ($email !~ /.@./) {
593 $errors->{email} = $msgs->(optsbademail=>
594 "Please enter a valid email address");
597 if ($nopassword && $email ne $user->{email}) {
598 my $conf_email = $cgi->param('confirmemail');
600 if ($conf_email eq $email) {
601 my $other = SiteUsers->getBy(userId=>$email);
604 $msgs->(optsdupemail =>
605 "That email address is already in use");
609 $errors->{confirmemail} =
610 $msgs->(optsconfemailnw=>
611 "Confirmation email address doesn't match email address");
615 $errors->{confirmemail} =
616 $msgs->(optsnoconfemail=> "Please enter a confirmation email address");
621 if (!$errors->{email}) {
622 my $checkemail = _generic_email($email);
623 require 'BSE/EmailBlacklist.pm';
624 my $blackentry = BSE::EmailBlacklist->getEntry($checkemail);
627 $msgs->(optsblackemail =>
628 "Email $email is blacklisted: $blackentry->{why}",
629 $email, $blackentry->{why});
635 my ($self, $req) = @_;
639 my $session = $req->session;
641 my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
643 my $partial_logon = 0;
644 my $user = $self->_partial_logon($req)
645 and ++$partial_logon;
649 $user = $self->_get_user($req, undef, \$result)
653 my $custom = custom_class($cfg);
654 if ($cfg->entry('custom', 'saveopts')) {
657 $custom->siteuser_saveopts($user, $req);
660 return $self->req_show_opts($req, $@);
664 my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
667 unless ($nopassword) {
668 my $oldpass = $cgi->param('old_password');
669 $newpass = $cgi->param('password');
670 my $confirm = $cgi->param('confirm_password');
672 if (defined $newpass && length $newpass) {
675 if (!$user->check_password($oldpass, \$error)) {
676 sleep 5; # yeah, it's ugly
677 $errors{old_password} = $msgs->(optsbadold=>"You need to enter your old password to change your password")
681 if (!SiteUser->check_password_rules($newpass, \$error)) {
682 my ($code, @more) = @$error;
683 $errors{password} = $req->catmsg("msg:bse/user/$code", \@more)
685 elsif (!defined $confirm || length $confirm == 0) {
686 $errors{confirm_password} = $msgs->(optsconfpass=>"Please enter a confirmation password");
688 elsif ($newpass ne $confirm) {
689 $errors{confirm_password} = $msgs->(optsconfmismatch=>"The confirmation password is different from the password");
694 $errors{old_password} =
695 $msgs->(optsoldpass=>"You need to enter your old password to change your password")
699 my $email = $cgi->param('email');
701 if (defined $email) {
703 _checkemail($user, \%errors, $email, $cgi, $msgs, $nopassword);
707 my @cols = grep !$donttouch{$_}, SiteUser->columns;
708 for my $col (@cols) {
709 my $value = $cgi->param($col);
710 if ($cfg->entryBool('site users', "require_$col")) {
711 if (defined $value && $value eq '') {
712 my $disp = $cfg->entry('site users', "display_$col", "\u$col");
713 $errors{$col} = $msgs->("optsrequired" =>
714 "$disp is a required field", $col, $disp);
718 my %fields = $user->valid_fields($cfg);
719 unless ($partial_logon) {
720 # only test fields for values supplied
721 my @remove = grep !defined $cgi->param($_), keys %fields;
722 delete @fields{@remove};
724 my %rules = $user->valid_rules($cfg);
725 $req->validate(errors => \%errors,
728 section => 'site user validation');
730 my $aff_name = $cgi->param('affiliate_name');
731 $aff_name = _validate_affiliate_name($cfg, $aff_name, \%errors, $msgs, $user);
733 $self->_save_images($cfg, $cgi, $user, \%errors);
736 and return $self->req_show_opts($req, undef, \%errors);
738 if ($saveemail && $email ne $user->{email}) {
739 $user->{confirmed} = 0;
740 $user->{confirmSecret} = '';
741 $user->{email} = $email;
742 $user->{userId} = $email if $nopassword;
745 if (!$nopassword && $newpass) {
746 $user->changepw($newpass, $user);
749 $user->{affiliate_name} = $aff_name if defined $aff_name;
751 for my $col (@cols) {
752 my $value = $cgi->param($col);
753 if (defined $value) {
754 $user->{$col} = $value;
758 $user->{textOnlyMail} = 0
759 if $cgi->param('saveTextOnlyMail') && !defined $cgi->param('textOnlyMail');
760 $user->{keepAddress} = 0
761 if $cgi->param('saveKeepAddress') && !defined $cgi->param('keepAddress');
766 if ($cgi->param('saveSubscriptions')) {
767 $subs = $self->_save_subs($user, $session, $cfg, $cgi);
770 $custom->can('siteuser_edit')
771 and $custom->siteuser_edit($user, 'user', $cfg);
774 return $self->send_conf_request($req, $user)
778 $subs = () = $user->subscriptions unless defined $subs;
779 return $self->send_conf_request($req, $user)
780 if $subs && !$user->{confirmed};
783 if ($cgi->param('save_file_subs')) {
784 my @new_subs = $cgi->param("file_subscriptions");
785 $user->set_subscribed_file_categories($cfg, @new_subs);
788 if ($partial_logon) {
789 $user->{previousLogon} = $user->{lastLogon};
790 $user->{lastLogon} = now_datetime;
791 $session->{userid} = $user->id;
792 delete $session->{partial_logon};
795 my $custom = custom_class($cfg);
796 if ($custom->can('siteuser_login')) {
797 $custom->siteuser_login($session->{_session_id}, $session->{userid}, $cfg);
800 $self->_send_user_cookie($user);
802 _got_user_refresh($session, $cgi, $cfg);
806 my $url = $cgi->param('r');
808 $url = $cfg->entryErr('site', 'url') . "$ENV{SCRIPT_NAME}?userpage=1";
810 $url =~ s/1$/$user->{password}/;
811 $url .= "&u=$user->{id}";
813 my $t = $cgi->param('t');
814 if ($t && $t =~ /^\w+$/) {
819 $custom->siteusers_changed($cfg);
826 # returns true if the caller needs to send output
828 my ($self, $user, $session, $cfg, $cgi) = @_;
830 my @subids = $cgi->param('subscription');
831 $user->removeSubscriptions;
835 my @cols = BSE::SubscribedUser->columns;
836 shift @cols; # don't set id
838 for my $subid (@subids) {
839 $subid =~ /^\d+$/ or next;
840 my $sub = BSE::SubscriptionTypes->getByPkey($subid)
844 $usersub{subId} = $subid;
845 $usersub{userId} = $user->{id};
847 push(@usersubs, BSE::SubscribedUsers->add(@usersub{@cols}));
856 my ($self, $req) = @_;
860 my $session = $req->session;
862 my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
864 my $user_register = $cfg->entryBool('site users', 'user_register', 1);
865 my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
866 unless ($user_register) {
867 my $msg = $msgs->(regdisabled => "Registration disabled");
869 return $self->req_show_lost_password($req, $msg);
872 return $self->req_show_logon($req, $msg);
877 my @cols = SiteUser->columns;
879 for my $field (@cols) {
884 my %fields = SiteUser->valid_fields($cfg);
885 my %rules = SiteUser->valid_rules($cfg);
886 $req->validate(errors => \%errors,
889 section => 'site user validation');
891 my $email = $cgi->param('email');
892 if (!defined $email or !length $email) {
893 $errors{email} = $msgs->(regnoemail => "Please enter an email address");
894 $email = ''; # prevent undefined value warnings later
896 elsif ($email !~ /.\@./) {
897 $errors{email} = $msgs->(regbademail => "Please enter a valid email address");
900 my $confemail = $cgi->param('confirmemail');
901 if (!defined $confemail or !length $confemail) {
902 $errors{confirmemail} = $msgs->(regnoconfemail => "Please enter a confirmation email address");
904 elsif ($email ne $confemail) {
905 $errors{confirmemail} = $msgs->(regbadconfemail => "Confirmation email must match the email address");
907 my $user = SiteUsers->getBy(userId=>$email);
909 $errors{email} = $msgs->(regemailexists=>
910 "Sorry, email $email already exists as a user",
913 $user{userId} = $email;
914 $user{password} = '';
917 my $min_pass_length = $cfg->entry('basic', 'minpassword') || 4;
918 my $userid = $cgi->param('userid');
919 if (!defined $userid || length $userid == 0) {
920 $errors{userid} = $msgs->(reguser=>"Please enter your username");
922 my $pass = $cgi->param('password');
923 my $pass2 = $cgi->param('confirm_password');
924 if (!defined $pass || length $pass == 0) {
925 $errors{password} = $msgs->(regpass=>"Please enter your password");
927 elsif (length $pass < $min_pass_length) {
928 $errors{password} = $msgs->(regpasslen=>"The password must be at least $min_pass_length characters");
930 elsif (!defined $pass2 || length $pass2 == 0) {
931 $errors{confirm_password} =
932 $msgs->(regconfpass=>"Please enter a confirmation password");
934 elsif ($pass ne $pass2) {
935 $errors{confirm_password} =
936 $msgs->(regconfmismatch=>"The confirmation password is different from the password");
938 my $user = SiteUsers->getBy(userId=>$userid);
940 # give the user a suggestion
941 my $workuser = $userid;
942 $workuser =~ s/\d+$//;
944 for my $suffix (1..100) {
945 unless (SiteUsers->getBy(userId=>"$workuser$suffix")) {
946 $cgi->param(userid=>"$workuser$suffix");
950 $errors{userid} = $msgs->(regexists=>
951 "Sorry, username $userid already exists",
954 $user{userId} = $userid;
955 $user{password} = $pass;
958 unless ($errors{email}) {
959 my $checkemail = _generic_email($email);
960 require 'BSE/EmailBlacklist.pm';
961 my $blackentry = BSE::EmailBlacklist->getEntry($checkemail);
963 $errors{email} = $msgs->(regblackemail =>
964 "Email $email is blacklisted: $blackentry->{why}",
965 $email, $blackentry->{why});
969 my @mod_cols = grep !$donttouch{$_}, @cols;
970 for my $col (@mod_cols) {
971 my $value = $cgi->param($col);
972 if ($cfg->entryBool('site users', "require_$col")) {
973 unless (defined $value && $value ne '') {
974 my $disp = $cfg->entry('site users', "display_$col", "\u$col");
976 $errors{$col} = $msgs->(regrequired => "$disp is a required field",
980 if (defined $value) {
981 $user{$col} = $value;
984 my $aff_name = $cgi->param('affiliate_name');
985 $aff_name = _validate_affiliate_name($cfg, $aff_name, \%errors, $msgs);
986 defined $aff_name or $aff_name = '';
989 return $self->req_show_register($req, undef, \%errors);
992 $user{email} = $email;
993 $user{lastLogon} = $user{whenRegistered} =
994 $user{previousLogon} = now_datetime;
995 $user{keepAddress} = 0;
996 $user{wantLetter} = 0;
997 $user{affiliate_name} = $aff_name;
999 use BSE::Util::Secure qw/make_secret/;
1000 $user{password} = make_secret($cfg);
1005 $user = SiteUsers->make(%user);
1008 my $custom = custom_class($cfg);
1009 $custom->can('siteuser_add')
1010 and $custom->siteuser_add($user, 'user', $cfg);
1016 component => "member:register:created",
1017 msg => "New user created",
1021 $self->_send_user_cookie($user);
1022 unless ($nopassword) {
1023 $session->{userid} = $user->id;
1024 my $custom = custom_class($cfg);
1025 if ($custom->can('siteuser_login')) {
1026 $custom->siteuser_login($session->{_session_id}, $session->{userid}, $cfg);
1030 my $subs = $self->_save_subs($user, $session, $cfg, $cgi);
1032 return $self->send_conf_request($req, $user);
1035 return if $self->send_conf_request($req, $user, 1);
1037 elsif ($cfg->entry('site users', 'notify_register_customer')) {
1038 $user->send_registration_notify
1040 remote_addr => $req->ip_address
1044 _got_user_refresh($session, $cgi, $cfg);
1046 $custom->siteusers_changed($cfg);
1048 if ($cfg->entry('site users', 'notify_register', 0)) {
1049 $self->_notify_registration($req, $user);
1053 $self->req_show_register($req, $msgs->(regdberr=> "Database error $@"));
1062 $user->subscribed_services;
1065 sub iter_sembookings {
1068 $user->seminar_bookings_detail;
1071 sub tag_order_item_options {
1072 my ($self, $req, $ritem) = @_;
1075 or return "** only usable in the items iterator **";
1078 require BSE::Shop::Util;
1079 BSE::Shop::Util->import(qw/order_item_opts nice_options/);
1081 if ($item->{options}) {
1084 my $product = Products->getByPkey($item->{productId});
1086 @options = order_item_opts($req, $item, $product);
1089 @options = order_item_opts($req, $item);
1092 return nice_options(@options);
1096 my ($self, $user) = @_;
1098 require BSE::TB::Orders;
1099 return sort { $b->{orderDate} cmp $a->{orderDate}
1100 || $b->{id} <=> $a->{id} }
1101 grep $_->complete, BSE::TB::Orders->getBy(userId=>$user->{userId});
1104 sub iter_order_items {
1105 my ($self, $rorder) = @_;
1107 $$rorder or return "** Not in the order iterator **";
1109 return $$rorder->items;
1112 sub iter_orderfiles {
1113 my ($self, $rorder) = @_;
1117 return BSE::DB->query(orderFiles => $$rorder->id);
1121 my ($self, $req, $user) = @_;
1123 my $cfg = $req->cfg;
1136 my $must_be_paid = $cfg->entryBool('downloads', 'must_be_paid', 0);
1137 my $must_be_filled = $cfg->entryBool('downloads', 'must_be_filled', 0);
1139 my $it = BSE::Util::Iterate->new(req => $req);
1142 $req->dyn_user_tags(),
1143 user => [ \&tag_hash, $user ],
1149 #index => \$order_index,
1150 code => [ iter_orders => $self, $user ],
1157 code => [ iter_order_items => $self, \$order ],
1162 $product = $item->product
1163 or print STDERR "No product found for item $item->{id}\n";
1168 $req->set_article(product => $product);
1174 single => "orderfile",
1175 plural => "orderfiles",
1176 code => [ iter_orderfiles => $self, \$order ],
1181 $item or return "* Not in item iterator *";
1182 $product or return "* No current product *";
1183 return tag_article($product, $cfg, $_[0]);
1187 single => "prodfile",
1188 plural => "prodfiles",
1189 code => [ files => $product ],
1196 return 1 if !$file->{forSale};
1198 return 0 if $must_be_paid && !$order->paidFor;
1199 return 0 if $must_be_filled && !$order->filled;
1202 options => [ tag_order_item_options => $self, $req, \$item ],
1207 my ($self, $req, $message) = @_;
1209 my $cfg = $req->cfg;
1210 my $cgi = $req->cgi;
1211 my $session = $req->session;
1214 $message = escape_html($message);
1217 $message = $req->message;
1221 my $user = $self->_get_user($req, 'userpage', \$result)
1223 $message ||= $cgi->param('message') || '';
1225 my $it = BSE::Util::Iterate->new;
1228 $self->_common_tags($req, $user),
1229 message => $message,
1230 $it->make_iterator([ \&iter_usersubs, $user ],
1231 'subscription', 'subscriptions'),
1232 $it->make_iterator([ \&iter_sembookings, $user ],
1233 'booking', 'bookings'),
1235 my $base_template = 'user/userpage';
1237 return $req->dyn_response($base_template, \%acts);
1240 sub tag_detail_product {
1241 my ($ritem, $products, $field) = @_;
1243 $$ritem or return '';
1244 my $product = $products->{$$ritem->{productId}}
1247 defined $product->{$field} or return '';
1249 return escape_html($product->{$field});
1252 sub iter_detail_productfiles {
1253 my ($ritem, $files) = @_;
1257 grep $$ritem->{productId} == $_->{articleId}, @$files;
1260 sub tag_detail_ifFileAvail {
1261 my ($order, $rfile, $must_be_paid, $must_be_filled) = @_;
1263 $$rfile or return 0;
1264 $$rfile->{forSale} or return 1;
1266 return 0 if $must_be_paid && !$order->{paidFor};
1267 return 0 if $must_be_filled && !$order->{filled};
1274 Display an order detail for an order for the currently logged in user.
1282 id - order id (the logged in user must own this order)
1286 See _orderdetail_low for tags.
1288 Template: user/orderdetail
1292 sub req_orderdetail {
1293 my ($self, $req, $message) = @_;
1295 my $cgi = $req->cgi;
1298 my $user = $self->_get_user($req, 'userpage', \$result)
1300 my $order_id = $cgi->param('id');
1302 if (defined $order_id && $order_id =~ /^\d+$/) {
1303 require BSE::TB::Orders;
1304 $order = BSE::TB::Orders->getByPkey($order_id);
1306 $order->{userId} eq $user->{userId} || $order->{siteuser_id} == $user->{id}
1309 or return $self->req_userpage($req, "No such order");
1311 return $self->_orderdetail_low($req, $order, $message, "user/orderdetail", 0);
1318 Display an order detail for an order identified by the order's
1331 See _orderdetail_low for tags.
1333 Template: user/orderdetaila
1337 sub req_orderdetaila {
1338 my ($self, $req, $message) = @_;
1340 my $cgi = $req->cgi;
1343 my $order_id = $cgi->param('id');
1345 if (defined $order_id && $order_id =~ /^[a-f0-9]{32,}$/) {
1346 require BSE::TB::Orders;
1347 ($order) = BSE::TB::Orders->getBy(randomId => $order_id);
1350 or return $self->req_show_logon($req, "No such order");
1352 return $self->_orderdetail_low($req, $order, $message, "user/orderdetaila", 1);
1355 *req_oda = \&req_orderdetaila;
1357 =item _orderdetail_low
1359 Common tags for orderdetail and orderdetaila.
1365 order I<field> - field from the order.
1373 item I<field> - access to the items in the order
1379 orderfile I<field> - access to files bought in the order. Note: the
1380 user will need to logon to download forSale files, even from the
1381 anonymous order detail page.
1387 sub _orderdetail_low {
1388 my ($self, $req, $order, $message, $template, $anon) = @_;
1390 my $cfg = $req->cfg;
1391 my $cgi = $req->cgi;
1393 $message ||= $cgi->param('message') || '';
1395 my $must_be_paid = $cfg->entryBool('downloads', 'must_be_paid', 0);
1396 my $must_be_filled = $cfg->entryBool('downloads', 'must_be_filled', 0);
1398 my @items = $order->items;
1399 my @files = $order->files;
1400 my @products = $order->products;
1401 my %products = map { $_->{id} => $_ } @products;
1405 my $it = BSE::Util::Iterate->new;
1410 $req->dyn_user_tags(),
1412 message => sub { escape_html($message) },
1416 return $req->dyn_response($template, \%acts);
1420 my ($self, $req) = @_;
1422 my $cfg = $req->cfg;
1423 my $cgi = $req->cgi;
1424 my $session = $req->session;
1426 my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
1428 my $user = $self->_get_user($req, 'show_opts', \$result)
1431 my $orderid = $cgi->param('order')
1432 or return _refresh_userpage($cfg, $msgs->('noorderid', "No order id supplied"));
1433 require BSE::TB::Orders;
1434 my $order = BSE::TB::Orders->getByPkey($orderid)
1435 or return _refresh_userpage($cfg, $msgs->('nosuchorder',
1436 "No such order $orderid", $orderid));
1437 unless (length $order->{userId}
1438 && $order->{userId} eq $user->{userId}) {
1439 return _refresh_userpage($cfg, $msgs->("notyourorder",
1440 "Order $orderid isn't yours", $orderid));
1442 my $itemid = $cgi->param('item')
1443 or return _refresh_userpage($cfg, $msgs->('noitemid', "No item id supplied"));
1444 require BSE::TB::OrderItems;
1445 my ($item) = grep $_->{id} == $itemid,
1446 BSE::TB::OrderItems->getBy(orderId=>$order->{id})
1447 or return _refresh_userpage($cfg, $msgs->(notinorder=>"Not part of that order"));
1448 require BSE::TB::ArticleFiles;
1449 my @files = BSE::TB::ArticleFiles->getBy(articleId=>$item->{productId})
1450 or return _refresh_userpage($cfg, $msgs->(nofilesonline=>"No files in this line"));
1451 my $fileid = $cgi->param('file')
1452 or return _refresh_userpage($cfg, $msgs->(nofileid=>"No file id supplied"));
1453 my ($file) = grep $_->{id} == $fileid, @files
1454 or return _refresh_userpage($cfg, $msgs->(nosuchfile=>"No such file in that line item"));
1456 my $must_be_paid = $cfg->entryBool('downloads', 'must_be_paid', 0);
1457 my $must_be_filled = $cfg->entryBool('downloads', 'must_be_filled', 0);
1458 if ($must_be_paid && !$order->{paidFor} && $file->{forSale}) {
1459 return _refresh_userpage($cfg, $msgs->("paidfor",
1460 "Order not marked as paid for"));
1462 if ($must_be_filled && !$order->{filled} && $file->{forSale}) {
1463 return _refresh_userpage($cfg, $msgs->("filled",
1464 "Order not marked as filled"));
1467 my $filebase = $cfg->entryVar('paths', 'downloads');
1468 my $filename = "$filebase/$file->{filename}";
1470 or return _refresh_userpage($cfg,
1472 "Sorry, cannot open that file. Contact the webmaster.",
1476 # downloads over https of non-HTML to IE causes a confusing error
1477 # if cache-control is "no-cache". Avoid setting that.
1478 no_cache_dynamic => 0,
1481 $result{content_filename} = $filename;
1482 push @headers, "Content-Length: $file->{sizeInBytes}";
1483 if ($file->{download}) {
1484 $result{type} = "application/octet-stream";
1486 qq/Content-Disposition: attachment; filename=$file->{displayName}/;
1489 $result{type} = $file->{contentType};
1491 qq/Content-Disposition: inline; filename=$file->{displayName}/;
1493 $result{headers} = \@headers;
1498 sub req_download_file {
1499 my ($self, $req) = @_;
1501 my ($fileid) = split '/', $self->rest;
1503 my $cfg = $req->cfg;
1504 my $cgi = $req->cgi;
1505 my $session = $req->session;
1507 my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
1508 my $userid = $session->{userid};
1511 $user = SiteUsers->getByPkey($userid);
1513 $fileid ||= $cgi->param('file')
1514 or return $self->req_show_logon($req,
1515 $msgs->('nofileid', "No file id supplied"));
1516 require BSE::TB::ArticleFiles;
1519 my $article_id = $cgi->param('page');
1522 if ($article_id eq '-1') {
1523 ($file) = grep $_->{name} eq $fileid, Articles->global_files;
1525 elsif ($article_id =~ /\A\d+\z/) {
1526 $article = Articles->getByPkey($article_id)
1527 or return $self->req_show_logon($req,
1528 $msgs->('nosucharticle', "No such article"));
1530 elsif ($article_id =~ /\A[a-zA-Z0-9-_]+\z/) {
1531 ($article) = Articles->getBy(linkAlias => $article_id)
1532 or return $self->req_show_logon($req,
1533 $msgs->('nosucharticle', "No such article"));
1536 return $self->req_show_logon($req, $msgs->('invalidarticle', "Invalid article id"));
1539 ($file) = grep $_->{name} eq $fileid, $article->files;
1542 $file = BSE::TB::ArticleFiles->getByPkey($fileid);
1545 or return $self->req_show_logon($req,
1546 $msgs->('nosuchfile', "No such download"));
1547 $cfg->entryBool('downloads', 'require_logon', 0) && !$user
1548 and return $self->req_show_logon($req,
1549 $msgs->('downloadlogonall',
1550 "You must be logged on to download files"));
1552 $file->{requireUser} && !$user
1553 and return $self->req_show_logon($req,
1554 $msgs->('downloadlogon',
1555 "You must be logged on to download this file"));
1556 if ($file->forSale) {
1557 unless ($user && $file->downloadable_by($user)) {
1558 return $self->req_show_logon($req,
1559 $msgs->('downloadforsale',
1560 "This file can only be downloaded as part of an order"));
1564 # check the user has access to this file (RT#531)
1565 if ($file->{articleId} != -1) {
1567 $article ||= Articles->getByPkey($file->{articleId})
1568 or return $self->req_show_logon($req,
1569 $msgs->('downloadarticle',
1570 "Could not load article for file"));
1571 if ($article->is_dynamic && !$req->siteuser_has_access($article)) {
1572 if ($req->siteuser) {
1573 return $self->req_userpage($req, $msgs->('downloadnoaccess',
1574 "You do not have access to this article"));
1577 my $cfg = $req->cfg;
1578 my $refresh = "/cgi-bin/user.pl?file=$fileid";
1580 $cfg->entry('site', 'url') . "/cgi-bin/user.pl?show_logon=1&r=".escape_uri($refresh)."&message=You+need+to+logon+download+this+file";
1587 # this this file is on an external storage, and qualifies for
1588 # external storage send the user to get it from there
1589 if ($file->{src} && $file->{storage} ne 'local'
1590 && !$file->{forSale} && !$file->{requireUser}
1591 && (!$article || !$article->is_access_controlled)) {
1592 refresh_to($file->{src});
1596 my $filebase = $cfg->entryVar('paths', 'downloads');
1597 my $filename = "$filebase/$file->{filename}";
1599 or return $self->req_show_logon($req,
1601 "Sorry, cannot open that file. Contact the webmaster.",
1606 # downloads over https of non-HTML to IE causes a confusing error
1607 # if cache-control is "no-cache". Avoid setting that.
1608 no_cache_dynamic => 0,
1611 $result{content_filename} = $filename;
1612 push @headers, "Content-Length: $file->{sizeInBytes}";
1613 if ($file->{download}) {
1614 $result{type} = "application/octet-stream";
1616 qq/Content-Disposition: attachment; filename=$file->{displayName}/;
1619 $result{type} = $file->{contentType};
1621 qq/Content-Disposition: inline; filename=$file->{displayName}/;
1623 $result{headers} = \@headers;
1628 sub req_file_metadata {
1629 my ($self, $req) = @_;
1631 my ($fileid, $metaname) = split '/', $self->rest;
1633 my $user = $req->siteuser;
1634 my $cgi = $req->cgi;
1635 $fileid ||= $cgi->param('file')
1636 or return $self->req_show_logon($req, $req->text(nofileid => "No file id supplied"));
1637 $metaname ||= $cgi->param('name')
1638 or return $self->req_show_logon($req, $req->text(nometaname => "No metaname supplied"));
1639 require BSE::TB::ArticleFiles;
1640 my $file = BSE::TB::ArticleFiles->getByPkey($fileid)
1641 or return $self->req_show_logon($req, $req->text(nosuchfile => "No such file"));
1643 if ($file->articleId != -1) {
1644 # check the user has access
1645 my $article = $file->article
1646 or return $self->req_show_logon($req, $req->text(nofilearticle => "No article found for this file"));
1647 if ($article->is_dynamic && !$req->siteuser_has_access($article)) {
1648 if ($req->siteuser) {
1649 return $self->req_userpage($req, $req->text(downloadnoacces => "You do not have access to this article"));
1652 return $self->req_show_logon($req, $req->text(needlogon => "You need to logon to download this file"));
1656 my $meta = $file->meta_by_name($metaname)
1657 or return $self->req_show_logon($req, $req->text(nosuchmeta => "There is no metadata by that name for this file"));
1661 # downloads over https of non-HTML to IE causes a confusing error
1662 # if cache-control is "no-cache". Avoid setting that.
1663 no_cache_dynamic => 0,
1665 type => $meta->content_type,
1666 content => $meta->value,
1672 sub req_file_cmetadata {
1673 my ($self, $req) = @_;
1675 my ($fileid, $metaname) = split '/', $self->rest;
1677 my $user = $req->siteuser;
1678 my $cgi = $req->cgi;
1679 $fileid ||= $cgi->param('file')
1680 or return $self->req_show_logon($req, $req->text(nofileid => "No file id supplied"));
1681 $metaname ||= $cgi->param('name')
1682 or return $self->req_show_logon($req, $req->text(nometaname => "No metaname supplied"));
1683 require BSE::TB::ArticleFiles;
1684 my $file = BSE::TB::ArticleFiles->getByPkey($fileid)
1685 or return $self->req_show_logon($req, $req->text(nosuchfile => "No such file"));
1687 if ($file->articleId != -1) {
1688 # check the user has access
1689 my $article = $file->article
1690 or return $self->req_show_logon($req, $req->text(nofilearticle => "No article found for this file"));
1691 if ($article->is_dynamic && !$req->siteuser_has_access($article)) {
1692 if ($req->siteuser) {
1693 return $self->req_userpage($req, $req->text(downloadnoacces => "You do not have access to this article"));
1696 return $self->req_show_logon($req, $req->text(needlogon => "You need to logon to download this file"));
1700 my $meta = $file->metacontent(cfg => $req->cfg, name => $metaname)
1701 or return $self->req_show_logon($req, $req->text(nosuchmeta => "There is no metadata by that name for this file"));
1706 sub req_show_lost_password {
1707 my ($self, $req, $message) = @_;
1709 my $cfg = $req->cfg;
1710 my $cgi = $req->cgi;
1711 my $session = $req->session;
1713 $message ||= $cgi->param('message') || '';
1717 $message = $req->message($errors);
1720 $message = escape_html($message);
1727 $req->dyn_user_tags(),
1728 message => $message,
1729 error_img => [ \&tag_error_img, $cfg, $errors ],
1731 BSE::Template->show_page('user/lostpassword', $cfg, \%acts);
1736 sub req_lost_password {
1737 my ($self, $req, $message) = @_;
1739 my $cfg = $req->cfg;
1740 my $cgi = $req->cgi;
1741 my $session = $req->session;
1743 my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
1744 my $userid = $cgi->param('userid');
1747 unless (defined $userid && length $userid) {
1748 $errors{userid} = $msgs->(lostnouserid=> "Please enter your username");
1752 unless (keys %errors) {
1753 $user = SiteUsers->getBy(userId=>$userid)
1754 or $errors{userid} = $msgs->(lostnosuch=> "Unknown username supplied", $userid);
1757 and return $self->req_show_lost_password($req, \%errors);
1760 my $email_user = $user->lost_password(\$error)
1761 or return $self->req_show_lost_password
1762 ($req, $msgs->(lostmailerror=> "Email error: .$error", $error));
1763 $message = $message ? escape_html($message) : $req->message;
1767 message => $message,
1768 $req->dyn_user_tags(),
1769 user => sub { escape_html($user->{$_[0]}) },
1770 emailuser => [ \&tag_hash, $email_user ],
1772 BSE::Template->show_page('user/lostemailsent', $cfg, \%acts);
1778 my ($self, $req) = @_;
1780 my $cfg = $req->cfg;
1781 my $cgi = $req->cgi;
1782 my $session = $req->session;
1784 my $id = $cgi->param('id')
1785 or return $self->show_opts($req, "No subscription id parameter");
1786 my $sub = BSE::SubscriptionTypes->getByPkey($id)
1787 or return $self->show_opts($req, "Unknown subscription id");
1791 $req->dyn_user_tags(),
1792 subscription=>sub { escape_html($sub->{$_[0]}) },
1794 BSE::Template->show_page('user/subdetail', $cfg, \%acts);
1799 sub req_nopassword {
1800 my ($self, $req) = @_;
1802 my $cfg = $req->cfg;
1803 my $cgi = $req->cgi;
1804 my $session = $req->session;
1809 $req->dyn_user_tags(),
1811 BSE::Template->show_page('user/nopassword', $cfg, \%acts);
1817 my ($self, $req) = @_;
1819 my $cfg = $req->cfg;
1820 my $cgi = $req->cgi;
1821 my $session = $req->session;
1823 my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
1824 my $email = $cgi->param('blacklist')
1825 or return $self->req_show_logon($req,
1826 $msgs->(blnoemail=>"No email supplied"));
1827 my $genemail = _generic_email($email);
1832 $req->dyn_user_tags(),
1833 email => sub { escape_html($email) },
1835 require BSE::EmailBlacklist;
1836 my $black = BSE::EmailBlacklist->getEntry($genemail);
1838 BSE::Template->show_page('user/alreadyblacklisted', $cfg, \%acts);
1842 my @cols = BSE::EmailBlackEntry->columns;
1844 $black{email} = $genemail;
1845 $black{why} = "Web request from $ENV{REMOTE_ADDR}";
1846 $black = BSE::EmailBlacklist->add(@black{@cols});
1847 BSE::Template->show_page('user/blacklistdone', $cfg, \%acts);
1853 my ($self, $req) = @_;
1855 my $cfg = $req->cfg;
1856 my $cgi = $req->cgi;
1857 my $session = $req->session;
1859 my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
1860 my $secret = $cgi->param('confirm')
1861 or return $self->req_show_logon($req,
1862 $msgs->(confnosecret=>"No secret supplied for confirmation"));
1863 my $userid = $cgi->param('u')
1864 or return $self->req_show_logon($req,
1865 $msgs->(confnouser=>"No user id supplied for confirmation"));
1866 if ($userid + 0 != $userid || $userid < 1) {
1867 return $self->req_show_logon($req,
1868 $msgs->(confbaduser=>"Invalid or unknown user id supplied for confirmation"));
1870 my $user = SiteUsers->getByPkey($userid)
1871 or return $self->req_show_logon($req,
1872 $msgs->(confbaduser=>"Invalid or unknown user id supplied for confirmation"));
1873 unless ($secret eq $user->{confirmSecret}) {
1874 return $self->req_show_logon($req,
1875 $msgs->(confbadsecret=>"Sorry, the confirmation secret does not match"));
1878 $user->{confirmed} = 1;
1879 # I used to reset this, but it doesn't really make sense
1880 # $user->{confirmSecret} = '';
1882 my $genEmail = _generic_email($user->{email});
1883 my $request = BSE::EmailRequests->getBy(genEmail=>$genEmail);
1884 $request and $request->remove();
1888 $req->dyn_user_tags(),
1889 user=>sub { escape_html($user->{$_[0]}) },
1891 BSE::Template->show_page('user/confirmed', $cfg, \%acts);
1896 sub _generic_email {
1897 # SiteUser->generic_email(shift);
1898 my ($checkemail) = @_;
1900 # Build a generic form for the email - since an attacker could
1901 # include comments or extra spaces or a bunch of other stuff.
1902 # this isn't strictly correct, but it's good enough
1903 1 while $checkemail =~ s/\([^)]\)//g;
1904 if ($checkemail =~ /<([^>]+)>/) {
1907 $checkemail = lc $checkemail;
1908 $checkemail =~ s/\s+//g;
1913 # returns non-zero if a page was generated
1914 sub send_conf_request {
1915 my ($self, $req, $user, $suppress_success) = @_;
1917 my $cfg = $req->cfg;
1918 my $cgi = $req->cgi;
1919 my $session = $req->session;
1921 my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
1923 # check for existing in-progress confirmations
1924 my $checkemail = _generic_email($user->{email});
1926 # check the blacklist
1927 require 'BSE/EmailBlacklist.pm';
1932 $req->dyn_user_tags(),
1933 user=>sub { escape_html($user->{$_[0]}) },
1936 # check that the from address has been configured
1937 my $from = $cfg->entry('confirmations', 'from') ||
1938 $cfg->entry('basic', 'emailfrom')|| $SHOP_FROM;
1940 $acts{mailerror} = sub { escape_html("Configuration Error: The confirmations from address has not been configured") };
1941 BSE::Template->show_page('user/email_conferror', $cfg, \%acts);
1945 my $blackentry = BSE::EmailBlacklist->getEntry($checkemail);
1948 $acts{black} = sub { escape_html($blackentry->{$_[0]}) },
1949 BSE::Template->show_page('user/blacklisted', $cfg, \%acts);
1953 unless ($user->{confirmSecret}) {
1954 use BSE::Util::Secure qw/make_secret/;
1955 # print STDERR "Generating secret\n";
1956 $user->{confirmSecret} = make_secret($cfg);
1960 # check for existing confirmations
1961 my $confirm = BSE::EmailRequests->getBy(genEmail=>$checkemail);
1963 $acts{confirm} = sub { escape_html($confirm->{$_[0]}) };
1964 my $too_many = $confirm->{unackedConfMsgs} >= MAX_UNACKED_CONF_MSGS;
1965 $acts{ifTooMany} = sub { $too_many };
1966 use BSE::Util::SQL qw/sql_datetime_to_epoch/;
1967 my $lastSentEpoch = sql_datetime_to_epoch($confirm->{lastConfSent});
1968 my $too_soon = $lastSentEpoch + MIN_UNACKED_CONF_GAP > time;
1969 $acts{ifTooSoon} = sub { $too_soon };
1972 BSE::Template->show_page('user/toomany', $cfg, \%acts);
1976 BSE::Template->show_page('user/toosoon', $cfg, \%acts);
1982 my @cols = BSE::EmailRequest->columns;
1984 $confirm{email} = $user->{email};
1985 $confirm{genEmail} = $checkemail;
1986 # prevents silliness on error
1987 use BSE::Util::SQL qw(sql_datetime);
1988 $confirm{lastConfSent} = sql_datetime(time - MIN_UNACKED_CONF_GAP);
1989 $confirm{unackedConfMsgs} = 0;
1990 $confirm = BSE::EmailRequests->add(@confirm{@cols});
1993 # ok, now we can send the confirmation request
1997 BSE::Util::Tags->basic(\%acts, $cgi, $cfg),
1998 user => sub { $user->{$_[0]} },
1999 confirm => sub { $confirm->{$_[0]} },
2000 remote_addr => sub { $ENV{REMOTE_ADDR} },
2002 my $email_template =
2003 $nopassword ? 'user/email_confirm_nop' : 'user/email_confirm';
2005 require BSE::ComposeMail;
2006 my $mail = BSE::ComposeMail->new(cfg => $cfg);
2008 my $subject = $cfg->entry('confirmations', 'subject')
2009 || 'Subscription Confirmation';
2010 unless ($mail->send(template => $email_template,
2014 subject=>$subject)) {
2015 # a problem sending the mail
2016 $acts{mailerror} = sub { escape_html($mail->errstr) };
2017 BSE::Template->show_page('user/email_conferror', $cfg, \%acts);
2020 ++$confirm->{unackedConfMsgs};
2021 $confirm->{lastConfSent} = now_datetime;
2023 return 0 if $suppress_success;
2024 BSE::Template->show_page($nopassword ? 'user/confsent_nop' : 'user/confsent', $cfg, \%acts);
2030 my ($self, $req) = @_;
2032 my $cfg = $req->cfg;
2033 my $cgi = $req->cgi;
2034 my $session = $req->session;
2036 my $msgs = BSE::Message->new(cfg=>$cfg, section=>'user');
2037 my $secret = $cgi->param('unsub')
2038 or return $self->req_show_logon($req,
2039 $msgs->(unsubnosecret=>"No secret supplied for unsubscribe"));
2040 my $userid = $cgi->param('u')
2041 or return $self->req_show_logon($req,
2042 $msgs->(unsubnouser=>"No user supplied for unsubscribe"));
2043 if ($userid + 0 != $userid || $userid < 1) {
2044 return $self->req_show_logon($req,
2045 $msgs->(unsubbaduser=>"Invalid or unknown username supplied for unsubscribe"));
2047 my $user = SiteUsers->getByPkey($userid)
2048 or return $self->req_show_logon($req,
2049 $msgs->(unsubbaduser=>"Invalid or unknown username supplied for unsubscribe"));
2050 unless ($secret eq $user->{confirmSecret}) {
2051 return $self->req_show_logon($req,
2052 $msgs->(unsubbadsecret=>"Sorry, the ubsubscribe secret does not match"));
2059 $req->dyn_user_tags(),
2060 user => sub { escape_html($user->{$_[0]}) },
2062 my $subid = $cgi->param('s');
2064 if ($subid eq 'all') {
2065 $user->removeSubscriptions();
2066 BSE::Template->show_page('user/unsuball', $cfg, \%acts);
2068 elsif (0+$subid eq $subid
2069 and $sub = BSE::SubscriptionTypes->getByPkey($subid)) {
2070 $acts{subscription} = sub { escape_html($sub->{$_[0]}) };
2071 $user->removeSubscription($subid);
2072 BSE::Template->show_page('user/unsubone', $cfg, \%acts);
2075 BSE::Template->show_page('user/cantunsub', $cfg, \%acts);
2081 sub _validate_affiliate_name {
2082 my ($cfg, $aff_name, $errors, $msgs, $user) = @_;
2084 my $display = $cfg->entry('site users', 'display_affiliate_name',
2086 my $required = $cfg->entry('site users', 'require_affiliate_name', 0);
2088 if (defined $aff_name) {
2089 $aff_name =~ s/^\s+|\s+$//g;
2090 if (length $aff_name) {
2091 if ($aff_name =~ /^\w+$/) {
2092 my $other = SiteUsers->getBy(affiliate_name => $aff_name);
2093 if ($other && (!$user || $other->{id} != $user->{id})) {
2094 $errors->{affiliate_name} = $msgs->(dupaffiliatename =>
2095 "$display '$aff_name' is already in use", $aff_name);
2102 $errors->{affiliate_name} = $msgs->(badaffiliatename =>
2103 "Invalid $display, no spaces or special characters are allowed");
2107 $errors->{affiliate_name} = $msgs->("optsrequired" =>
2108 "$display is a required field",
2109 "affiliate_name", $display);
2116 # always required if making a new user
2117 if (!$errors->{affiliate_name} && $required && !$user) {
2118 $errors->{affiliate_name} = $msgs->("optsrequired" =>
2119 "$display is a required field",
2120 "affiliate_name", $display);
2127 my ($self, $req) = @_;
2129 my $cfg = $req->cfg;
2130 my $cgi = $req->cgi;
2131 my $session = $req->session;
2133 my $u = $cgi->param('u');
2134 my $i = $cgi->param('i');
2135 defined $u && $u =~ /^\d+$/ && defined $i && $i =~ /^\w+$/
2136 or return $self->req_show_logon($req, "Missing or bad image parameter");
2138 my $user = SiteUsers->getByPkey($u)
2139 or return $self->req_show_logon($req, "Missing or bad image parameter");
2140 my $image = $user->get_image($i)
2141 or return $self->req_show_logon($req, "Unknown image id");
2142 my $image_dir = $cfg->entryVar('paths', 'siteuser_images');
2144 my $filename = "$image_dir/$image->{filename}";
2146 or return $self->req_show_logon($req, "Image file missing");
2149 type => $image->{content_type},
2150 content_filename => $filename,
2153 "Content-Length: $image->{bytes}",
2160 sub _notify_registration {
2161 my ($self, $req, $user) = @_;
2163 my $cfg = $req->cfg;
2165 my $email = $cfg->entry('site users', 'notify_register_email',
2166 $Constants::SHOP_FROM);
2167 $email ||= $cfg->entry('shop', 'from');
2169 print STDERR "No email configured for notify_register, set [site users].notify_register_email\n";
2172 print STDERR "email $email\n";
2174 my $subject = $cfg->entry('site users', 'notify_register_subject',
2175 "New user {userId} registered");
2177 $subject =~ s/\{(\w+)\}/defined $user->{$1} ? $user->{$1} : "** $1 unknown **"/ge;
2178 $subject =~ tr/ -~//cd;
2179 substr($subject, 80) = '...' if length $subject > 80;
2184 $req->dyn_user_tags(),
2185 user => [ \&tag_hash_plain, $user ],
2188 require BSE::ComposeMail;
2189 my $mailer = BSE::ComposeMail->new(cfg => $cfg);
2190 $mailer->send(template => 'admin/registeremail',
2194 subject => $subject,
2195 log_object => $user,
2196 log_msg => "Notify admin of user registration to $email",
2197 log_component => "member:register:notifyadmin");
2201 # my ($self, $req, $error) = @_;
2203 # my $result = $self->SUPER::error($req, $error);
2205 # BSE::Template->output_result($req, $result);
2212 Display a given user's wishlist.
2220 user - user logon of the user to display the wishlist for
2224 Template: user/wishlist.tmpl
2231 my ($self, $req) = @_;
2233 my $user_id = $req->cgi->param('user');
2235 defined $user_id && length $user_id
2236 or return $self->error($req, "Invalid or missing user id");
2238 my $custom = custom_class($req->cfg);
2240 my $user = SiteUsers->getBy(userId => $user_id)
2241 or return $self->error($req, "No such user $user_id");
2243 my $curr_user = $req->siteuser;
2245 $custom->can_user_see_wishlist($user, $curr_user, $req)
2246 or return $self->error($req, "Sorry, you cannot see ${user_id}'s wishlist");
2248 my @wishlist = $user->wishlist;
2251 my $it = BSE::Util::Iterate::Article->new(req => $req);
2254 $req->dyn_user_tags(),
2255 $it->make_iterator(undef, 'uwishlistentry', 'uwishlist', \@wishlist),
2256 wuser => [ \&tag_hash, $user ],
2259 my $template = 'user/wishlist';
2260 my $t = $req->cgi->param('_t');
2261 if ($t && $t =~ /^\w+$/ && $t ne 'base') {
2265 BSE::Template->show_page($template, $req->cfg, \%acts);
2274 Download a user file.
2279 my ($self, $req) = @_;
2281 require BSE::TB::OwnedFiles;
2282 my $cgi = $req->cgi;
2283 my $cfg = $req->cfg;
2284 my $id = $cgi->param("id");
2285 defined $id && $id =~ /^\d+$/
2286 or return $self->error($req, "Invalid or missing file id");
2288 # return the same error to avoid giving someone a mechanism to find
2289 # which files are in use
2290 my $file = BSE::TB::OwnedFiles->getByPkey($id)
2291 or return $self->error($req, "Invalid or missing file id");
2294 my $user = $self->_get_user($req, 'downufile', \$result)
2297 require BSE::TB::SiteUserGroups;
2299 if ($file->owner_type eq $user->file_owner_type) {
2300 $accessible = $user->id == $file->owner_id;
2302 elsif ($file->owner_type eq BSE::TB::SiteUserGroup->file_owner_type) {
2303 my $owner_id = $file->owner_id;
2304 my $group = $owner_id < 0
2305 ? BSE::TB::SiteUserGroups->getQueryGroup($cfg, $owner_id)
2306 : BSE::TB::SiteUserGroups->getByPkey($owner_id);
2308 $accessible = $group->contains_user($user);
2311 print STDERR "** downufile: unknown group id ", $file->owner_id, " in file ", $file->id, "\n";
2315 print STDERR "** downufile: Unknown file owner type ", $file->owner_type, " in file ", $file->id, "\n";
2320 or return $self->error($req, "Sorry, you don't have access to this file");
2323 return $file->download_result
2326 download => scalar($cgi->param("force_download")),
2330 or return $self->error($req, $msg);
2334 my ($self, $req, $errors) = @_;
2336 my ($id) = $self->rest;
2337 $id ||= $req->cgi->param("id");
2339 or return $self->req_show_logon($req, $req->catmsg("msg:bse/user/nolostid"));
2342 my $user = SiteUsers->lost_password_next($id, \$error)
2343 or return $self->req_show_logon($req, { _ => "msg:bse/user/lost/$error" });
2345 my $message = $req->message($errors);
2349 $req->dyn_user_tags,
2351 error_img => [ \&tag_error_img, $req->cfg, $errors ],
2352 message => $message,
2355 return $req->response("user/lost_prompt", \%acts);
2362 description => "New Password",
2367 description => "Confirm Password",
2374 my ($self, $req) = @_;
2376 my ($id) = $self->rest;
2377 $id ||= $req->cgi->param("id");
2379 or return $self->req_show_logon($req, $req->catmsg("msg:bse/user/nolostid"));
2382 $req->validate(fields => \%lost_fields,
2383 errors => \%errors);
2384 my $password = $req->cgi->param("password");
2385 unless ($errors{password}) {
2387 unless (SiteUser->check_password_rules($password, \$error)) {
2388 my ($errorid, @more) = @$error;
2389 $errors{password} = $req->catmsg("msg:bse/user/$errorid", \@more)
2394 and return $self->req_lost($req, \%errors);
2398 my $user = SiteUsers->lost_password_save($id, $password, \$error)
2399 or return $self->req_show_logon($req, "msg:bse/user/lost/$error");
2401 $req->flash("msg:bse/user/lostsaved");
2403 return $req->get_refresh($req->cfg->user_url("user", "show_logon"));