--- loncom/interface/statistics/lonstudentassessment.pm 2006/02/27 02:25:44 1.130 +++ loncom/interface/statistics/lonstudentassessment.pm 2010/02/12 15:11:08 1.150.2.4 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonstudentassessment.pm,v 1.130 2006/02/27 02:25:44 bowersj2 Exp $ +# $Id: lonstudentassessment.pm,v 1.150.2.4 2010/02/12 15:11:08 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -50,16 +50,20 @@ Presents assessment data about a student package Apache::lonstudentassessment; use strict; -use Apache::lonstatistics; -use Apache::lonhtmlcommon; +use Apache::lonstatistics(); +use Apache::lonhtmlcommon(); use Apache::loncommon(); use Apache::loncoursedata; use Apache::lonnet; # for logging porpoises use Apache::lonlocal; -use Apache::grades; +use Apache::grades(); +use Apache::lonmsgdisplay(); use Time::HiRes; use Spreadsheet::WriteExcel; use Spreadsheet::WriteExcel::Utility(); +use lib '/home/httpd/lib/perl/'; +use LONCAPA; + ####################################################### ####################################################### @@ -146,6 +150,7 @@ sub BuildStudentAssessmentPage { 'chartoutputmode' => 'scalar', 'chartoutputdata' => 'scalar', 'Section' => 'array', + 'Groups' => 'array', 'StudentData' => 'array', 'Maps' => 'array'); &Apache::loncommon::store_course_settings('chart',\%Saveable_Parameters); @@ -243,14 +248,14 @@ sub next_and_previous_buttons { if (defined($previous)) { my $sname = $previous->{'username'}.':'.$previous->{'domain'}; $previousbutton .= '<input type="button" value="'. - 'Previous Student ('. - $previous->{'username'}.'@'.$previous->{'domain'}.')'. + &mt('Previous Student ([_1])', + $previous->{'username'}.':'.$previous->{'domain'}). '" onclick="document.Statistics.SelectedStudent.value='. "'".$sname."'".';'. 'document.Statistics.submit();" />'; } else { $previousbutton .= '<input type="button" value="'. - 'Previous student (none)'.'" />'; + &mt('Previous student').'" disabled="disabled" />'; } # # Build the next student link @@ -259,19 +264,19 @@ sub next_and_previous_buttons { if (defined($next)) { my $sname = $next->{'username'}.':'.$next->{'domain'}; $nextbutton .= '<input type="button" value="'. - 'Next Student ('. - $next->{'username'}.'@'.$next->{'domain'}.')'. + &mt('Next Student ([_1])', + $next->{'username'}.':'.$next->{'domain'}). '" onclick="document.Statistics.SelectedStudent.value='. - "'".$sname."'".';'. + "'$sname'".';'. 'document.Statistics.submit();" />'; } else { $nextbutton .= '<input type="button" value="'. - 'Next student (none)'.'" />'; + &mt('Next student').'" disabled="disabled" />'; } # # Build the 'all students' button my $all = ''; - $all .= '<input type="button" value="All Students" '. + $all .= '<input type="button" value="'.&mt('All Students').'" '. '" onclick="document.Statistics.SelectedStudent.value='. "''".';'.'document.Statistics.submit();" />'; $Str .= $previousbutton.(' 'x5).$all.(' 'x5).$nextbutton; @@ -313,14 +318,24 @@ the chart page. ####################################################### sub CreateInterface { my $Str = ''; - $Str .= &Apache::lonhtmlcommon::breadcrumbs(undef,'Chart'); + $Str .= &Apache::lonhtmlcommon::breadcrumbs('Chart','Chart_Description:Chart_Sections:Chart_Student_Data:Chart_Enrollment_Status:Chart_Sequences:Chart_Output_Formats:Chart_Output_Data'); # $Str .= &CreateLegend(); $Str .= '<table cellspacing="5">'."\n"; $Str .= '<tr>'; - $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>'; - $Str .= '<td align="center"><b>'.&mt('Student Data</b>').'</td>'; - $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>'; - $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>'; + $Str .= '<td align="center"><b>'.&mt('Sections').'</b>'. + &Apache::loncommon::help_open_topic("Chart_Sections"). + '</td>'; + $Str .= '<td align="center"><b>'.&mt('Groups').'</b>'. + '</td>'; + $Str .= '<td align="center"><b>'.&mt('Student Data').'</b>'. + &Apache::loncommon::help_open_topic("Chart_Student_Data"). + '</td>'; + $Str .= '<td align="center"><b>'.&mt('Access Status').'</b>'. + &Apache::loncommon::help_open_topic("Chart_Enrollment_Status"). + '</td>'; + $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b>'. + &Apache::loncommon::help_open_topic("Chart_Sequences"). + '</td>'; $Str .= '<td align="center"><b>'.&mt('Output Format').'</b>'. &Apache::loncommon::help_open_topic("Chart_Output_Formats"). '</td>'; @@ -332,6 +347,8 @@ sub CreateInterface { $Str .= '<tr><td align="center">'."\n"; $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); $Str .= '</td><td align="center">'; + $Str .= &Apache::lonstatistics::GroupSelect('Group','multiple',5); + $Str .= '</td><td align="center">'; $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple', 5,undef); $Str .= '</td><td>'."\n"; @@ -344,19 +361,20 @@ sub CreateInterface { $Str .= &CreateAndParseOutputDataSelector(); $Str .= '</td></tr>'."\n"; $Str .= '</table>'."\n"; - $Str .= '<input type="submit" name="Generate Chart" value="'. - &mt('Generate Chart').'" />'; - $Str .= ' 'x5; + $Str .= '<p>'. + &mt('Status:').' '. + '<input type="text" name="stats_status" size="60" value="" readonly="readonly" />'. + '</p>'; $Str .= '<input type="submit" name="selectstudent" value="'. &mt('Select One Student').'" />'; $Str .= ' 'x5; $Str .= '<input type="submit" name="ClearCache" value="'. &mt('Clear Caches').'" />'; - $Str .= ' 'x5; - $Str .= - &mt('Status [_1]', - '<input type="text" name="stats_status" size="60" value="" />'); - $Str .= '<br />'; + $Str .= '<p>'. + '<input type="submit" name="Generate Chart"'. + ' value="'.&mt('Generate Chart').'" />'. + '</p>'; + return $Str; } @@ -529,7 +547,7 @@ my @OutputDataOptions = summary_table => 1, maximum_row => 0, ignore_weight => 1, - shortdesc => 'Number of Problem Parts completed successfully.', + shortdesc => 'Number of Problem Parts completed successfully', longdesc => 'The Number of Problem Parts completed successfully and '. 'the maximum possible for each student', }, @@ -537,7 +555,8 @@ my @OutputDataOptions = sub HTMLifyOutputDataDescriptions { my $Str = ''; - $Str .= "<h2>Output Data</h2>\n"; + $Str .= '<h2>'.&mt('Output Data').'</h2>'."\n"; + $Str .= "<dl>\n"; foreach my $option (@OutputDataOptions) { $Str .= ' <dt>'.$option->{'name'}.'</dt>'; @@ -646,16 +665,29 @@ sub html_initialize { &Apache::lonstatistics::selected_sequences_with_assessments(); if (! ref($navmap)) { # Unable to get data, so bail out - $r->print("<h3>". + $r->print('<p class="LC_error">'. &mt('Unable to retrieve course information.'). - '</h3>'); + '</p>'); + } + + # If we're showing links, show a checkbox to open in new + # windows. + if ($show_links ne 'no') { + my $labeltext = &mt('Show links in new window:'); + $r->print(<<NEW_WINDOW_CHECKBOX); +<script type="text/javascript">new_window = true;</script> +<p><label>$labeltext +<input type="checkbox" checked="checked" onclick="new_window=this.checked" /> +</label></p> +NEW_WINDOW_CHECKBOX } + # $r->print("<h3>".$env{'course.'.$env{'request.course.id'}.'.description'}. - " ".localtime(time)."</h3>"); + " ".&Apache::lonlocal::locallocaltime(time)."</h3>"); # if ($chosen_output->{'base'} !~ /^final table/) { - $r->print("<h3>".$chosen_output->{'shortdesc'}."</h3>"); + $r->print("<h3>".&mt($chosen_output->{'shortdesc'})."</h3>"); } my $Str = "<pre>\n"; # First, the @StudentData fields need to be listed @@ -719,6 +751,58 @@ sub html_initialize { $Str .= "<pre>"; $r->print($Str); $r->rflush(); + + $r->print(<<JS); +<script type="text/javascript"> +// get the left offset of a given widget as an absolute position +function getLeftOffset (element) { + return collect(element, "offsetLeft"); +} + +// get the top offset of a given widget as an absolute position +function getTopOffset (element) { + return collect(element, "offsetTop"); +} + +function collect(element, att) { + var val = 0; + while(element) { + val += element[att]; + element = element.offsetParent; + } + return val; +} + +var currentDiv; +var currentElement; +function popup_score(element, score) { + popdown_score(); + var left = getLeftOffset(element); + var top = getTopOffset(element); + var div = document.createElement("div"); + div.className = "LC_chrt_popup"; + div.appendChild(document.createTextNode(score)); + div.style.position = "absolute"; + div.style.top = (top - 25) + "px"; + div.style.left = (left - 10) + "px"; + currentDiv = div; + document.body.insertBefore(div, document.body.childNodes[0]); + element.className = "LC_chrt_popup_up"; + currentElement = element; +} + +function popdown_score() { + if (currentDiv) { + document.body.removeChild(currentDiv); + } + if (currentElement) { + currentElement.className = 'LC_chrt_popup_exists'; + } + currentDiv = undefined; +} +</script> +JS + # # Let the user know what we are doing my $studentcount = scalar(@Apache::lonstatistics::Students); @@ -762,12 +846,20 @@ sub html_outputstudent { my @tmp = &Apache::loncoursedata::get_current_state ($student->{'username'},$student->{'domain'},undef, $env{'request.course.id'}); - if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) { + if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:(.*)/)) { %StudentsData = @tmp; - } - if (scalar(@tmp) < 1) { + } else { + my $error = $1; + if (scalar(@tmp) < 1) { + $Str .= '<span class="LC_warning">' + .&mt('No Course Data') + .'</span>'."\n"; + } else { + $Str .= '<span class="LC_error">' + .&mt('Error getting student data ([_1])',$error) + .'</span>'."\n"; + } $nodata_count++; - $Str .= '<font color="blue">No Course Data</font>'."\n"; $r->print($Str); $r->rflush(); return; @@ -872,12 +964,12 @@ sub html_finish { sub StudentAverageTotal { my $Str = '<h3>'.&mt('Summary Tables').'</h3>'.$/; - $Str .= '<table border=2 cellspacing="1">'."\n"; - $Str .= '<tr>'. + $Str .= &Apache::loncommon::start_data_table(); + $Str .= &Apache::loncommon::start_data_table_header_row(). '<th>'.&mt('Title').'</th>'. '<th>'.&mt('Average').'</th>'. '<th>'.&mt('Maximum').'</th>'. - '</tr>'.$/; + &Apache::loncommon::end_data_table_header_row().$/; foreach my $seq (@sequences) { my $symb = $seq->symb; my $ave; @@ -891,46 +983,53 @@ sub StudentAverageTotal { } my $max = $Statistics->{$symb}->{'max'}; $ave = sprintf("%.2f",$ave); - $Str .= '<tr><td>'.$seq->compTitle.'</td>'. + $Str .= &Apache::loncommon::start_data_table_row(). + '<td>'.$seq->compTitle.'</td>'. '<td align="right">'.$ave.' </td>'. - '<td align="right">'.$max.' '.'</td></tr>'."\n"; + '<td align="right">'.$max.' '.'</td>'. + &Apache::loncommon::end_data_table_row()."\n"; } - $Str .= "</table>\n"; + $Str .= &Apache::loncommon::end_data_table()."\n"; return $Str; } sub SingleStudentTotal { return if (! defined($navmap)); my $student = &Apache::lonstatistics::current_student(); - my $Str = '<h3>'.&mt('Summary table for [_1] ([_2]@[_3])', + my $Str = '<h3>'.&mt('Summary table for [_1] ([_2])', $student->{'fullname'}, - $student->{'username'},$student->{'domain'}).'</h3>'; + $student->{'username'}.':'.$student->{'domain'}).'</h3>'; $Str .= $/; - $Str .= '<table border=2 cellspacing="1">'."\n"; + $Str .= &Apache::loncommon::start_data_table()."\n"; $Str .= - '<tr>'. + &Apache::loncommon::start_data_table_header_row(). '<th>'.&mt('Sequence or Folder').'</th>'; if ($chosen_output->{'base'} eq 'tries') { $Str .= '<th>'.&mt('Parts Correct').'</th>'; } else { $Str .= '<th>'.&mt('Score').'</th>'; } - $Str .= '<th>'.&mt('Maximum').'</th>'."</tr>\n"; + $Str .= '<th>'.&mt('Maximum').'</th>'. + &Apache::loncommon::end_data_table_header_row()."\n"; my $total = 0; my $total_max = 0; foreach my $seq (@sequences) { my $value = $Statistics->{$seq->symb}->{'score'}; my $max = $Statistics->{$seq->symb}->{'max'}; - $Str .= '<tr><td>'.&HTML::Entities::encode($seq->compTitle).'</td>'. + $Str .= &Apache::loncommon::start_data_table_row(). + '<td>'.&HTML::Entities::encode($seq->compTitle).'</td>'. '<td align="right">'.$value.'</td>'. - '<td align="right">'.$max.'</td></tr>'."\n"; + '<td align="right">'.$max.'</td>'. + &Apache::loncommon::end_data_table_row()."\n"; $total += $value; $total_max +=$max; } - $Str .= '<tr><td><b>'.&mt('Total').'</b></td>'. + $Str .= &Apache::loncommon::start_data_table_row(). + '<td><b>'.&mt('Total').'</b></td>'. '<td align="right">'.$total.'</td>'. - '<td align="right">'.$total_max."</td></tr>\n"; - $Str .= "</table>\n"; + '<td align="right">'.$total_max.'</td>'. + &Apache::loncommon::end_data_table_row()."\n"; + $Str .= &Apache::loncommon::end_data_table()."\n"; return $Str; } @@ -1080,11 +1179,11 @@ sub excel_initialize { # # Put the date in there too $excel_sheet->write($header_row,$cols_output++, - 'Compiled on '.localtime(time),$format->{'h3'}); + &mt('Compiled on [_1]',&Apache::lonlocal::locallocaltime(time)),$format->{'h3'}); # $cols_output = 0; $excel_sheet->write($description_row,$cols_output++, - $chosen_output->{'shortdesc'}, + &mt($chosen_output->{'shortdesc'}), $format->{'b'}); # $cols_output = 0; @@ -1162,17 +1261,17 @@ sub excel_initialize { $formula_data{$symb}->{'Excel:scorecol'}=$cols_output; if ($chosen_output->{'base'} eq 'parts correct total') { $excel_sheet->write($resource_name_row,$cols_output++, - 'parts correct', + &mt('parts correct'), $format->{'bold'}); } elsif ($chosen_output->{'sequence_sum'}) { if ($chosen_output->{'correct'}) { # Only reporting the number correct, so do not call it score $excel_sheet->write($resource_name_row,$cols_output++, - 'sum', + &mt('sum'), $format->{'bold'}); } else { $excel_sheet->write($resource_name_row,$cols_output++, - 'score', + &mt('score'), $format->{'bold'}); } } @@ -1182,7 +1281,7 @@ sub excel_initialize { ($first_data_row,$cols_output-1); if ($chosen_output->{'sequence_max'}) { $excel_sheet->write($resource_name_row,$cols_output, - 'maximum', + &mt('maximum'), $format->{'bold'}); $formula_data{$symb}->{'Excel:maxcell'} = &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell @@ -1196,11 +1295,11 @@ sub excel_initialize { } } if ($chosen_output->{'grand_total'}) { - $excel_sheet->write($resource_name_row,$cols_output++,'Total', + $excel_sheet->write($resource_name_row,$cols_output++,&mt('Total'), $format->{'bold'}); } if ($chosen_output->{'grand_maximum'}) { - $excel_sheet->write($resource_name_row,$cols_output++,'Max. Total', + $excel_sheet->write($resource_name_row,$cols_output++,&mt('Max. Total'), $format->{'bold'}); } $total_formula = $excel_sheet->store_formula($total_formula_string); @@ -1291,17 +1390,17 @@ sub excel_initialize { if ($chosen_output->{'summary_table'}) { $cols_output = 0; $excel_sheet->write($summary_header_row,$cols_output++, - 'Summary Table',$format->{'bold'}); + &mt('Summary Table'),$format->{'bold'}); if ($chosen_output->{'maximum_row'}) { $excel_sheet->write($summary_header_row,$cols_output++, - 'Maximum',$format->{'bold'}); + &mt('Maximum'),$format->{'bold'}); } $excel_sheet->write($summary_header_row,$cols_output++, - 'Average',$format->{'bold'}); + &mt('Average'),$format->{'bold'}); $excel_sheet->write($summary_header_row,$cols_output++, - 'Median',$format->{'bold'}); + &mt('Median'),$format->{'bold'}); $excel_sheet->write($summary_header_row,$cols_output++, - 'Std Dev',$format->{'bold'}); + &mt('Std Dev'),$format->{'bold'}); my $row = $summary_header_row+1; foreach my $seq (@sequences) { my $symb = $seq->symb; @@ -1341,14 +1440,10 @@ sub excel_initialize { if ($env{'form.SelectedStudent'}) { $studentcount = '1'; } - if ($studentcount > 1) { - $r->print('<h1>'.&mt('Compiling Excel spreadsheet for [_1] students', - $studentcount)."</h1>\n"); - } else { - $r->print('<h1>'. - &mt('Compiling Excel spreadsheet for 1 student'). - "</h1>\n"); - } + $r->print('<p>' + .&mt('Compiling Excel spreadsheet for [quant,_1,student]...',$studentcount) + ."</p>\n" + ); $r->rflush(); # # Initialize progress window @@ -1374,7 +1469,7 @@ sub excel_outputstudent { foreach my $field (@to_show) { my $value = $student->{$field}; if ($field eq 'comments') { - $value = &Apache::lonmsg::retrieve_instructor_comments + $value = &Apache::lonmsgdisplay::retrieve_instructor_comments ($student->{'username'},$student->{'domain'}); } $excel_sheet->write($rows_output,$cols_output++,$value); @@ -1496,7 +1591,7 @@ sub excel_finish { # # Tell the user where to get their excel file $r->print('<br />'. - '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n"); + '<a href="'.$filename.'">'.&mt('Your Excel spreadsheet').'</a>'."\n"); $r->rflush(); &excel_cleanup(); return; @@ -1619,23 +1714,23 @@ END $sequence_row.='"",'x$count; if ($chosen_output->{'sequence_sum'}) { if($chosen_output->{'correct'}) { - $resource_row .= '"sum",'; + $resource_row .= '"'.&mt('sum').'",'; } else { - $resource_row .= '"score",'; + $resource_row .= '"'.&mt('score').'",'; } } if ($chosen_output->{'sequence_max'}) { $sequence_row.= '"",'; - $resource_row .= '"maximum possible",'; + $resource_row .= '"'.&mt('maximum possible').'",'; } } if ($chosen_output->{'grand_total'}) { $sequence_row.= '"",'; - $resource_row.= '"Total",'; + $resource_row.= '"'.&mt('Total').'",'; } if ($chosen_output->{'grand_maximum'}) { $sequence_row.= '"",'; - $resource_row.= '"Maximum",'; + $resource_row.= '"'.&mt('Maximum').'",'; } chomp($sequence_row); chomp($resource_row); @@ -1656,7 +1751,7 @@ sub csv_outputstudent { foreach my $field (@to_show) { my $value = $student->{$field}; if ($field eq 'comments') { - $value = &Apache::lonmsg::retrieve_instructor_comments + $value = &Apache::lonmsgdisplay::retrieve_instructor_comments ($student->{'username'},$student->{'domain'}); } $Str .= '"'.&Apache::loncommon::csv_translate($value).'",'; @@ -1739,7 +1834,7 @@ sub csv_finish { # # Tell the user where to get their csv file $r->print('<br />'. - '<a href="'.$filename.'">'.&mt('Your csv file.').'</a>'."\n"); + '<a href="'.$filename.'">'.&mt('Your CSV file.').'</a>'."\n"); $r->rflush(); &csv_cleanup(); return; @@ -1748,6 +1843,18 @@ sub csv_finish { } +# This function will return an HTML string including a star, with +# a mouseover popup showing the "real" value. An optional second +# argument lets you show something other than a star. +sub show_star { + my $popup = shift; + my $symbol = shift || '*'; + # Escape the popup for JS. + $popup =~ s/([^-a-zA-Z0-9:;,._ ()|!\/?=&*])/'\\' . sprintf("%lo", ord($1))/ge; + + return "<span class=\"LC_chrt_popup_exists\" onmouseover='popup_score(this, \"$popup\");return false;' onmouseout='popdown_score();return false;'>$symbol</span>"; +} + ####################################################### ####################################################### @@ -1813,12 +1920,12 @@ sub student_tries_on_sequence { if ($status eq 'excused') { $symbol = 'x'; $max--; - } elsif ($status eq 'correct_by_override') { + } elsif ($status eq 'correct_by_override' && !$resource->is_task()) { $symbol = '+'; $sum++; } elsif ($tries > 0) { if ($tries > 9) { - $symbol = '*'; + $symbol = show_star($tries); } else { $symbol = $tries; } @@ -1858,14 +1965,12 @@ sub student_tries_on_sequence { # if ( ($links eq 'yes' && $symbol ne ' ') || ($links eq 'all')) { - if (length($symbol) > 1) { - &Apache::lonnet::logthis('length of symbol "'.$symbol.'" > 1'); - } - $symbol = '<a href="/adm/grades'. - '?symb='.&Apache::lonnet::escape($resource->symb). + my $link = '/adm/grades'. + '?symb='.&escape($resource->shown_symb). '&student='.$student->{'username'}. '&userdom='.$student->{'domain'}. - '&command=submission">'.$symbol.'</a>'; + '&command=submission'; + $symbol = &link($symbol, $link); } $value .= $symbol; } @@ -1880,6 +1985,37 @@ sub student_tries_on_sequence { return ($Str,$performance_length,$sum,$max,\@TriesData); } +=pod + +=item &link + +Inputs: + +=over 4 + +=item $text + +=item $target + +=back + +Takes the text and creates a link to the $text that honors +the value of 'new window' if clicked on, but uses a real +'href' so middle and right clicks still work. + +$target and $text are assumed to be already correctly escaped; i.e., it +can be dumped out directly into the output stream as-is. + +=cut + +sub link { + my ($text,$target) = @_; + return + "<a href='$target' onclick=\"t=this.href;if(new_window)" + ."{window.open(t)}else{return void(window." + ."location=t)};return false;\">$text</a>"; +} + ####################################################### ####################################################### @@ -1943,7 +2079,7 @@ sub student_performance_on_sequence { $hasdata = 1; } # - $partscore = Apache::grades::compute_points($weight,$awarded); + $partscore = &Apache::grades::compute_points($weight,$awarded); if (! defined($awarded)) { $partscore = undef; } @@ -1953,7 +2089,7 @@ sub student_performance_on_sequence { $symbol = sprintf("%.0f",$symbol); } if (length($symbol) > 1) { - $symbol = '*'; + $symbol = show_star($symbol); } if (exists($resource_data->{'resource.'.$part.'.solved'}) && $resource_data->{'resource.'.$part.'.solved'} ne '') { @@ -1984,11 +2120,12 @@ sub student_performance_on_sequence { push (@ScoreData,$partscore); # if ( ($links eq 'yes' && $symbol ne ' ') || ($links eq 'all')) { - $symbol = '<a href="/adm/grades'. - '?symb='.&Apache::lonnet::escape($symb). + my $link = '/adm/grades' . + '?symb='.&escape($resource->shown_symb). '&student='.$student->{'username'}. '&userdom='.$student->{'domain'}. - '&command=submission">'.$symbol.'</a>'; + '&command=submission'; + $symbol = &link($symbol, $link); } $Str .= $symbol; }