ONO::FW::Apps::Core::Question

package ONO::FW::Apps::Core::Question;
################################################################################
# COPYRIGHT / LICENSE #
################################################################################
#
# This file is part of the ONO Software Project.
#
# Copyright (C) 2000-2025 Jos KIRPS [ www.kirps.com | jos_AT_kirps_DOT_com ]
# and The Joopita Project [ www.joopita.org | contact_AT_joopita_DOT_com ]
#
# This file, as well as other parts of the ONO Software Project or related
# elements, are FREE SOFTWARE available under the ARTISTIC LICENSE 2.0.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# For the full license, see /ono/osr/license/LICENSE.txt, or write to
# jos_AT_kirps_DOT_com or contact_AT_joopita_DOT_com.
#
################################################################################
# END OF COPYRIGHT / LICENSE, HERE COMES THE CODE ... #
################################################################################

use strict;

use ONO::IO;
use ONO::DB;

use ONO::Lib::UI::Progress;

use ONO::Lib::DateTime::ToolBox;
use ONO::Lib::Code::RandomID;
use ONO::Lib::PDF::ToolBox;
use ONO::Lib::PDF::Draw;
use ONO::Lib::PDF::Gen;
use ONO::Lib::Mail::ToolBox;
use ONO::Lib::Web::Domain;

use ONO::FW::Apps::Core;
use ONO::FW::Apps::Core::LangKit;
use ONO::FW::Apps::Core::Views;
use ONO::FW::Apps::ToolBox;

use ONO::FW::Apps::PDF;
use ONO::FW::User::Screen::Dashboard::Microblog;
use ONO::FW::User::Init;

use ONO::ToolBox::Logfile;

###################################################################
#
###################################################################

#: This module offers functions that allow tracking exercise progress.

sub question_number {

#: Check if question number is valid.

my $num = $_[1];
if ($num < 2) {
$num = 1;
}
return $num;

}

sub question_progress {

my (
$self,
$total,
$question,
$MORE,
$switches,
) = @_;

#: Track exercise progress
#:
#: Switches:
#:
#: -f box_fabric
#: -i info field (can be used for mistakes, stats, etc...)
#: -p box_paper
#: -P no padding

if ($total) {

my $question2 = $question;
$question--;

if ($question > $total) {
$question = $total;
}
if ($question2 > $total) {
$question2 = $total;
}

my $CLASS = "p10";
if ($switches =~ /P/) {
$CLASS = "";
}
if ($switches =~ /f/) {
$CLASS = "box_fabric";
}
if ($switches =~ /p/) {
$CLASS = "box_paper";
}

my $percent = int(100/$total*$question)+1;
if ($percent > 100) {
$percent = 100;
}

my $PROG = qq~<div class="$CLASS"><div class="bg_paper green">~.ONO::Lib::UI::Progress->percent($percent,"","","","","","","$question2 / $total","Rt").qq~</div>$MORE</div>~;

if ($switches =~ /i/) {
$PROG = qq~<table class="wide_table">
<tr>
<td class="p0 w100">$PROG</td>
<td class="p0"><div class="###PROGRESS_INFO_CLASS### radius10 bo pad10_0 lh125 center nowrap mr10">###PROGRESS_INFO###</div></td>
</tr>
</table>
~;
}

return $PROG;

} else {

return $MORE;

}

}

sub question_progress_done {

#: Check if exercise has been completed.

if ($_[1] && $_[2] > $_[1]) {

return 1;

} else {

return 0;

}

}

sub question_progress_done_msg {

my (
$self,
$app,
$db,
$community,
$form,
$action_url,
$questions,
$mistakes,
$lang,
$BLK_ref,
$vars_ref,
$switches,
) = @_;

#: Exercise completed message + followup options.
#:
#: Switches:
#:
#: -M don't show mistakes
#: -R don't allow repeating

my %BLK = %$BLK_ref;
my %vars = %$vars_ref;

if (ONO::IO->devstation) {
# $vars{'username'} = "jos";
}

my $APPS_BASE = ONO::FW::Apps::ToolBox->apps_base();

if ($vars{'username'} || $vars{'virclass'} || $vars{'anonymous'}) {

my $DIR = ONO::FW::Apps::Core->varstoredir($vars{'username'},$vars{'virclass'},$vars{'anonymous'});

if ($DIR =~ /[A-Za-z0-9]/) {

foreach my $sub (ONO::IO->dir($DIR)) {
foreach my $file (ONO::IO->dir("$DIR/$sub")) {
if ($file =~ /\.txt/) {
ONO::IO->rm("$DIR/$sub/$file");
ONO::IO->rmdir("$DIR/$sub");
}
}
}

$vars{'varstoreaction'} = "delete";
($vars_ref,%vars) = ONO::FW::Apps::Core->varstoreaction(\%vars);

}

}

my @txts = ONO::FW::Apps::Core::LangKit->question_progress_done_msg($lang);

my ($APP,%info);
foreach my $line (ONO::IO->list("var/community/$community/apps/$vars{'app_name'}.txt")) {
if ($line =~ m~^(.*?):(.*):~) {
$info{$1} = $2;
}
}

if (!$APP && $info{'title'}) {
$APP = $info{'title'};
}
if (!$APP && $info{"title_$vars{'app_lang'}"}) {
$APP = $info{"title_$vars{'app_lang'}"};
}
if (!$APP) {
$APP = ucfirst $vars{'app_name'};
}

my (
$sec,$min,$hour,
$mday,$mon,$year,
$wday,$yday,$isdst
) = ONO::Lib::DateTime::ToolBox->get;

if ($vars{'output_web_report'}) {

my $pdfkey = "$year$mon$mday\_".ONO::Lib::Code::RandomID->make;
my $BASE = ONO::IO->base();
my $format = $vars{'app_input_paper_format_status'};
my $canvas = ONO::Lib::PDF::ToolBox->canvas_init($format,30,50,190,230);
my $count;

ONO::FW::Apps::PDF->new("$BASE/pdf/pdf-$pdfkey.pdf","",$format);

# print the headers

$vars{'auto_name'} = $vars{'username'};
$vars{'auto_date'} = 1;
$vars{'app_input_print_options_design'} = "default";

ONO::FW::Apps::Core->headers(\%vars,$format);

ONO::Lib::PDF::Draw->rect($canvas,0,0,160,180,1,"","0:200:200:180",2);
ONO::Lib::PDF::Draw->rect($canvas,5,5,155,175,1,"","0:220:220:200",2);
ONO::Lib::PDF::Draw->rect($canvas,10,10,150,170,1,"","0:250:250:220",2);

for (my $i = 0; $i < 4; $i++) {
if ($i < 3) {
ONO::Lib::PDF::Draw->line($canvas,60+$i*3,40+$i*3,100-$i*3,40+$i*3,1.2/($i+1));
}

}

ONO::Lib::PDF::Draw->font('',48);
ONO::Lib::PDF::Draw->text($canvas,80,30,$txts[9],"c");
ONO::Lib::PDF::Draw->font('',12);

ONO::Lib::PDF::Draw->font('HB',36);
ONO::Lib::PDF::Draw->text($canvas,80,65,$APP,"c");
ONO::Lib::PDF::Draw->font('H',8);
ONO::Lib::PDF::Draw->text($canvas,80,70,ONO::IO->http_domain_base."/$APPS_BASE/$vars{'app_name'}/","c");
ONO::Lib::PDF::Draw->font('',12);

my $time = ONO::Lib::DateTime::ToolBox->convert_ss2hhmmss($vars{'output_report_timer_elapsed'});

foreach my $opt (

"app_input_number_of_questions^$vars{'app_input_number_of_questions'}^$txts[2]^",
"output_report_mistakes^$vars{'output_report_mistakes'}^$txts[3]^",
"output_report_timer_elapsed^$time^$BLK{'Time'}^",

) {

my @op = split(/\^/,$opt);

if ($vars{$op[0]}) {

ONO::Lib::PDF::Draw->text($canvas,30,90+$count*5,"$op[2]:");
ONO::Lib::PDF::Draw->font('HB');
ONO::Lib::PDF::Draw->text($canvas,130,90+$count*5,$op[1],"r");
ONO::Lib::PDF::Draw->font('H');
ONO::Lib::PDF::Draw->line($canvas,30,92+$count*5,130,92+$count*5,0.5);
$count++;
$count++;

}

}

ONO::Lib::PDF::Gen->close();

my $WAIT = ONO::FW::Apps::PDF->please_wait(
$pdfkey,
$BLK_ref,
$vars_ref,
"",
ONO::IO->http,
ONO::IO->base,
"a",
);

return qq~<div class="abs bg_white w100" style="top:0px;left:0px;height:480px;overflow:hidden">$WAIT</div>~;

} else {

my ($MISTAKES,$HINTS,$TIME,$USER,$SHARE_BUTTON,$EXERCISE,$LOG1,$LOG2,$MAIL,$MAILDATA,$SHARE_REPORT,$PRINT_CERT);

$MAILDATA .= qq~$BLK{'User'}: $vars{'username'} \@ $hour:$min:$sec\n$txts[2]: $questions\n~;
$SHARE_REPORT .= qq~<span class="bold green">$questions</span> $BLK{'questions'}, ~;

if ($vars{'output_web_hints'} > 0 || $vars{'output_web_hints_single'} > 0) {
if ($vars{'output_web_hints_single'} > 0) {
$vars{'output_web_hints'} = $vars{'output_web_hints_single'};
}
$HINTS = qq~<tr class="vtop">
<td class="pad5_2 tar">$BLK{'Hints'}:</td>
<td class="pad5_2 tal bold lightred">$vars{'output_web_hints'}</td>
</tr>
~;
$MAILDATA .= qq~$BLK{'Hints'}: $vars{'output_web_hints'}\n~;
$SHARE_REPORT .= qq~<span class="bold green">$vars{'output_web_hints'}</span> $BLK{'hints'}, ~;
}

if ($switches !~ /M/) {
my $mistake_color = "lightred";
if ($mistakes < 1) {
$mistakes = 0;
$mistake_color = "green";
}
$MISTAKES = qq~<tr class="vtop">
<td class="pad5_2 tar">$txts[3]:</td>
<td class="pad5_2 tal bold $mistake_color">$mistakes</td>
</tr>
~;
$MAILDATA .= qq~$txts[3]: $mistakes\n~;
$SHARE_REPORT .= qq~<span class="bold green">$mistakes</span> $BLK{'mistakes'}, ~;
}

$vars{'output_web_timer_end'} = time;

if ($vars{'output_web_timer_start'} && $vars{'output_web_timer_end'}) {

$vars{'output_web_timer_elapsed'} = $vars{'output_web_timer_end'} - $vars{'output_web_timer_start'};
$vars{'output_web_timer_time'} = ONO::Lib::DateTime::ToolBox->convert_ss2hhmmss($vars{'output_web_timer_elapsed'});

$TIME = qq~<tr class="vtop">
<td class="pad5_2 tar">$BLK{'Time'}:</td>
<td class="pad5_2 tal bold green">$vars{'output_web_timer_time'}</td>
</tr>
~;
$MAILDATA .= qq~$BLK{'Time'}: $vars{'output_web_timer_time'}\n~;
$SHARE_REPORT .= qq~$BLK{'time'}: <span class="bold green">$vars{'output_web_timer_time'}</span>, ~;
}

my $exercise = $vars{'exercise'};
$exercise =~ s~[^A-Za-z0-9]~~g;

if ($vars{'username'} || $exercise) {
foreach my $key (sort keys %vars) {
if ($key =~ /^app_input_/) {
$LOG1 .= qq~$key=$vars{$key}:~;
}
}

my $ID = ONO::Lib::Code::RandomID->make(10);
my $NAME = ONO::DB->get($db,"name","${community}_school_apps_storage","exercise = '$exercise'");
$NAME =~ s~(\:|\;|\=)~~g;

$LOG2 = "app=$app:unique_id=$ID:custom_name=$NAME:output_web_questions=$questions:output_web_mistakes=$mistakes:";
foreach my $opt ('output_web_hints','output_web_timer_start','output_web_timer_end','output_web_timer_elapsed','output_web_timer_time','exercise_id','exercise') {
if ($vars{$opt}) {
$vars{$opt} =~ s~\:~\.~g;
$LOG2 .= "$opt=$vars{$opt}:";
}
}

}

my $SHARE_ICON = qq~<img class="app48 mr5" src="/images/apps/developer_64.png" alt="">~;
my $IMG = "images/apps/$app\_64.png";
if (ONO::IO->exists($IMG)) {
$SHARE_ICON = qq~<img class="app48 mr5" src="/$IMG" alt="">~;
}

if ($vars{'username'}) {

if ($vars{'output_web_share'}) {

$USER .= qq~<div class="mb10">
<div class="box_green w80 auto center p5">
<table class="default_table auto">
<tr>
<td><img class="block32 mr10" src="/ono/osr/images/icons/crystal/32x32/actions/ok.png" alt=""></td>
<td class="large">$txts[13]</td>
</tr>
</table>
</div>
</div>
~;

my $SHARE1 = substr($vars{'output_web_share_comment'},0,256);
my $SHARE2 = substr($vars{'output_web_share_data'},0,256);
$SHARE1 =~ s~\|~~g;
$SHARE2 =~ s~\|~~g;
my $SHARE = qq~<table class="default_table"><tr class="vtop"><td>$SHARE_ICON</td><td><div class="bold large">$APP</div><div class="mt5 mb5 large">$SHARE1</div><div>$SHARE2</div></td></tr></table>~;

my (@users,%used);

if ($vars{'output_web_share_with_friends'}) {

foreach my $line (ONO::DB->select($db,"${community}_community_friends","username = '$vars{'username'}' AND confirm = '1'")) {
my @row = ONO::DB->readcols($line);
@users = (@users,$row[2]);
}

}

if ($vars{'output_web_share_with_teachers'} || $vars{'output_web_share_with_students'}) {

my $TYPE = "type = 'teacher' OR type = 'owner'";
if ($vars{'output_web_share_with_students'}) {
$TYPE .= " OR type = 'student'";
}
if (!$vars{'output_web_share_with_teachers'}) {
$TYPE = "type = 'student'";
}

foreach my $line (ONO::DB->select($db,"${community}_school_virclass_relationships","username = '$vars{'username'}' AND status = '1'")) {
my @row = ONO::DB->readcols($line);
foreach my $line2 (ONO::DB->select($db,"${community}_school_virclass","id = '$row[2]' AND (type = 'real' OR type = 'virtual' OR type = 'project')")) {
my @row2 = ONO::DB->readcols($line2);
if ($row2[17] !~ /H/) {
foreach my $line3 (ONO::DB->select($db,"${community}_school_virclass_relationships","virclass = '$row[2]' AND status = '1' AND ($TYPE)")) {
my @row3 = ONO::DB->readcols($line3);
@users = (@users,$row3[1]);
}
}
}
}
}

my $realname = ONO::DB->get($db,"realname","${community}_community_profiles","username = '$vars{'username'}'");
my $ID = ONO::Lib::Code::RandomID->make();

my $FLAG;
if ($vars{'output_web_share_with_homepage'}) {
$FLAG .= "H";
}

my ($DEBUG1,$DEBUG2) = ONO::FW::User::Screen::Dashboard::Microblog->microblog_post_simple(
$community,
$vars{'username'},
$realname,
$vars{'username'},
$ID,
$year,$mon,$mday,
time(),
$SHARE,
$FLAG,
);

# $USER .= ONO::IO->devprint("$DEBUG1 // $DEBUG2");

my $count; # send to max 256 users

foreach my $user (@users) {
if (!$used{$user} && $user ne $vars{'username'} && $count < 256) {
$used{$user}++;
$count++;

# user is receiver, and don't transmit FLAG (homepage) here

ONO::FW::User::Screen::Dashboard::Microblog->microblog_post_simple(
$community,
$vars{'username'},
$realname,
$user,
$ID,
$year,$mon,$mday,
time(),
$SHARE,
);

}
}

} else {

$SHARE_BUTTON .= qq~<div class="inline mt10">
<a href="javascript:void(0);" onclick="onojs_hide('app_results_box');onojs_block('app_share_box');onojs_select('output_web_share_comment_id');"
class="button_green button_big ml10 mt10 mb10">$txts[10]</a>
</div>
~;

}

my $PROIMG = ONO::FW::User::Init->profileimage($vars{'username'},48);
my $User = ucfirst $vars{'username'};
$USER .= qq~<div>
<table class="default_table mt10">
<tr class="vtop">
<td><div class="mr5">$PROIMG</div></td>
<td>
<div class="large col6">
<span class="bold"><a href="/users/$vars{'username'}/" class="col3" target="_parent">$User</a></span>, $txts[14]
</div>
<div class="inline">
<a href="/account/?community_screen=custom01" class="button mt10" target="_parent">$txts[4]</a>
<a href="javascript:void(0);" class="button mt10" onclick="onojs_hide('app_results_box');onojs_block('app_print_box');">$txts[8]</a>
</div>
</td>
</tr>
</table>
</div>
~;

# big log for all apps (per user):

ONO::ToolBox::Logfile->log(
"var/community/$community/accounts/data/".ONO::IO->deepdir($vars{'username'})."/$vars{'username'}/apps/apps.log",
$vars{'username'},
$LOG1,
$LOG2,
);

# dedicated log for each single app (per user):

ONO::ToolBox::Logfile->log(
"var/community/$community/accounts/data/".ONO::IO->deepdir($vars{'username'})."/$vars{'username'}/apps/$app.log",
$vars{'username'},
$LOG1,
$LOG2,
);

} else {

$PRINT_CERT = qq~<div class="inline auto">
<a href="javascript:void(0);" class="button"
onclick="onojs_hide('app_results_box');onojs_block('app_print_box');">$txts[8]</a>
</div>
~;

if ($vars{'virclass_user'}) {

$USER .= qq~<div class="mt20"><div class="box_green w66 auto center large bold">$vars{'virclass_user'}</div></div>~;

} else {

my $LOGIN = qq~<a href="/account/" target="_parent" class="button_green mt10">$BLK{'login'}</a>
<a href="/account/join/" target="_parent" class="button_yellow mt10">$BLK{'signup_free'}!</a>
~;

if (ONO::Lib::Web::Domain->variation eq "education") {
$LOGIN = qq~<a href="/oli/$lang/cgi-bin/local/perl/school/cgie/login.pl" target="_parent" class="button_green mt10">IAM $BLK{'login'}</a>~;
}

$USER .= qq~<div>
<table class="default_table mt10">
<tr class="vtop">
<td><img class="block48 ml5" src="/ono/osr/images/icons/crystal/64x64/apps/ktip.png" alt=""></td>
<td>
<div class="large col6 mb5 bold">$txts[15]</div>
<div class="large col6 mb5">$txts[16]</div>
<div>$LOGIN</div>
</td>
</tr>
</table>
</div>
~;

}

}

if ($exercise && $vars{'exercise_id'}) {

my $exercise_id = $vars{'exercise_id'};
$exercise_id =~ s~[^A-Za-z0-9]~~g;

$EXERCISE = qq~<div class="center small colc mt10">$BLK{'Exercise'} ID: $exercise</div>~;

my $USER = $vars{'username'};
if (!$USER) {
$USER = "$BLK{'none_m'}/$BLK{'anonymous'}";
}

# dedicated log for this exercise (all users):

ONO::ToolBox::Logfile->log(
"var/community/$community/apps/log/".ONO::IO->deepdir($exercise)."/$exercise/$exercise.log",
$USER,
$LOG1,
$LOG2,
);

# update exercise stats:

my $timestamp = ONO::Lib::DateTime::ToolBox->timestamp;

if (ONO::DB->count($db,"${community}_school_apps_stats","exercise = '$exercise' AND username = '$USER'")) {

ONO::DB->inc($db,"completed","${community}_school_apps_stats","exercise = '$exercise' AND username = '$USER'");

} else {

ONO::DB->command(
$db,
"INSERT INTO ${community}_school_apps_stats
(id_10,exercise,app,username,completed,flags,
creation_username,creation_timestamp,modification_username,modification_timestamp) VALUES
('$exercise_id','$exercise','$app','$USER','1','',
'$USER','$timestamp','$USER','$timestamp');",
);

}

}

foreach my $line (ONO::DB->select($db,"${community}_school_apps_storage","exercise = '$exercise'")) {
my @row = ONO::DB->readcols($line);
my %used;

my $site = ONO::FW::Apps::Core->site();
my $domain = ONO::FW::Apps::Core->domain();
my $domain_open = ONO::FW::Apps::Core->domain();
if (ONO::Lib::Web::Domain->variation eq "education" || ONO::IO->devstation) {
$domain = "education.lu";
$domain_open = "ssl.education.lu/oli/lu";
}

my $date = ONO::Lib::DateTime::ToolBox->convert("date",$row[10]);

my $EXERINFO = "$BLK{'Exercise'}: $row[6] ($date)";

if ($row[5]) {
$MAILDATA .= qq~https://$domain_open/bin/virclass/?virclass=$row[5]&mode=exercises&exercise=$row[1]~;
} else {
$MAILDATA .= qq~https://$domain_open/account/?community_screen=custom01&exercise=$row[1]~;
}

my @mail_recipients;
my $MAIL_MSG;

if ($row[4] && $row[18] =~ /e/) {

if (ONO::DB->get($db,"flags","${community}_school_virclass_relationships","username = '$row[4]' AND virclass = '$row[5]'") !~ /x/) {

$used{$row[4]}++;
foreach my $line2 (ONO::DB->select($db,"${community}_community_users","username = '$row[4]'")) {
my @row2 = ONO::DB->readcols($line2);
if ($row2[1] =~ /^(.*?)\@(.*?)\.(.*?)$/) {
$MAIL_MSG .= $txts[5];

@mail_recipients = (@mail_recipients,$row2[1]);

}
}
}
}
if ($row[5] && $row[18] =~ /E/) {
my $virclass = ONO::DB->get($db,"fullname","${community}_school_virclass","id = '$row[5]'");
if (!$virclass) {
$virclass = $row[5];
}
my $found;
foreach my $line2 (ONO::DB->select($db,"${community}_school_virclass_relationships","virclass = '$row[5]' AND type = 'teacher' AND status = '1'")) {
my @row2 = ONO::DB->readcols($line2);
if (!$used{$row2[1]} && $row2[6] !~ /X/) {
foreach my $line3 (ONO::DB->select($db,"${community}_community_users","username = '$row2[1]'")) {
my @row3 = ONO::DB->readcols($line3);
if ($row3[1] =~ /^(.*?)\@(.*?)\.(.*?)$/) {
$found++;

@mail_recipients = (@mail_recipients,$row3[1]);

}
}
}
}
if ($found) {
$MAIL_MSG .= $txts[7];
}
}

if ($MAIL_MSG) {
$MAIL .= qq~<div class="mt5 bold green">$MAIL_MSG</div>~;
}

foreach my $mail_recipient (@mail_recipients) {

ONO::Lib::Mail::ToolBox->mail_queue_append($community,"2hour","exercise_completed",$row[1],$mail_recipient,"$EXERINFO\n$MAILDATA\n------\n");

}

}

# store statistics...

ONO::IO->mkpath("var/community/$community/statistics/school_exercises_completed");
ONO::IO->append("var/community/$community/statistics/school_exercises_completed.txt","x");
ONO::IO->append("var/community/$community/statistics/school_exercises_completed/$year$mon$mday.txt","x");

return ONO::FW::Apps::Core::Views->question_progress_done_msg(
$form,
$action_url,
$switches,
$EXERCISE,
$questions,
$USER,
$MAIL,
$HINTS,
$MISTAKES,
$TIME,
$APP,
$SHARE_REPORT,
$SHARE_ICON,
$SHARE_BUTTON,
$PRINT_CERT,
\@txts,
$BLK_ref,
\%vars,
);

}

}

###################################################################
# THAT'S IT :-D
###################################################################

1;

__END__