X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Faccess_user.pm;h=a9fdf5b1e5063b0cfcd9714fb92c0182ee9af653;hp=0a441c45a68cb6592d7e37b79ee4079221478795;hb=e71dd08fc2e0aa3ee8cdbeb4e1f39f04898f773b;hpb=32072dbf59a054529f5304574c0f56f9567d14d0 diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm index 0a441c45a..a9fdf5b1e 100644 --- a/FS/FS/access_user.pm +++ b/FS/FS/access_user.pm @@ -1,8 +1,10 @@ package FS::access_user; -use base qw( FS::m2m_Common FS::option_Common ); +use base qw( FS::Password_Mixin + FS::m2m_Common + FS::option_Common ); use strict; -use vars qw( $DEBUG $me $conf ); +use vars qw( $DEBUG $me ); use FS::UID; use FS::Auth; use FS::Conf; @@ -40,17 +42,37 @@ inherits from FS::Record. The following fields are currently supported: =over 4 -=item usernum - primary key +=item usernum -=item username - +primary key -=item _password - +=item username -=item last - +=item _password -=item first - +=item _password_encoding -=item disabled - empty or 'Y' +Empty or bcrypt + +=item last + +Last name + +=item first + +First name + +=item user_custnum + +Master customer for this employee (for commissions) + +=item report_salesnum + +Default sales person for this employee (for reports) + +=item disabled + +Empty or 'Y' =back @@ -105,6 +127,9 @@ sub insert { } $error = $self->SUPER::insert(@_); + if ( $self->_password ) { + $error ||= $self->insert_password_history; + } if ( $error ) { $dbh->rollback or die $dbh->errstr if $oldAutoCommit; @@ -136,7 +161,8 @@ sub delete { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my $error = $self->SUPER::delete(@_); + my $error = $self->delete_password_history + || $self->SUPER::delete(@_); if ( $error ) { $dbh->rollback or die $dbh->errstr if $oldAutoCommit; @@ -180,6 +206,9 @@ sub replace { ); my $error = $new->SUPER::replace($old, @_); + if ( $old->_password ne $new->_password ) { + $error ||= $new->insert_password_history; + } if ( $error ) { $dbh->rollback or die $dbh->errstr if $oldAutoCommit; @@ -263,6 +292,22 @@ sub report_sales { Returns links to the the groups this user is a part of, as FS::access_usergroup objects (see L). +=item num_agents + +Returns the number of agents this user can view (via group membership). + +=cut + +sub num_agents { + my $self = shift; + $self->scalar_sql( + 'SELECT COUNT(DISTINCT agentnum) FROM access_usergroup + JOIN access_groupagent USING ( groupnum ) + WHERE usernum = ?', + $self->usernum, + ); +} + =item agentnums Returns a list of agentnums this user can view (via group membership). @@ -334,7 +379,9 @@ sub agentnums_sql { if ( $self->access_right($viewall_right) ) { push @or, "$agentnum IS NOT NULL"; } else { - push @or, "$agentnum IN (". join(',', $self->agentnums). ')'; + my @agentnums = $self->agentnums; + push @or, "$agentnum IN (". join(',', @agentnums). ')' + if @agentnums; } push @or, "$agentnum IS NULL" @@ -350,17 +397,24 @@ sub agentnums_sql { Returns true if the user can view the specified agent. +Also accepts optional hashref cache, to avoid redundant database calls. + =cut sub agentnum { - my( $self, $agentnum ) = @_; + my( $self, $agentnum, $cache ) = @_; + $cache ||= {}; + return $cache->{$self->usernum}->{$agentnum} + if $cache->{$self->usernum}->{$agentnum}; my $sth = dbh->prepare( "SELECT COUNT(*) FROM access_usergroup JOIN access_groupagent USING ( groupnum ) WHERE usernum = ? AND agentnum = ?" ) or die dbh->errstr; $sth->execute($self->usernum, $agentnum) or die $sth->errstr; - $sth->fetchrow_arrayref->[0]; + $cache->{$self->usernum}->{$agentnum} = $sth->fetchrow_arrayref->[0]; + $sth->finish; + return $cache->{$self->usernum}->{$agentnum}; } =item agents [ HASHREF | OPTION => VALUE ... ] @@ -380,6 +434,104 @@ sub agents { }); } +=item access_users [ HASHREF | OPTION => VALUE ... ] + +Returns an array of FS::access_user objects, one for each non-disabled +access_user in the system that shares an agent (via group membership) with +the invoking object. Regardless of options and agents, will always at +least return the invoking user and any users who have viewall_right. + +Accepts the following options: + +=over 4 + +=item table + +Only return users who appear in the usernum field of this table + +=item disabled + +Include disabled users if true (defaults to false) + +=item viewall_right + +All users will be returned if the current user has the provided +access right, regardless of agents (other filters still apply.) +Defaults to 'View customers of all agents' + +=cut + +#Leaving undocumented until such time as this functionality is actually used +# +#=item null +# +#Users with no agents will be returned. +# +#=item null_right +# +#Users with no agents will be returned if the current user has the provided +#access right. + +sub access_users { + my $self = shift; + my %opt = ref($_[0]) ? %{$_[0]} : @_; + my $table = $opt{'table'}; + my $search = { 'table' => 'access_user' }; + $search->{'hashref'} = $opt{'disabled'} ? {} : { 'disabled' => '' }; + $search->{'addl_from'} = "INNER JOIN $table ON (access_user.usernum = $table.usernum)" + if $table; + my @access_users = qsearch($search); + my $viewall_right = $opt{'viewall_right'} || 'View customers of all agents'; + return @access_users if $self->access_right($viewall_right); + #filter for users with agents $self can view + my @out; + my $agentnum_cache = {}; +ACCESS_USER: + foreach my $access_user (@access_users) { + # you can always view yourself, regardless of agents, + # and you can always view someone who can view you, + # since they might have affected your customers + if ( ($self->usernum eq $access_user->usernum) + || $access_user->access_right($viewall_right) + ) { + push(@out,$access_user); + next; + } + # if user has no agents, you need null or null_right to view + my @agents = $access_user->agents('viewall_right'=>'NONE'); #handled viewall_right above + if (!@agents) { + if ( $opt{'null'} || + ( $opt{'null_right'} && $self->access_right($opt{'null_right'}) ) + ) { + push(@out,$access_user); + } + next; + } + # otherwise, you need an agent in common + foreach my $agent (@agents) { + if ($self->agentnum($agent->agentnum,$agentnum_cache)) { + push(@out,$access_user); + next ACCESS_USER; + } + } + } + return @out; +} + +=item access_users_hashref [ HASHREF | OPTION => VALUE ... ] + +Accepts same options as L. Returns a hashref of +users, with keys of usernum and values of username. + +=cut + +sub access_users_hashref { + my $self = shift; + my %access_users = map { $_->usernum => $_->username } + $self->access_users(@_); + return \%access_users; +} + =item access_right RIGHTNAME | LISTREF Given a right name or a list reference of right names, returns true if this @@ -404,7 +556,7 @@ sub access_right { unless ( grep !exists($self->{_ACLcache}{$_}), @$rightname ) { warn "$me ACL cache hit for ". join(', ', @$rightname). "\n" if $DEBUG; - return grep $self->{_ACLcache}{$_}, @$rightname + return scalar( grep $self->{_ACLcache}{$_}, @$rightname ); } warn "$me ACL cache miss for ". join(', ', @$rightname). "\n" @@ -444,6 +596,43 @@ sub access_right { } +=item refund_rights PAYBY + +Accepts payment $payby (BILL,CASH,MCRD,MCHK,CARD,CHEK) and returns a +list of the refund rights associated with that $payby. + +Returns empty list if $payby wasn't recognized. + +=cut + +sub refund_rights { + my $self = shift; + my $payby = shift; + my @rights = (); + push @rights, 'Post refund' if $payby =~ /^(BILL|CASH|MCRD|MCHK)$/; + push @rights, 'Post check refund' if $payby eq 'BILL'; + push @rights, 'Post cash refund ' if $payby eq 'CASH'; + push @rights, 'Refund payment' if $payby =~ /^(CARD|CHEK)$/; + push @rights, 'Refund credit card payment' if $payby eq 'CARD'; + push @rights, 'Refund Echeck payment' if $payby eq 'CHEK'; + return @rights; +} + +=item refund_access_right PAYBY + +Returns true if user has L for any L +for the specified payby. + +=cut + +sub refund_access_right { + my $self = shift; + my $payby = shift; + my @rights = $self->refund_rights($payby); + return '' unless @rights; + return $self->access_right(\@rights); +} + =item default_customer_view Returns the default customer view for this user, from the @@ -456,7 +645,7 @@ sub default_customer_view { my $self = shift; $self->option('default_customer_view') - || $conf->config('cust_main-default_view') + || FS::Conf->new->config('cust_main-default_view') || 'basics'; #s/jumbo/basics/ starting with 3.0 } @@ -491,7 +680,7 @@ sub spreadsheet_format { my $f = $override || $self->option('spreadsheet_format') - || $conf->config('spreadsheet_format') + || FS::Conf->new->config('spreadsheet_format') || 'XLS'; $formats{$f}; @@ -513,11 +702,18 @@ sub is_system_user { fs_signup fs_bootstrap fs_selfserv + fs_api ) ); } =item change_password NEW_PASSWORD +Changes the user's password to NEW_PASSWORD. This does not check password +policy rules (see C) and will return an error only if +editing the user's record fails for some reason. + +If NEW_PASSWORD is the same as the existing password, this does nothing. + =cut sub change_password { @@ -536,6 +732,88 @@ sub change_password_fields { FS::Auth->auth_class->change_password_fields( @_ ); } +=item locale + +=cut + +sub locale { + my $self = shift; + return $self->{_locale} if exists($self->{_locale}); + $self->{_locale} = $self->option('locale'); +} + +=item get_page_pref PATH, NAME, TABLENUM + +Returns the user's page preference named NAME for the page at PATH. If the +page is a view or edit page or otherwise shows a single record at a time, +it should use TABLENUM to tell which record the preference is for. + +=cut + +sub get_page_pref { + my $self = shift; + my ($path, $prefname, $tablenum) = @_; + $tablenum ||= ''; + + my $access_user_page_pref = qsearchs('access_user_page_pref', { + path => $path, + usernum => $self->usernum, + tablenum => $tablenum, + prefname => $prefname, + }); + $access_user_page_pref ? $access_user_page_pref->prefvalue : ''; +} + +=item set_page_pref PATH, NAME, TABLENUM, VALUE + +Sets the user's page preference named NAME for the page at PATH. Use TABLENUM +as for get_page_pref. + +=cut + +sub set_page_pref { + my $self = shift; + my ($path, $prefname, $tablenum, $prefvalue) = @_; + $tablenum ||= ''; + + my $error; + my $access_user_page_pref = qsearchs('access_user_page_pref', { + path => $path, + usernum => $self->usernum, + tablenum => $tablenum, + prefname => $prefname, + }); + if ( $access_user_page_pref ) { + if ( $prefvalue eq $access_user_page_pref->get('prefvalue') ) { + return ''; + } + if ( length($prefvalue) > 0 ) { + $access_user_page_pref->set('prefvalue', $prefvalue); + $error = $access_user_page_pref->replace; + $error .= " (updating $prefname)" if $error; + } else { + $error = $access_user_page_pref->delete; + $error .= " (removing $prefname)" if $error; + } + } else { + if ( length($prefvalue) > 0 ) { + $access_user_page_pref = FS::access_user_page_pref->new({ + path => $path, + usernum => $self->usernum, + tablenum => $tablenum, + prefname => $prefname, + prefvalue => $prefvalue, + }); + $error = $access_user_page_pref->insert; + $error .= " (creating $prefname)" if $error; + } else { + return ''; + } + } + + return $error; +} + =back =head1 BUGS