+#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</access_users>. 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
+user has this right, or, for a list, one of the rights (currently via group
+membership, eventually also via user overrides).
+
+=cut
+
+sub access_right {
+ my( $self, $rightname ) = @_;
+
+ $rightname = [ $rightname ] unless ref($rightname);
+
+ warn "$me access_right called on ". join(', ', @$rightname). "\n"
+ if $DEBUG;
+
+ #some caching of ACL requests for low-hanging fruit perf improvement
+ #since we get a new $CurrentUser object each page view there shouldn't be any
+ #issues with stickiness
+ if ( $self->{_ACLcache} ) {
+
+ unless ( grep !exists($self->{_ACLcache}{$_}), @$rightname ) {
+ warn "$me ACL cache hit for ". join(', ', @$rightname). "\n"
+ if $DEBUG;
+ return scalar( grep $self->{_ACLcache}{$_}, @$rightname );
+ }
+
+ warn "$me ACL cache miss for ". join(', ', @$rightname). "\n"
+ if $DEBUG;
+
+ } else {
+
+ warn "initializing ACL cache\n"
+ if $DEBUG;
+ $self->{_ACLcache} = {};
+
+ }
+
+ my $has_right = ' rightname IN ('. join(',', map '?', @$rightname ). ') ';
+
+ my $sth = dbh->prepare("
+ SELECT groupnum FROM access_usergroup
+ LEFT JOIN access_group USING ( groupnum )
+ LEFT JOIN access_right
+ ON ( access_group.groupnum = access_right.rightobjnum )
+ WHERE usernum = ?
+ AND righttype = 'FS::access_group'
+ AND $has_right
+ LIMIT 1
+ ") or die dbh->errstr;
+ $sth->execute($self->usernum, @$rightname) or die $sth->errstr;
+ my $row = $sth->fetchrow_arrayref;
+
+ my $return = $row ? $row->[0] : '';
+
+ #just caching the single-rightname hits should be enough of a win for now
+ if ( scalar(@$rightname) == 1 ) {
+ $self->{_ACLcache}{${$rightname}[0]} = $return;
+ }
+
+ $return;
+
+}
+
+=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</access_right> for any L</refund_rights>
+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
+"default_customer_view" user preference, the "cust_main-default_view" config,
+or the hardcoded default, "basics" (formerly "jumbo" prior to 3.0).
+
+=cut
+
+sub default_customer_view {
+ my $self = shift;
+
+ $self->option('default_customer_view')
+ || FS::Conf->new->config('cust_main-default_view')
+ || 'basics'; #s/jumbo/basics/ starting with 3.0
+
+}
+
+=item spreadsheet_format [ OVERRIDE ]
+
+Returns a hashref of this user's Excel spreadsheet download settings:
+'extension' (xls or xlsx), 'class' (Spreadsheet::WriteExcel or
+Excel::Writer::XLSX), and 'mime_type'. If OVERRIDE is 'XLS' or 'XLSX',
+use that instead of the user's setting.
+
+=cut
+
+# is there a better place to put this?
+my %formats = (
+ XLS => {
+ extension => '.xls',
+ class => 'Spreadsheet::WriteExcel',
+ mime_type => 'application/vnd.ms-excel',
+ },
+ XLSX => {
+ extension => '.xlsx',
+ class => 'Excel::Writer::XLSX',
+ mime_type => # it's on wikipedia, it must be true
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ }
+);
+
+sub spreadsheet_format {
+ my $self = shift;
+ my $override = shift;
+
+ my $f = $override
+ || $self->option('spreadsheet_format')
+ || FS::Conf->new->config('spreadsheet_format')
+ || 'XLS';
+
+ $formats{$f};
+}
+
+=item is_system_user
+
+Returns true if this user has the name of a known system account. These
+users cannot log into the web interface and can't have passwords set.
+
+=cut
+
+sub is_system_user {
+ my $self = shift;
+ return grep { $_ eq $self->username } ( qw(
+ fs_queue
+ fs_daily
+ fs_selfservice
+ 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<is_password_allowed>) 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 {
+ #my( $self, $password ) = @_;
+ #FS::Auth->auth_class->change_password( $self, $password );
+ FS::Auth->auth_class->change_password( @_ );
+}
+
+=item change_password_fields NEW_PASSWORD
+
+=cut
+
+sub change_password_fields {
+ #my( $self, $password ) = @_;
+ #FS::Auth->auth_class->change_password_fields( $self, $password );
+ 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;
+}