1 package FS::access_user;
2 use base qw( FS::m2m_Common FS::option_Common );
5 use vars qw( $DEBUG $me );
9 use FS::Record qw( qsearch qsearchs dbh );
15 $me = '[FS::access_user]';
19 FS::access_user - Object methods for access_user records
25 $record = new FS::access_user \%hash;
26 $record = new FS::access_user { 'column' => 'value' };
28 $error = $record->insert;
30 $error = $new_record->replace($old_record);
32 $error = $record->delete;
34 $error = $record->check;
38 An FS::access_user object represents an internal access user. FS::access_user
39 inherits from FS::Record. The following fields are currently supported:
43 =item usernum - primary key
53 =item disabled - empty or 'Y'
63 Creates a new internal access user. To add the user to the database, see L<"insert">.
65 Note that this stores the hash reference, not a distinct copy of the hash it
66 points to. You can ask the object for a copy with the I<hash> method.
70 # the new method can be inherited from FS::Record, if a table method is defined
72 sub table { 'access_user'; }
74 sub _option_table { 'access_user_pref'; }
75 sub _option_namecol { 'prefname'; }
76 sub _option_valuecol { 'prefvalue'; }
80 Adds this record to the database. If there is an error, returns the error,
81 otherwise returns false.
88 my $error = $self->check;
89 return $error if $error;
91 local $SIG{HUP} = 'IGNORE';
92 local $SIG{INT} = 'IGNORE';
93 local $SIG{QUIT} = 'IGNORE';
94 local $SIG{TERM} = 'IGNORE';
95 local $SIG{TSTP} = 'IGNORE';
96 local $SIG{PIPE} = 'IGNORE';
98 my $oldAutoCommit = $FS::UID::AutoCommit;
99 local $FS::UID::AutoCommit = 0;
103 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
107 $error = $self->SUPER::insert(@_);
110 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
113 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
121 Delete this record from the database.
128 local $SIG{HUP} = 'IGNORE';
129 local $SIG{INT} = 'IGNORE';
130 local $SIG{QUIT} = 'IGNORE';
131 local $SIG{TERM} = 'IGNORE';
132 local $SIG{TSTP} = 'IGNORE';
133 local $SIG{PIPE} = 'IGNORE';
135 my $oldAutoCommit = $FS::UID::AutoCommit;
136 local $FS::UID::AutoCommit = 0;
139 my $error = $self->SUPER::delete(@_);
142 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
145 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
151 =item replace OLD_RECORD
153 Replaces the OLD_RECORD with this one in the database. If there is an error,
154 returns the error, otherwise returns false.
161 my $old = ( ref($_[0]) eq ref($new) )
165 local $SIG{HUP} = 'IGNORE';
166 local $SIG{INT} = 'IGNORE';
167 local $SIG{QUIT} = 'IGNORE';
168 local $SIG{TERM} = 'IGNORE';
169 local $SIG{TSTP} = 'IGNORE';
170 local $SIG{PIPE} = 'IGNORE';
172 my $oldAutoCommit = $FS::UID::AutoCommit;
173 local $FS::UID::AutoCommit = 0;
176 return "Must change password when enabling this account"
177 if $old->disabled && !$new->disabled
178 && ( $new->_password =~ /changeme/i
179 || $new->_password eq 'notyet'
182 my $error = $new->SUPER::replace($old, @_);
185 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
188 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
196 Checks all fields to make sure this is a valid internal access user. If there is
197 an error, returns the error, otherwise returns false. Called by the insert
202 # the check method should currently be supplied - FS::Record contains some
203 # data checking routines
209 $self->ut_numbern('usernum')
210 || $self->ut_alpha_lower('username')
211 || $self->ut_textn('_password')
212 || $self->ut_textn('last')
213 || $self->ut_textn('first')
214 || $self->ut_foreign_keyn('user_custnum', 'cust_main', 'custnum')
215 || $self->ut_foreign_keyn('report_salesnum', 'sales', 'salesnum')
216 || $self->ut_enum('disabled', [ '', 'Y' ] )
218 return $error if $error;
225 Returns a name string for this user: "Last, First".
231 return $self->username
232 if $self->get('last') eq 'Lastname' && $self->first eq 'Firstname'
233 or $self->get('last') eq '' && $self->first eq '';
234 return $self->get('last'). ', '. $self->first;
239 Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
246 qsearchs( 'cust_main', { 'custnum' => $self->user_custnum } );
251 Returns the FS::sales object (see L<FS::sales>), if any, for this
258 qsearchs( 'sales', { 'salesnum' => $self->report_salesnum } );
261 =item access_usergroup
263 Returns links to the the groups this user is a part of, as FS::access_usergroup
264 objects (see L<FS::access_usergroup>).
268 Returns a list of agentnums this user can view (via group membership).
274 my $sth = dbh->prepare(
275 "SELECT DISTINCT agentnum FROM access_usergroup
276 JOIN access_groupagent USING ( groupnum )
278 ) or die dbh->errstr;
279 $sth->execute($self->usernum) or die $sth->errstr;
280 map { $_->[0] } @{ $sth->fetchall_arrayref };
285 Returns a hashref of agentnums this user can view.
291 scalar( { map { $_ => 1 } $self->agentnums } );
294 =item agentnums_sql [ HASHREF | OPTION => VALUE ... ]
296 Returns an sql fragement to select only agentnums this user can view.
298 Options are passed as a hashref or a list. Available options are:
304 The frament will also allow the selection of null agentnums.
308 The fragment will also allow the selection of null agentnums if the current
309 user has the provided access right
313 Optional table name in which agentnum is being checked. Sometimes required to
314 resolve 'column reference "agentnum" is ambiguous' errors.
318 All agents will be viewable if the current user has the provided access right.
319 Defaults to 'View customers of all agents'.
327 my %opt = ref($_[0]) ? %{$_[0]} : @_;
329 my $agentnum = $opt{'table'} ? $opt{'table'}.'.agentnum' : 'agentnum';
333 my $viewall_right = $opt{'viewall_right'} || 'View customers of all agents';
334 if ( $self->access_right($viewall_right) ) {
335 push @or, "$agentnum IS NOT NULL";
337 push @or, "$agentnum IN (". join(',', $self->agentnums). ')';
340 push @or, "$agentnum IS NULL"
342 || ( $opt{'null_right'} && $self->access_right($opt{'null_right'}) );
344 return ' 1 = 0 ' unless scalar(@or);
345 '( '. join( ' OR ', @or ). ' )';
351 Returns true if the user can view the specified agent.
356 my( $self, $agentnum ) = @_;
357 my $sth = dbh->prepare(
358 "SELECT COUNT(*) FROM access_usergroup
359 JOIN access_groupagent USING ( groupnum )
360 WHERE usernum = ? AND agentnum = ?"
361 ) or die dbh->errstr;
362 $sth->execute($self->usernum, $agentnum) or die $sth->errstr;
363 $sth->fetchrow_arrayref->[0];
366 =item agents [ HASHREF | OPTION => VALUE ... ]
368 Returns the list of agents this user can view (via group membership), as
369 FS::agent objects. Accepts the same options as the agentnums_sql method.
377 'hashref' => { disabled=>'' },
378 'extra_sql' => ' AND '. $self->agentnums_sql(@_),
379 'order_by' => 'ORDER BY agent',
383 =item access_right RIGHTNAME | LISTREF
385 Given a right name or a list reference of right names, returns true if this
386 user has this right, or, for a list, one of the rights (currently via group
387 membership, eventually also via user overrides).
392 my( $self, $rightname ) = @_;
394 $rightname = [ $rightname ] unless ref($rightname);
396 warn "$me access_right called on ". join(', ', @$rightname). "\n"
399 #some caching of ACL requests for low-hanging fruit perf improvement
400 #since we get a new $CurrentUser object each page view there shouldn't be any
401 #issues with stickiness
402 if ( $self->{_ACLcache} ) {
404 unless ( grep !exists($self->{_ACLcache}{$_}), @$rightname ) {
405 warn "$me ACL cache hit for ". join(', ', @$rightname). "\n"
407 return grep $self->{_ACLcache}{$_}, @$rightname
410 warn "$me ACL cache miss for ". join(', ', @$rightname). "\n"
415 warn "initializing ACL cache\n"
417 $self->{_ACLcache} = {};
421 my $has_right = ' rightname IN ('. join(',', map '?', @$rightname ). ') ';
423 my $sth = dbh->prepare("
424 SELECT groupnum FROM access_usergroup
425 LEFT JOIN access_group USING ( groupnum )
426 LEFT JOIN access_right
427 ON ( access_group.groupnum = access_right.rightobjnum )
429 AND righttype = 'FS::access_group'
432 ") or die dbh->errstr;
433 $sth->execute($self->usernum, @$rightname) or die $sth->errstr;
434 my $row = $sth->fetchrow_arrayref;
436 my $return = $row ? $row->[0] : '';
438 #just caching the single-rightname hits should be enough of a win for now
439 if ( scalar(@$rightname) == 1 ) {
440 $self->{_ACLcache}{${$rightname}[0]} = $return;
447 =item default_customer_view
449 Returns the default customer view for this user, from the
450 "default_customer_view" user preference, the "cust_main-default_view" config,
451 or the hardcoded default, "basics" (formerly "jumbo" prior to 3.0).
455 sub default_customer_view {
458 $self->option('default_customer_view')
459 || FS::Conf->new->config('cust_main-default_view')
460 || 'basics'; #s/jumbo/basics/ starting with 3.0
464 =item spreadsheet_format [ OVERRIDE ]
466 Returns a hashref of this user's Excel spreadsheet download settings:
467 'extension' (xls or xlsx), 'class' (Spreadsheet::WriteExcel or
468 Excel::Writer::XLSX), and 'mime_type'. If OVERRIDE is 'XLS' or 'XLSX',
469 use that instead of the user's setting.
473 # is there a better place to put this?
477 class => 'Spreadsheet::WriteExcel',
478 mime_type => 'application/vnd.ms-excel',
481 extension => '.xlsx',
482 class => 'Excel::Writer::XLSX',
483 mime_type => # it's on wikipedia, it must be true
484 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
488 sub spreadsheet_format {
490 my $override = shift;
493 || $self->option('spreadsheet_format')
494 || FS::Conf->new->config('spreadsheet_format')
502 Returns true if this user has the name of a known system account. These
503 users cannot log into the web interface and can't have passwords set.
509 return grep { $_ eq $self->username } ( qw(
519 =item change_password NEW_PASSWORD
523 sub change_password {
524 #my( $self, $password ) = @_;
525 #FS::Auth->auth_class->change_password( $self, $password );
526 FS::Auth->auth_class->change_password( @_ );
529 =item change_password_fields NEW_PASSWORD
533 sub change_password_fields {
534 #my( $self, $password ) = @_;
535 #FS::Auth->auth_class->change_password_fields( $self, $password );
536 FS::Auth->auth_class->change_password_fields( @_ );
545 L<FS::Record>, schema.html from the base documentation.