--- loncom/interface/domainprefs.pm 2012/12/20 13:15:36 1.180 +++ loncom/interface/domainprefs.pm 2013/06/04 23:12:08 1.196 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.180 2012/12/20 13:15:36 raeburn Exp $ +# $Id: domainprefs.pm,v 1.196 2013/06/04 23:12:08 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -45,7 +45,7 @@ described at http://www.lon-capa.org. =head1 OVERVIEW Each institution using LON-CAPA will typically have a single domain designated -for use by individuals affliated with the institution. Accordingly, each domain +for use by individuals affiliated with the institution. Accordingly, each domain may define a default set of logos and a color scheme which can be used to "brand" the LON-CAPA instance. In addition, an institution will typically have a language and timezone which are used for the majority of courses. @@ -386,11 +386,11 @@ sub handler { col2 => 'Rules'}], }, 'loadbalancing' => - {text => 'Dedicated Load Balancer', + {text => 'Dedicated Load Balancer(s)', help => 'Domain_Configuration_Load_Balancing', header => [{col1 => 'Balancers', col2 => 'Default destinations', - col3 => 'User affliation', + col3 => 'User affiliation', col4 => 'Overrides'}, ], }, @@ -407,8 +407,6 @@ sub handler { }; } - - my @roles = ('student','coordinator','author','admin'); my @actions = &Apache::loncommon::get_env_multiple('form.actions'); &Apache::lonhtmlcommon::add_breadcrumb @@ -419,7 +417,8 @@ sub handler { if ($phase eq 'process') { &Apache::lonconfigsettings::make_changes($r,$dom,$phase,$context,\@prefs_order,\%prefs,\%domconfig,$confname,\@roles); } elsif ($phase eq 'display') { - my $js = &recaptcha_js(); + my $js = &recaptcha_js(). + &credits_js(); if ((keys(%servers) > 1) || (keys(%existing) > 0)) { my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); @@ -436,22 +435,25 @@ sub handler { my ($configuserok,$author_ok,$switchserver) = &config_check($dom,$confname,$servadm); unless ($configuserok eq 'ok') { - &Apache::lonconfigsettings::print_header($r,$phase,$context). - &mt('The domain configuration user "[_1]" has yet to be created.', - $confname). - '
'; + &Apache::lonconfigsettings::print_header($r,$phase,$context); + $r->print(&mt('The domain configuration user "[_1]" has yet to be created.', + $confname). + '
' + ); if ($switchserver) { - &mt('Ordinarily, that domain configuration user is created when the ./UPDATE script is run to install LON-CAPA for the first time.'). - '
'. - &mt('However, that does not apply when new domains are added to a multi-domain server, and ./UPDATE has not been run recently.'). - '
'. - &mt('The "[_1]" user can be created automatically when a Domain Coordinator visits the web-based "Set domain configuration" screen, in a session hosted on the primary library server.',$confname). - '
'. - &mt("To do that now, use the following link: [_1],$switchserver); - } else { - &mt('To create that user from the command line run the ./UPDATE script found in the top level directory of the extracted LON-CAPA tarball.'). - '
'. - &mt('Once that is done, you will be able to use the web-based "Set domain configuration" to configure the domain'); + $r->print(&mt('Ordinarily, that domain configuration user is created when the ./UPDATE script is run to install LON-CAPA for the first time.'). + '
'. + &mt('However, that does not apply when new domains are added to a multi-domain server, and ./UPDATE has not been run recently.'). + '
'. + &mt('The "[_1]" user can be created automatically when a Domain Coordinator visits the web-based "Set domain configuration" screen, in a session hosted on the primary library server.',$confname). + '
'. + &mt('To do that now, use the following link: [_1]',$switchserver) + ); + } else { + $r->print(&mt('To create that user from the command line run the ./UPDATE script found in the top level directory of the extracted LON-CAPA tarball.'). + '
'. + &mt('Once that is done, you will be able to use the web-based "Set domain configuration" to configure the domain') + ); } $r->print(&Apache::loncommon::end_page()); return OK; @@ -489,7 +491,6 @@ sub handler { } } } - &Apache::lonconfigsettings::display_choices($r,$phase,$context,\@prefs_order,\%prefs); } return OK; @@ -551,7 +552,6 @@ sub print_config_box { my ($r,$dom,$confname,$phase,$action,$item,$settings) = @_; my $rowtotal = 0; my $output; - if ($action eq 'coursecategories') { $output = &coursecategories_javascript($settings); } @@ -840,7 +840,7 @@ sub print_login { ''. - $fullname.' ('.$uname.':'.$udom.')'; - } - $datatable .= ''; + my ($numdc,$table,$rows) = &active_dc_picker($dom,$numinrow,'checkbox', + 'reqapprovalnotify',%currapp); + if ($numdc > 0) { + $datatable .= $table; } else { $datatable .= &mt('There are no active Domain Coordinators'); - $rows ++; } $datatable .=''; $$rowtotal += $rows; @@ -2156,8 +2107,7 @@ sub print_autoupdate { sub print_autocreate { my ($dom,$settings,$rowtotal) = @_; - my (%createon,%createoff); - my $curr_dc; + my (%createon,%createoff,%currhash); my @types = ('xml','req'); if (ref($settings) eq 'HASH') { foreach my $item (@types) { @@ -2170,7 +2120,9 @@ sub print_autocreate { } } } - $curr_dc = $settings->{'xmldc'}; + if ($settings->{'xmldc'} ne '') { + $currhash{$settings->{'xmldc'}} = 1; + } } else { foreach my $item (@types) { $createoff{$item} = ' checked="checked" '; @@ -2178,6 +2130,7 @@ sub print_autocreate { } } $$rowtotal += 2; + my $numinrow = 2; my $datatable=''. ''.&mt('Create pending official courses from XML files').''. ' '. ''; - my ($numdc,$dctable) = &active_dc_picker($dom,$curr_dc); + my ($numdc,$dctable,$rows) = &active_dc_picker($dom,$numinrow,'radio', + 'autocreate_xmldc',%currhash); if ($numdc > 1) { $datatable .= ''. &mt('Course creation processed as: (choose Dom. Coord.)'). ''.$dctable.''; - $$rowtotal ++ ; } else { $datatable .= $dctable.''; } + $$rowtotal += $rows; return $datatable; } @@ -2323,7 +2277,7 @@ sub print_contacts { my @contacts = ('adminemail','supportemail'); my (%checked,%to,%otheremails,%bccemails); my @mailings = ('errormail','packagesmail','lonstatusmail','helpdeskmail', - 'requestsmail'); + 'requestsmail','updatesmail'); foreach my $type (@mailings) { $otheremails{$type} = ''; } @@ -2359,6 +2313,7 @@ sub print_contacts { $checked{'helpdeskmail'}{'supportemail'} = ' checked="checked" '; $checked{'lonstatusmail'}{'adminemail'} = ' checked="checked" '; $checked{'requestsmail'}{'adminemail'} = ' checked="checked" '; + $checked{'updatesmail'}{'adminemail'} = ' checked="checked" '; } my ($titles,$short_titles) = &contact_titles(); my $rownum = 0; @@ -2418,7 +2373,8 @@ sub print_helpsettings { } sub radiobutton_prefs { - my ($settings,$toggles,$defaultchecked,$choices,$itemcount) = @_; + my ($settings,$toggles,$defaultchecked,$choices,$itemcount,$onclick, + $additional) = @_; return unless ((ref($toggles) eq 'ARRAY') && (ref($defaultchecked) eq 'HASH') && (ref($choices) eq 'HASH')); @@ -2444,17 +2400,22 @@ sub radiobutton_prefs { } } } + if ($onclick) { + $onclick = ' onclick="'.$onclick.'"'; + } foreach my $item (@{$toggles}) { $css_class = $itemcount%2?' class="LC_odd_row"':''; $datatable .= - ''.$choices->{$item}. + ''. + ''.$choices->{$item}. ''. ''. ' '. - ''. + $checkedoff{$item}.' value="0"'.$onclick.' />'.&mt('No').''. + ''.$additional. + ''. ''; $itemcount ++; } @@ -2463,28 +2424,31 @@ sub radiobutton_prefs { sub print_coursedefaults { my ($position,$dom,$settings,$rowtotal) = @_; - my ($css_class,$datatable); + my ($css_class,$datatable,%checkedon,%checkedoff,%defaultchecked,@toggles); my $itemcount = 1; + my %choices = &Apache::lonlocal::texthash ( + canuse_pdfforms => 'Course/Community users can create/upload PDF forms', + anonsurvey_threshold => 'Responder count needed before showing submissions for anonymous surveys', + coursecredits => 'Credits can be specified for courses', + ); if ($position eq 'top') { - my (%checkedon,%checkedoff,%choices,%defaultchecked,@toggles); - %choices = - &Apache::lonlocal::texthash ( - canuse_pdfforms => 'Course/Community users can create/upload PDF forms', - ); %defaultchecked = ('canuse_pdfforms' => 'off'); - @toggles = ('canuse_pdfforms',); + @toggles = ('canuse_pdfforms'); ($datatable,$itemcount) = &radiobutton_prefs($settings,\@toggles,\%defaultchecked, \%choices,$itemcount); - $$rowtotal += $itemcount; } else { $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; - my %choices = - &Apache::lonlocal::texthash ( - anonsurvey_threshold => 'Responder count needed before showing submissions for anonymous surveys', - ); - my $currdefresponder; + my ($currdefresponder,$def_official_credits,$def_unofficial_credits); + my $currusecredits = 0; if (ref($settings) eq 'HASH') { $currdefresponder = $settings->{'anonsurvey_threshold'}; + if (ref($settings->{'coursecredits'}) eq 'HASH') { + $def_official_credits = $settings->{'coursecredits'}->{'official'}; + $def_unofficial_credits = $settings->{'coursecredits'}->{'unofficial'}; + if (($def_official_credits ne '') || ($def_unofficial_credits ne '')) { + $currusecredits = 1; + } + } } if (!$currdefresponder) { $currdefresponder = 10; @@ -2492,13 +2456,41 @@ sub print_coursedefaults { $currdefresponder = 1; } $datatable .= - ''.$choices{'anonsurvey_threshold'}. + ''. + $choices{'anonsurvey_threshold'}. ''. ''. ''. - ''; + ''."\n"; + $itemcount ++; + my $onclick = 'toggleCredits(this.form);'; + my $display = 'none'; + if ($currusecredits) { + $display = 'block'; + } + my $additional = '
'. + ''. + &mt('Default credits for official courses [_1]', + ''). + '
'. + ''. + &mt('Default credits for unofficial courses [_1]', + ''). + '
'."\n"; + %defaultchecked = ('coursecredits' => 'off'); + @toggles = ('coursecredits'); + my $current = { + 'coursecredits' => $currusecredits, + }; + (my $table,$itemcount) = + &radiobutton_prefs($current,\@toggles,\%defaultchecked, + \%choices,$itemcount,$onclick,$additional); + $datatable .= $table; } + $$rowtotal += $itemcount; return $datatable; } @@ -2789,7 +2781,10 @@ sub spares_row { $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; $datatable .= ' - '.$server.' when busy, offloads to:'."\n"; + '. + &mt('[_1] when busy, offloads to:' + ,''.$server.''). + "\n"; my (%current,%canselect); my @choices = &possible_newspares($server,$spareid->{$server},$serverhomes,$altids); @@ -3038,7 +3033,6 @@ sub print_loadbalancing { ''.$targettable.'

'; } } - my $cssidx = $balnum%2; $datatable .= ''. &loadbalancing_rules($dom,$intdom,$currrules{$lonhost}, $othertitle,$usertypes,$types,\%servers, @@ -3247,6 +3241,7 @@ sub contact_titles { 'helpdeskmail' => 'Helpdesk requests to be e-mailed to', 'lonstatusmail' => 'E-mail from nightly status check (warnings/errors)', 'requestsmail' => 'E-mail from course requests requiring approval', + 'updatesmail' => 'E-mail from nightly check of LON-CAPA module integrity/updates', ); my %short_titles = &Apache::lonlocal::texthash ( adminemail => 'Admin E-mail address', @@ -3294,7 +3289,7 @@ sub authorrequest_titles { sub courserequest_conditions { my %conditions = &Apache::lonlocal::texthash ( approval => '(Processing of request subject to approval by Domain Coordinator).', - validate => '(Processing of request subject to instittutional validation).', + validate => '(Processing of request subject to institutional validation).', ); return %conditions; } @@ -4209,7 +4204,7 @@ sub print_serverstatuses { sub serverstatus_pages { return ('userstatus','lonstatus','loncron','server-status','codeversions', - 'clusterstatus','metadata_keywords','metadata_harvest', + 'checksums','clusterstatus','metadata_keywords','metadata_harvest', 'takeoffline','takeonline','showenv','toggledebug','ping','domconf'); } @@ -4664,6 +4659,7 @@ sub modify_login { %curr_loginvia,%loginhash,@currlangs,@newlangs,$addedfile,%title,@offon); %title = ( coursecatalog => 'Display course catalog', adminmail => 'Display administrator E-mail address', + helpdesk => 'Display "Contact Helpdesk" link', newuser => 'Link for visitors to create a user account', loginheader => 'Log-in box header'); @offon = ('off','on'); @@ -4676,7 +4672,7 @@ sub modify_login { } ($errors,%colchanges) = &modify_colors($r,$dom,$confname,['login'], \%domconfig,\%loginhash); - my @toggles = ('coursecatalog','adminmail','newuser'); + my @toggles = ('coursecatalog','adminmail','helpdesk','newuser'); foreach my $item (@toggles) { $loginhash{login}{$item} = $env{'form.'.$item}; } @@ -4864,9 +4860,10 @@ sub modify_login { my $putresult = &Apache::lonnet::put_dom('configuration',\%loginhash, $dom); if ($putresult eq 'ok') { - my @toggles = ('coursecatalog','adminmail','newuser'); + my @toggles = ('coursecatalog','adminmail','helpdesk','newuser'); my %defaultchecked = ( 'coursecatalog' => 'on', + 'helpdesk' => 'on', 'adminmail' => 'off', 'newuser' => 'off', ); @@ -5457,14 +5454,14 @@ sub publishlogo { !defined(&Apache::loncommon::fileembstyle($1))) { $output = &mt('Unrecognized file extension ([_1]) - rename the file with a proper extension and re-upload.',$1); } elsif ($file=~/\.(\d+)\.(\w+)$/) { - $output = &mt('File name not allowed - rename the file to remove the number immediately before the file extension([_1]) and re-upload.',$2); + $output = &mt('Filename not allowed - rename the file to remove the number immediately before the file extension([_1]) and re-upload.',$2); } elsif (-d "$filepath/$file") { - $output = &mt('File name is a directory name - rename the file and re-upload'); + $output = &mt('Filename is a directory name - rename the file and re-upload'); } else { my $source = $filepath.'/'.$file; my $logfile; if (!open($logfile,">>$source".'.log')) { - return (&mt('No write permission to Construction Space')); + return (&mt('No write permission to Authoring Space')); } print $logfile "\n================= Publish ".localtime()." ================\n". @@ -5611,7 +5608,7 @@ sub write_metadata { print $logfile "\nWrite metadata file for ".$targetdir.'/'.$file; my $mfh; if (open($mfh,'>'.$targetdir.'/'.$file.'.meta')) { - foreach (sort keys %metadatafields) { + foreach (sort(keys(%metadatafields))) { unless ($_=~/\./) { my $unikey=$_; $unikey=~/^([A-Za-z]+)/; @@ -6553,7 +6550,7 @@ sub modify_contacts { my (%others,%to,%bcc); my @contacts = ('supportemail','adminemail'); my @mailings = ('errormail','packagesmail','helpdeskmail','lonstatusmail', - 'requestsmail'); + 'requestsmail','updatesmail'); foreach my $type (@mailings) { @{$newsetting{$type}} = &Apache::loncommon::get_env_multiple('form.'.$type); @@ -6609,6 +6606,7 @@ sub modify_contacts { $default{'helpdeskmail'} = 'supportemail'; $default{'lonstatusmail'} = 'adminemail'; $default{'requestsmail'} = 'adminemail'; + $default{'updatesmail'} = 'adminemail'; foreach my $item (@contacts) { if ($to{$item} ne $default{$item}) { $changes{$item} = 1; @@ -7851,7 +7849,6 @@ sub modify_coursedefaults { my ($dom,%domconfig) = @_; my ($resulttext,$errors,%changes,%defaultshash); my %defaultchecked = ('canuse_pdfforms' => 'off'); - my @offon = ('off','on'); my @toggles = ('canuse_pdfforms'); $defaultshash{'coursedefaults'} = {}; @@ -7868,7 +7865,7 @@ sub modify_coursedefaults { if (($domconfig{'coursedefaults'}{$item} eq '') && ($env{'form.'.$item} eq '0')) { $changes{$item} = 1; - } elsif ($domconfig{'coursdefaults'}{$item} ne $env{'form.'.$item}) { + } elsif ($domconfig{'coursedefaults'}{$item} ne $env{'form.'.$item}) { $changes{$item} = 1; } } elsif ($defaultchecked{$item} eq 'off') { @@ -7893,14 +7890,42 @@ sub modify_coursedefaults { $changes{'anonsurvey_threshold'} = 1; } } + my $officialcreds = $env{'form.official_credits'}; + $officialcreds =~ s/^[^\d\.]//g; + my $unofficialcreds = $env{'form.unofficial_credits'}; + $unofficialcreds =~ s/^[^\d\.]//g; + if (ref($domconfig{'coursedefaults'}{'coursecredits'} ne 'HASH') && + ($env{'form.coursecredits'} eq '1')) { + $changes{'coursecredits'} = 1; + } else { + if (($domconfig{'coursedefaults'}{'coursecredits'}{'official'} ne $officialcreds) || + ($domconfig{'coursedefaults'}{'coursecredits'}{'unofficial'} ne $unofficialcreds)) { + $changes{'coursecredits'} = 1; + } + } + $defaultshash{'coursedefaults'}{'coursecredits'} = { + official => $officialcreds, + unofficial => $unofficialcreds, + } } my $putresult = &Apache::lonnet::put_dom('configuration',\%defaultshash, $dom); if ($putresult eq 'ok') { + my %domdefaults; if (keys(%changes) > 0) { - if ($changes{'canuse_pdfforms'}) { - my %domdefaults = &Apache::lonnet::get_domain_defaults($dom); - $domdefaults{'canuse_pdfforms'}=$defaultshash{'coursedefaults'}{'canuse_pdfforms'}; + if (($changes{'canuse_pdfforms'}) || ($changes{'coursecredits'})) { + %domdefaults = &Apache::lonnet::get_domain_defaults($dom); + if ($changes{'canuse_pdfforms'}) { + $domdefaults{'canuse_pdfforms'}=$defaultshash{'coursedefaults'}{'canuse_pdfforms'}; + } + if ($changes{'coursecredits'}) { + if (ref($defaultshash{'coursedefaults'}{'coursecredits'}) eq 'HASH') { + $domdefaults{'officialcredits'} = + $defaultshash{'coursedefaults'}{'coursecredits'}{'official'}; + $domdefaults{'unofficialcredits'} = + $defaultshash{'coursedefaults'}{'coursecredits'}{'unofficial'}; + } + } my $cachetime = 24*60*60; &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); } @@ -7913,7 +7938,22 @@ sub modify_coursedefaults { $resulttext .= '
  • '.&mt('Course/Community users can create/upload PDF forms set to "off"').'
  • '; } } elsif ($item eq 'anonsurvey_threshold') { - $resulttext .= '
  • '.&mt('Responder count required for display of anonymous survey submissions set to [_1].',$defaultshash{'coursedefaults'}{'anonsurvey_threshold'}).'
  • '; + $resulttext .= '
  • '.&mt('Responder count required for display of anonymous survey submissions set to [_1].',$defaultshash{'coursedefaults'}{'anonsurvey_threshold'}).'
  • '; + } elsif ($item eq 'coursecredits') { + if (ref($defaultshash{'coursedefaults'}{'coursecredits'}) eq 'HASH') { + if (($domdefaults{'officialcredits'} eq '') && + ($domdefaults{'unofficialcredits'} eq '')) { + $resulttext .= '
  • '.&mt('Student credits not in use for courses in this domain').'
  • '; + } else { + $resulttext .= '
  • '.&mt('Student credits can be set per course by a Domain Coordinator, with the following defaults applying:').'
      '. + '
    • '.&mt('Official courses: [_1]',$defaultshash{'coursedefaults'}{'coursecredits'}{'official'}).'
    • '. + '
    • '.&mt('Unofficial courses: [_1]',$defaultshash{'coursedefaults'}{'coursecredits'}{'unofficial'}).'
    • '. + '
    '. + '
  • '; + } + } else { + $resulttext .= '
  • '.&mt('Student credits not in use for courses in this domain').'
  • '; + } } } $resulttext .= ''; @@ -8438,73 +8478,94 @@ sub recurse_cat_deletes { sub get_active_dcs { my ($dom) = @_; - my %dompersonnel = &Apache::lonnet::get_domain_roles($dom,['dc']); + my $now = time; + my %dompersonnel = &Apache::lonnet::get_domain_roles($dom,['dc'],$now,$now); my %domcoords; my $numdcs = 0; - my $now = time; foreach my $server (keys(%dompersonnel)) { foreach my $user (sort(keys(%{$dompersonnel{$server}}))) { my ($trole,$uname,$udom,$runame,$rudom,$rsec) = split(/:/,$user); - my ($end,$start) = split(':',$dompersonnel{$server}{$user}); - if (($end eq '') || ($end == 0) || ($end > $now)) { - if ($start <= $now) { - $domcoords{$uname.':'.$udom} = $dompersonnel{$server}{$user}; - } - } + $domcoords{$uname.':'.$udom} = $dompersonnel{$server}{$user}; } } return %domcoords; } sub active_dc_picker { - my ($dom,$curr_dc) = @_; + my ($dom,$numinrow,$inputtype,$name,%currhash) = @_; my %domcoords = &get_active_dcs($dom); - my @dcs = sort(keys(%domcoords)); - my $numdcs = scalar(@dcs); - my $datatable; - my $numinrow = 2; + my @domcoord = keys(%domcoords); + if (keys(%currhash)) { + foreach my $dc (keys(%currhash)) { + unless (exists($domcoords{$dc})) { + push(@domcoord,$dc); + } + } + } + @domcoord = sort(@domcoord); + my $numdcs = scalar(@domcoord); + my $rows = 0; + my $table; if ($numdcs > 1) { - $datatable = ''; - for (my $i=0; $i<@dcs; $i++) { + $table = '
    '; + for (my $i=0; $i<@domcoord; $i++) { my $rem = $i%($numinrow); if ($rem == 0) { if ($i > 0) { - $datatable .= ''; + $table .= ''; } - $datatable .= ''; + $table .= ''; + $rows ++; } - my $check = ' '; - if ($curr_dc eq '') { - if (!$i) { - $check = ' checked="checked" '; + my $check = ''; + if ($inputtype eq 'radio') { + if (keys(%currhash) == 0) { + if (!$i) { + $check = ' checked="checked"'; + } + } elsif (exists($currhash{$domcoord[$i]})) { + $check = ' checked="checked"'; + } + } else { + if (exists($currhash{$domcoord[$i]})) { + $check = ' checked="checked"'; } - } elsif ($dcs[$i] eq $curr_dc) { - $check = ' checked="checked" '; } - if ($i == @dcs - 1) { + if ($i == @domcoord - 1) { my $colsleft = $numinrow - $rem; if ($colsleft > 1) { - $datatable .= ''; } - my ($dcname,$dcdom) = split(':',$dcs[$i]); - $datatable .= ''; } - $datatable .= '
    '; + $table .= ''; } else { - $datatable .= ''; + $table .= ''; } } else { - $datatable .= ''; + $table .= ''; + } + my ($dcname,$dcdom) = split(':',$domcoord[$i]); + my $user = &Apache::loncommon::plainname($dcname,$dcdom); + $table .= '
    '; - } elsif (@dcs) { - $datatable .= ''; + $table .= ''; + } elsif ($numdcs == 1) { + if ($inputtype eq 'radio') { + $table .= ''; + } else { + my $check; + if (exists($currhash{$domcoord[0]})) { + $check = ' checked="checked"'; + } + $table .= ''; + $rows ++; + } } - return ($numdcs,$datatable); + return ($numdcs,$table,$rows); } sub usersession_titles { @@ -9024,6 +9085,40 @@ function updateCaptcha(caller,context) { } } return; +} + +// ]]> + + +END + +} + +sub credits_js { + return <<"END"; + +