From f5968befc8c92e0c1e81d4ffee67a2aa22cbb3c8 Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Tue, 3 Jan 2023 14:57:37 -0500 Subject: [PATCH 01/13] add message listener for modifying css overrides --- lib/WebworkClient/jwe_secure_format.pl | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/WebworkClient/jwe_secure_format.pl b/lib/WebworkClient/jwe_secure_format.pl index c13914fb9..9beab1943 100644 --- a/lib/WebworkClient/jwe_secure_format.pl +++ b/lib/WebworkClient/jwe_secure_format.pl @@ -31,7 +31,7 @@ WeBWorK using host: $SITE_URL - +
@@ -56,6 +56,25 @@ console.log("response message ", JSON.parse('JWTanswerURLstatus')); window.parent.postMessage('JWTanswerURLstatus', '*'); } + + window.addEventListener('message', event => { + let message; + try { + message = JSON.parse(event.data); + } + catch (e) { + return; + } + if (message.hasOwnProperty('styles')) { + message.styles.forEach((incoming) => { + const elements = window.document.querySelectorAll(incoming.selector); + elements.forEach(el => el.style.cssText += incoming.style); + }); + } else { + return; + } + event.source.postMessage('css updated', event.origin); + }); From a52dfdc07766c99c1644aea2d1d3678263be9f3a Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Thu, 5 Jan 2023 17:34:13 -0500 Subject: [PATCH 02/13] add class manipulation and standardize behavior --- lib/WebworkClient/jwe_secure_format.pl | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/WebworkClient/jwe_secure_format.pl b/lib/WebworkClient/jwe_secure_format.pl index 9beab1943..7fb6d5a89 100644 --- a/lib/WebworkClient/jwe_secure_format.pl +++ b/lib/WebworkClient/jwe_secure_format.pl @@ -65,15 +65,22 @@ catch (e) { return; } + if (message.hasOwnProperty('styles')) { message.styles.forEach((incoming) => { const elements = window.document.querySelectorAll(incoming.selector); - elements.forEach(el => el.style.cssText += incoming.style); + elements.forEach(el => el.style.cssText = incoming.style); }); - } else { - return; + event.source.postMessage('css styles updated', event.origin); + } + + if (message.hasOwnProperty('classes')) { + message.classes.forEach((incoming) => { + const elements = window.document.querySelectorAll(incoming.selector); + elements.forEach(el => el.className = incoming.class); + }); + event.source.postMessage('css classes updated', event.origin); } - event.source.postMessage('css updated', event.origin); }); From 4cf54c1278d813d3f582a3330b18da26b74394da Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Fri, 6 Jan 2023 18:05:43 -0500 Subject: [PATCH 03/13] adjust for restructured incoming cssUpdate object cleanup --- lib/WebworkClient/jwe_secure_format.pl | 29 +++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/WebworkClient/jwe_secure_format.pl b/lib/WebworkClient/jwe_secure_format.pl index 7fb6d5a89..442064ad2 100644 --- a/lib/WebworkClient/jwe_secure_format.pl +++ b/lib/WebworkClient/jwe_secure_format.pl @@ -66,20 +66,29 @@ return; } - if (message.hasOwnProperty('styles')) { - message.styles.forEach((incoming) => { - const elements = window.document.querySelectorAll(incoming.selector); - elements.forEach(el => el.style.cssText = incoming.style); + if (message.hasOwnProperty('elements')) { + message.elements.forEach((incoming) => { + let elements; + if (incoming.hasOwnProperty('selector')) { + elements = window.document.querySelectorAll(incoming.selector); + if (incoming.hasOwnProperty('style')) { + elements.forEach(el => {el.style.cssText = incoming.style}); + } + if (incoming.hasOwnProperty('class')) { + elements.forEach(el => {el.className = incoming.class}); + } + } }); - event.source.postMessage('css styles updated', event.origin); + event.source.postMessage('updated elements', event.origin); } - if (message.hasOwnProperty('classes')) { - message.classes.forEach((incoming) => { - const elements = window.document.querySelectorAll(incoming.selector); - elements.forEach(el => el.className = incoming.class); + if (message.hasOwnProperty('templates')) { + message.templates.forEach((cssString) => { + const element = document.createElement('style'); + element.innerText = cssString; + document.head.insertAdjacentElement('beforeend', element); }); - event.source.postMessage('css classes updated', event.origin); + event.source.postMessage('updated templates', event.origin); } }); From 332b5548c8b5ec761e22a42da5eac1c03df8a0da Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Thu, 5 Jan 2023 19:37:49 -0500 Subject: [PATCH 04/13] new endpoints for copy and delete from private cleanup --- lib/RenderApp.pm | 2 + lib/RenderApp/Controller/IO.pm | 85 ++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/lib/RenderApp.pm b/lib/RenderApp.pm index 02e88b2cc..f63095074 100644 --- a/lib/RenderApp.pm +++ b/lib/RenderApp.pm @@ -96,6 +96,8 @@ sub startup { $r->any('/render-api/cat')->to('IO#catalog'); $r->any('/render-api/find')->to('IO#search'); $r->post('/render-api/upload')->to('IO#upload'); + $r->delete('/render-api/remove')->to('IO#remove'); + $r->post('/render-api/clone')->to('IO#clone'); $r->post('/render-api/sma')->to('IO#findNewVersion'); $r->post('/render-api/unique')->to('IO#findUniqueSeeds'); $r->post('/render-api/tags')->to('IO#setTags'); diff --git a/lib/RenderApp/Controller/IO.pm b/lib/RenderApp/Controller/IO.pm index e13bb039d..21d085aec 100644 --- a/lib/RenderApp/Controller/IO.pm +++ b/lib/RenderApp/Controller/IO.pm @@ -118,6 +118,91 @@ sub upload { return $c->render( text => 'File successfully uploaded', status => 200 ); } +sub remove { + my $c = shift; + my $required = []; + push @$required, + { + field => 'removeFilePath', + checkType => 'like', + check => $regex->{privateOnly}, + }; + my $validatedInput = $c->validateRequest( { required => $required } ); + return unless $validatedInput; + + my $file_path = $validatedInput->{removeFilePath}; + my $file = Mojo::File->new($file_path); + + return $c->render( text => 'Path does not exist', status => 404 ) + unless (-e $file); + + if (-d $file) { + return $c->render( text => 'Directory is not empty', status => 400 ) + unless ($file->list({ dir => 1 })->size == 0); + + $file->remove_tree; + } else { + $file->remove; + } + + return $c->render( text => 'Path deleted' ); +} + +sub clone { + my $c = shift; + my $required = []; + push @$required, + { + field => 'sourceFilePath', + checkType => 'like', + check => $regex->{privateOnly}, + }; + push @$required, + { + field => 'targetFilePath', + checkType => 'like', + check => $regex->{privateOnly}, + }; + my $validatedInput = $c->validateRequest( { required => $required } ); + return unless $validatedInput; + + my $source_path = $validatedInput->{sourceFilePath}; + my $source_file = Mojo::File->new($source_path); + my $target_path = $validatedInput->{targetFilePath}; + my $target_file = Mojo::File->new($target_path); + + return $c->render( text => 'source does not exist', status => 404 ) + unless (-e $source_file); + + return $c->render( text => 'target already exists', status => 400 ) + if (-e $target_file); + + # allow cloning of directories - problems with static assets + # no recursing through directories! + if (-d $source_file) { + return $c->render( text => 'source does not contain clone-able files', status => 400) + if ($source_file->list->size == 0); + + return $c->render( text => 'target must also be a directory', status => 400) + unless ($target_path =~ m!.*/$!); + + $target_file->make_path; + for ($source_file->list->each) { + $_->copy_to($target_path . $_->basename); + } + } else { + return $c->render( text => 'you may not create new directories with this method', status => 400) + unless (-e $target_file->dirname); + + return($c->render( text => 'file extensions do not match')) + unless ($source_file->extname eq $target_file->extname); + + $source_file->copy_to($target_file); + } + + return $c->render( text => 'clone successful' ); +} + async sub catalog { my $c = shift; my $required = []; From c2629b0e75f3e2c69ddab02f48371e39f3ff6689 Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Tue, 10 Jan 2023 17:42:47 -0500 Subject: [PATCH 05/13] catalogue all files, not just .pg --- lib/RenderApp/Controller/IO.pm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/RenderApp/Controller/IO.pm b/lib/RenderApp/Controller/IO.pm index 21d085aec..af0e4c2d2 100644 --- a/lib/RenderApp/Controller/IO.pm +++ b/lib/RenderApp/Controller/IO.pm @@ -254,13 +254,12 @@ sub depthSearch_p { my $wanted = sub { # measure depth relative to root_path ( my $rel = $File::Find::name ) =~ s!^\Q$root_path\E/?!!; + return unless $rel; my $path = $File::Find::name; $File::Find::prune = 1 if File::Spec::Functions::splitdir($rel) >= $depth; $path = $path . '/' if -d $File::Find::name; - # only report .pg files and directories - $all{$rel} = $path - if ( $rel =~ /\S/ && ( $path =~ m!.+/$! || $path =~ m!.+\.pg$! ) ); + $all{$rel} = $path; }; File::Find::find { wanted => $wanted, no_chdir => 1 }, $root_path; return \%all, 200; From af268841c419483506b7e636e5f19a72d087247b Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Thu, 9 Mar 2023 18:19:34 -0500 Subject: [PATCH 06/13] allow sessionJWT to restore AttemptsTable --- .../Controller/FormatRenderedProblem.pm | 47 ++++++++++--------- lib/WebworkClient/standard_format.pl | 2 +- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/lib/RenderApp/Controller/FormatRenderedProblem.pm b/lib/RenderApp/Controller/FormatRenderedProblem.pm index 777bc3e2e..a73928381 100755 --- a/lib/RenderApp/Controller/FormatRenderedProblem.pm +++ b/lib/RenderApp/Controller/FormatRenderedProblem.pm @@ -102,7 +102,7 @@ sub formatRenderedProblem { my $problemHeadText = $rh_result->{header_text}//''; ##head_text vs header_text my $problemPostHeaderText = $rh_result->{post_header_text}//''; my $rh_answers = $rh_result->{answers}//{}; - my $answerOrder = $rh_result->{flags}->{ANSWER_ENTRY_ORDER}; #[sort keys %{ $rh_result->{answers} }]; + my $answerOrder = $rh_result->{flags}->{ANSWER_ENTRY_ORDER}//[]; #[sort keys %{ $rh_result->{answers} }]; my $encoded_source = $self->encoded_source//''; my $sourceFilePath = $self->{sourceFilePath}//''; my $problemSourceURL = $self->{inputs_ref}->{problemSourceURL}; @@ -166,9 +166,9 @@ sub formatRenderedProblem { my $sessionJWT = $self->{return_object}{sessionJWT} // ''; my $previewMode = defined( $self->{inputs_ref}{previewAnswers} ) || 0; - my $checkMode = defined( $self->{inputs_ref}{checkAnswers} ) || 0; - my $submitMode = defined( $self->{inputs_ref}{submitAnswers} ) || 0; + # showCorrectMode needs more security -- ww2 uses want/can/will my $showCorrectMode = defined( $self->{inputs_ref}{showCorrectAnswers} ) || 0; + my $submitMode = defined($self->{inputs_ref}{submitAnswers}) || $self->{inputs_ref}{answersSubmitted} || 0; # problemUUID can be added to the request as a parameter. It adds a prefix # to the identifier used by the format so that several different problems @@ -180,6 +180,8 @@ sub formatRenderedProblem { // $rh_result->{flags}{showPartialCorrectAnswers}; my $showSummary = $self->{inputs_ref}{showSummary} // 1; #default to show summary for the moment my $formLanguage = $self->{inputs_ref}{language} // 'en'; + my $showTable = $self->{inputs_ref}{hideAttemptsTable} ? 0 : 1; + my $showMessages = $self->{inputs_ref}{hideMessages} ? 0 : 1; my $scoreSummary = ''; my $COURSE_LANG_AND_DIR = get_lang_and_dir($formLanguage); @@ -191,24 +193,27 @@ sub formatRenderedProblem { my $PROBLEM_LANG_AND_DIR = join(" ", map { qq{$_="$PROBLEM_LANG_AND_DIR{$_}"} } keys %PROBLEM_LANG_AND_DIR); my $mt = WeBWorK::Localize::getLangHandle($self->{inputs_ref}{language} // 'en'); - my $tbl = WeBWorK::Utils::AttemptsTable->new( - $rh_answers, - answersSubmitted => $self->{inputs_ref}{answersSubmitted}//0, - answerOrder => $answerOrder//[], - displayMode => $self->{inputs_ref}{displayMode}, - showAnswerNumbers => 0, - showAttemptAnswers => 0, - showAttemptPreviews => ($previewMode or $submitMode or $showCorrectMode), - showAttemptResults => ($submitMode and $showPartialCorrectAnswers), - showCorrectAnswers => ($showCorrectMode), - showMessages => ($previewMode or $submitMode or $showCorrectMode), - showSummary => ( ($showSummary and ($submitMode or $showCorrectMode) )//0 )?1:0, - maketext => WeBWorK::Localize::getLoc($formLanguage//'en'), - summary => $problemResult->{summary} //'', # can be set by problem grader??? - ); - - my $answerTemplate = $tbl->answerTemplate; - $tbl->imgGen->render(body_text => \$answerTemplate) if $tbl->displayMode eq 'images'; + my $answerTemplate = ''; + if ($submitMode && $showTable) { + my $tbl = WeBWorK::Utils::AttemptsTable->new( + $rh_answers, + answersSubmitted => 1, + answerOrder => $answerOrder, + displayMode => $displayMode, + showAnswerNumbers => 0, + showAttemptAnswers => 0, + showAttemptPreviews => 1, + showAttemptResults => $showPartialCorrectAnswers, + showCorrectAnswers => $showCorrectMode, + showMessages => $showMessages, + showSummary => $showSummary, + maketext => WeBWorK::Localize::getLoc($formLanguage), + summary => $problemResult->{summary} // '', # can be set by problem grader??? + ); + + $answerTemplate = $tbl->answerTemplate; + $tbl->imgGen->render(body_text => \$answerTemplate) if $tbl->displayMode eq 'images'; + } # warn "imgGen is ", $tbl->imgGen; #warn "answerOrder ", $tbl->answerOrder; diff --git a/lib/WebworkClient/standard_format.pl b/lib/WebworkClient/standard_format.pl index b1fdd507c..bb9f2212c 100644 --- a/lib/WebworkClient/standard_format.pl +++ b/lib/WebworkClient/standard_format.pl @@ -68,7 +68,7 @@ - +


From 1e954d83cc662cedca1c36b79b3d3117624d05c3 Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Sat, 8 Apr 2023 15:48:05 -0400 Subject: [PATCH 07/13] improve answerURL response handling escape quote after encoding json --- lib/RenderApp/Controller/Render.pm | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/RenderApp/Controller/Render.pm b/lib/RenderApp/Controller/Render.pm index 62de9e173..6e4966a16 100644 --- a/lib/RenderApp/Controller/Render.pm +++ b/lib/RenderApp/Controller/Render.pm @@ -154,24 +154,26 @@ async sub problem { my $response = shift->result; $answerJWTresponse->{status} = int($response->code); - if ($response->is_success) { + # answerURL responses are expected to be JSON + if ($response->json) { + # munge data with default response object + $answerJWTresponse = { %$answerJWTresponse, %{$response->json} }; + } else { + # otherwise throw the whole body as the message $answerJWTresponse->{message} = $response->body; } - elsif ($response->is_error) { - $answerJWTresponse->{message} = '[' . $c->logID . '] ' . $response->message; - } - - $answerJWTresponse->{message} =~ s/"/\\"/g; - $answerJWTresponse->{message} =~ s/'/\'/g; })-> catch(sub { - my $response = shift; - $c->log->error($response); + my $err = shift; + $c->log->error($err); $answerJWTresponse->{status} = 500; - $answerJWTresponse->{message} = '[' . $c->logID . '] ' . $response; + $answerJWTresponse->{message} = '[' . $c->logID . '] ' . $err; }); + $answerJWTresponse = encode_json($answerJWTresponse); + # this will become a string literal, so single-quote characters must be escaped + $answerJWTresponse =~ s/'/\\'/g; $c->log->info("answerJWT response ".$answerJWTresponse); $ww_return_hash->{renderedHTML} =~ s/JWTanswerURLstatus/$answerJWTresponse/g; From e7bb9990ee0eefc3a9287d1fc2bb8c8fb202df47 Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Tue, 9 May 2023 12:43:33 -0400 Subject: [PATCH 08/13] support postMessage "showSolutions" --- lib/WebworkClient/jwe_secure_format.pl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/WebworkClient/jwe_secure_format.pl b/lib/WebworkClient/jwe_secure_format.pl index 442064ad2..5511cbb4b 100644 --- a/lib/WebworkClient/jwe_secure_format.pl +++ b/lib/WebworkClient/jwe_secure_format.pl @@ -90,6 +90,12 @@ }); event.source.postMessage('updated templates', event.origin); } + + if (message.hasOwnProperty('showSolutions')) { + const elements = Array.from(window.document.querySelectorAll('.knowl[data-type="solution"]')); + const solutions = elements.map(el => el.dataset.knowlContents); + event.source.postMessage(JSON.stringify({solutions: solutions}), event.origin); + } }); From 635873783bc6cd9266f818d4f6381d33bcfc257a Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Fri, 19 May 2023 14:10:36 -0400 Subject: [PATCH 09/13] Static file support if baseURL is nonempty --- lib/RenderApp.pm | 28 ++++++++++++------------- lib/RenderApp/Controller/StaticFiles.pm | 4 ++++ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/RenderApp.pm b/lib/RenderApp.pm index f63095074..a8f64d73d 100644 --- a/lib/RenderApp.pm +++ b/lib/RenderApp.pm @@ -107,20 +107,20 @@ sub startup { $r->any('/pg_files/CAPA_Graphics/*static')->to('StaticFiles#CAPA_graphics_file'); $r->any('/pg_files/tmp/*static')->to('StaticFiles#temp_file'); $r->any('/pg_files/*static')->to('StaticFiles#pg_file'); - - # any other requests fall through - $r->any('/*fail' => sub { - my $c = shift; - my $report = $c->stash('fail')."\nCOOKIE:"; - for my $cookie (@{$c->req->cookies}) { - $report .= "\n".$cookie->to_string; - } - $report .= "\nFORM DATA:"; - foreach my $k (@{$c->req->params->names}) { - $report .= "\n$k = ".join ', ', @{$c->req->params->every_param($k)}; - } - $c->log->fatal($report); - $c->rendered(404)}); + $r->any('/*fail')->to('StaticFiles#public_file'); + # # any other requests fall through + # $r->any('/*fail' => sub { + # my $c = shift; + # my $report = $c->stash('fail')."\nCOOKIE:"; + # for my $cookie (@{$c->req->cookies}) { + # $report .= "\n".$cookie->to_string; + # } + # $report .= "\nFORM DATA:"; + # foreach my $k (@{$c->req->params->names}) { + # $report .= "\n$k = ".join ', ', @{$c->req->params->every_param($k)}; + # } + # $c->log->fatal($report); + # $c->rendered(404)}); } 1; diff --git a/lib/RenderApp/Controller/StaticFiles.pm b/lib/RenderApp/Controller/StaticFiles.pm index faf8bea20..6695d53f5 100644 --- a/lib/RenderApp/Controller/StaticFiles.pm +++ b/lib/RenderApp/Controller/StaticFiles.pm @@ -28,4 +28,8 @@ sub pg_file ($c) { $c->reply_with_file_if_readable(path($ENV{PG_ROOT}, 'htdocs', $c->stash('static'))); } +sub public_file($c) { + $c->reply_with_file_if_readable($c->app->home->child('public', $c->stash('fail'))); +} + 1; From accb165c66200b6859fec7c21d0e394e3bbb7aa6 Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Fri, 19 May 2023 14:16:30 -0400 Subject: [PATCH 10/13] Convert static URLs from relative to absolute --- lib/RenderApp/Controller/FormatRenderedProblem.pm | 4 ++-- templates/exception.html.ep | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/RenderApp/Controller/FormatRenderedProblem.pm b/lib/RenderApp/Controller/FormatRenderedProblem.pm index a73928381..8ca271591 100755 --- a/lib/RenderApp/Controller/FormatRenderedProblem.pm +++ b/lib/RenderApp/Controller/FormatRenderedProblem.pm @@ -255,7 +255,7 @@ sub formatRenderedProblem { } else { my $url = getAssetURL($self->{inputs_ref}{language} // 'en', $_->{file}); push @{ $rh_result->{js} }, $SITE_URL.$url; - $extra_js_files .= CGI::script({ src => $url, %attributes }, ''); + $extra_js_files .= CGI::script({ src => $SITE_URL.$url, %attributes }, ''); } } } @@ -278,7 +278,7 @@ sub formatRenderedProblem { } else { my $url = getAssetURL($self->{inputs_ref}{language} // 'en', $_->{file}); push @{ $rh_result->{css} }, $SITE_URL.$url; - $extra_css_files .= CGI::Link({ href => $url, rel => 'stylesheet' }); + $extra_css_files .= CGI::Link({ href => $SITE_URL.$url, rel => 'stylesheet' }); } } diff --git a/templates/exception.html.ep b/templates/exception.html.ep index 2310d32ed..4c93b0964 100644 --- a/templates/exception.html.ep +++ b/templates/exception.html.ep @@ -1,5 +1,5 @@ -%= stylesheet $ENV{SITE_HOST} . '/typing-sim.css' -%= stylesheet $ENV{SITE_HOST} . '/crt-display.css' +%= stylesheet $ENV{baseURL} . '/typing-sim.css' +%= stylesheet $ENV{baseURL} . '/crt-display.css' %= javascript begin window.onload = function() { var i = 0; From 3799b1149b86b9851d02bad826f2c2aa9350ba14 Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Fri, 19 May 2023 14:23:54 -0400 Subject: [PATCH 11/13] Convert all problem requests to JWT --- lib/RenderApp/Controller/Render.pm | 14 +++++++++++--- lib/RenderApp/Controller/RenderProblem.pm | 2 +- lib/WebworkClient/classic_format.pl | 9 +-------- lib/WebworkClient/nosubmit_format.pl | 7 +------ lib/WebworkClient/practice_format.pl | 8 +------- lib/WebworkClient/simple_format.pl | 9 +-------- lib/WebworkClient/single_format.pl | 9 +-------- lib/WebworkClient/standard_format.pl | 17 +---------------- lib/WebworkClient/static_format.pl | 9 +-------- lib/WebworkClient/ww3_format.pl | 8 +------- 10 files changed, 20 insertions(+), 72 deletions(-) diff --git a/lib/RenderApp/Controller/Render.pm b/lib/RenderApp/Controller/Render.pm index 6e4966a16..7b2b10b44 100644 --- a/lib/RenderApp/Controller/Render.pm +++ b/lib/RenderApp/Controller/Render.pm @@ -37,7 +37,6 @@ sub parseRequest { foreach my $key (keys %$claims) { $params{$key} //= $claims->{$key}; } - # @params{ keys %$claims } = values %$claims; } # problemJWT sets basic problem request configuration and rendering options @@ -60,6 +59,17 @@ sub parseRequest { # $claims->{problemJWT} = $problemJWT; # because we're merging claims, this is unnecessary? # override key-values in params with those provided in the JWT @params{ keys %$claims } = values %$claims; + } else { + # if no JWT is provided, create one + $params{aud} = $ENV{SITE_HOST}; + my $req_jwt = encode_jwt( + payload => \%params, + key => $ENV{problemJWTsecret}, + alg => 'PBES2-HS512+A256KW', + enc => 'A256GCM', + auto_iat => 1 + ); + $params{problemJWT} = $req_jwt; } return \%params; } @@ -280,7 +290,6 @@ sub jweFromRequest { my $inputs_ref = $c->parseRequest; return unless $inputs_ref; $inputs_ref->{aud} = $ENV{SITE_HOST}; - $inputs_ref->{key} = $ENV{problemJWTsecret}; my $req_jwt = encode_jwt( payload => $inputs_ref, key => $ENV{problemJWTsecret}, @@ -296,7 +305,6 @@ sub jwtFromRequest { my $inputs_ref = $c->parseRequest; return unless $inputs_ref; $inputs_ref->{aud} = $ENV{SITE_HOST}; - $inputs_ref->{key} = $ENV{problemJWTsecret}; my $req_jwt = encode_jwt( payload => $inputs_ref, key => $ENV{problemJWTsecret}, diff --git a/lib/RenderApp/Controller/RenderProblem.pm b/lib/RenderApp/Controller/RenderProblem.pm index 04dc4001e..13dc13145 100644 --- a/lib/RenderApp/Controller/RenderProblem.pm +++ b/lib/RenderApp/Controller/RenderProblem.pm @@ -427,7 +427,7 @@ sub get_current_process_memory { sub generateJWTs { my $pg = shift; my $inputs_ref = shift; - my $sessionHash = {'answersSubmitted' => 1, 'iss' =>$ENV{SITE_HOST}}; + my $sessionHash = {'answersSubmitted' => 1, 'iss' =>$ENV{SITE_HOST}, problemJWT => $inputs_ref->{problemJWT}}; my $scoreHash = {}; # if no problemJWT exists, then why bother? diff --git a/lib/WebworkClient/classic_format.pl b/lib/WebworkClient/classic_format.pl index fd8376ec4..1d800d5b9 100644 --- a/lib/WebworkClient/classic_format.pl +++ b/lib/WebworkClient/classic_format.pl @@ -42,14 +42,7 @@
$scoreSummary - - - - - - - - +

diff --git a/lib/WebworkClient/nosubmit_format.pl b/lib/WebworkClient/nosubmit_format.pl index 1dd80d03a..0a5606096 100644 --- a/lib/WebworkClient/nosubmit_format.pl +++ b/lib/WebworkClient/nosubmit_format.pl @@ -39,12 +39,7 @@

$scoreSummary - - - - - - +
diff --git a/lib/WebworkClient/practice_format.pl b/lib/WebworkClient/practice_format.pl index 303dc099b..b9fdc5f29 100644 --- a/lib/WebworkClient/practice_format.pl +++ b/lib/WebworkClient/practice_format.pl @@ -42,13 +42,7 @@ $scoreSummary - - - - - - - +

diff --git a/lib/WebworkClient/simple_format.pl b/lib/WebworkClient/simple_format.pl index 630490c3a..568df1b53 100644 --- a/lib/WebworkClient/simple_format.pl +++ b/lib/WebworkClient/simple_format.pl @@ -42,14 +42,7 @@ $scoreSummary - - - - - - - - +

diff --git a/lib/WebworkClient/single_format.pl b/lib/WebworkClient/single_format.pl index 3aac0e498..570947795 100644 --- a/lib/WebworkClient/single_format.pl +++ b/lib/WebworkClient/single_format.pl @@ -41,14 +41,7 @@ $scoreSummary - - - - - - - - +

diff --git a/lib/WebworkClient/standard_format.pl b/lib/WebworkClient/standard_format.pl index bb9f2212c..4c7eb5919 100644 --- a/lib/WebworkClient/standard_format.pl +++ b/lib/WebworkClient/standard_format.pl @@ -41,22 +41,7 @@ $scoreSummary - - - - - - - - - - - - - - - - +

Show:   diff --git a/lib/WebworkClient/static_format.pl b/lib/WebworkClient/static_format.pl index 443a05a5f..b6c28b4bb 100644 --- a/lib/WebworkClient/static_format.pl +++ b/lib/WebworkClient/static_format.pl @@ -39,14 +39,7 @@ $scoreSummary - - - - - - - - + diff --git a/lib/WebworkClient/ww3_format.pl b/lib/WebworkClient/ww3_format.pl index 7801b40c2..320676457 100644 --- a/lib/WebworkClient/ww3_format.pl +++ b/lib/WebworkClient/ww3_format.pl @@ -9,13 +9,7 @@ $problemText - - - - - - - + ENDPROBLEMTEMPLATE }; From 381c1ff61ee215897a9156861534bb15c0e0217f Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Mon, 22 May 2023 11:28:29 -0400 Subject: [PATCH 12/13] do not require provided sourcecode to be in base64 --- lib/RenderApp/Controller/Render.pm | 2 +- lib/RenderApp/Model/Problem.pm | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/RenderApp/Controller/Render.pm b/lib/RenderApp/Controller/Render.pm index 7b2b10b44..57be59d93 100644 --- a/lib/RenderApp/Controller/Render.pm +++ b/lib/RenderApp/Controller/Render.pm @@ -91,7 +91,7 @@ sub fetchRemoteSource_p { then( sub { my $tx = shift; - return encode_base64($tx->result->body); + return $tx->result->body; })-> catch( sub { diff --git a/lib/RenderApp/Model/Problem.pm b/lib/RenderApp/Model/Problem.pm index b435a29ac..8747231ec 100644 --- a/lib/RenderApp/Model/Problem.pm +++ b/lib/RenderApp/Model/Problem.pm @@ -8,6 +8,7 @@ use Mojo::IOLoop; use Mojo::JSON qw( encode_json ); use Mojo::Base -async_await; use Time::HiRes qw( time ); +use MIME::Base64 qw( decode_base64 ); use RenderApp::Controller::RenderProblem; ##### Problem params: ##### @@ -88,9 +89,12 @@ sub source { if ( scalar(@_) == 1 ) { my $contents = shift; + # recognize and decode base64 if necessary + $contents = Encode::decode( "UTF-8", decode_base64($contents) ) + if ( $contents =~ m!^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$!); + # UNIX style line-endings are required - $contents =~ s/\r\n/\n/g; - $contents =~ s/\r/\n/g; + $contents =~ s!\r\n?!\n!g; $self->{problem_contents} = $contents; } return $self->{problem_contents}; @@ -131,7 +135,8 @@ sub path { } $self->{_error} = "404 I cannot find a problem with that file path." unless ( -e $read_path || $force ); - $self->{read_path} = Mojo::File->new($read_path); + # if we objectify an empty string, it becomes truth-y -- AVOID! + $self->{read_path} = Mojo::File->new($read_path) if $read_path; } return $self->{read_path}; } From 3ac56a065f125becead6200505562923fa09c3ab Mon Sep 17 00:00:00 2001 From: "K. Andrew Parker" Date: Mon, 22 May 2023 11:37:51 -0400 Subject: [PATCH 13/13] refactor and cleanup --- lib/RenderApp/Controller/RenderProblem.pm | 259 ++++++---------------- 1 file changed, 66 insertions(+), 193 deletions(-) diff --git a/lib/RenderApp/Controller/RenderProblem.pm b/lib/RenderApp/Controller/RenderProblem.pm index 13dc13145..61fcaba72 100644 --- a/lib/RenderApp/Controller/RenderProblem.pm +++ b/lib/RenderApp/Controller/RenderProblem.pm @@ -26,8 +26,8 @@ use WeBWorK::Utils::Tags; use WeBWorK::Localize; use RenderApp::Controller::FormatRenderedProblem; -use 5.10.0; -$Carp::Verbose = 1; +# use 5.10.0; +# $Carp::Verbose = 1; ### verbose output when UNIT_TESTS_ON =1; our $UNIT_TESTS_ON = 0; @@ -38,7 +38,7 @@ our $UNIT_TESTS_ON = 0; # create log files :: expendable ################################################## -my $path_to_log_file = 'logs/standalone_results.log'; +my $path_to_log_file = "$ENV{RENDER_ROOT}/logs/standalone_results.log"; eval { # attempt to create log file local (*FH); @@ -76,35 +76,33 @@ sub UNIVERSAL::TO_JSON { sub process_pg_file { my $problem = shift; - my $inputHash = shift; - - my $file_path = $problem->path; - my $problem_seed = $problem->seed || '666'; + my $inputs_ref = shift; # just make sure we have the fundamentals covered... - $inputHash->{displayMode} //= 'MathJax'; - $inputHash->{sourceFilePath} ||= $file_path; - $inputHash->{outputFormat} ||= 'static'; - $inputHash->{language} ||= 'en'; + $inputs_ref->{displayMode} ||= 'MathJax'; + $inputs_ref->{outputFormat} ||= 'static'; + $inputs_ref->{language} ||= 'en'; # HACK: required for problemRandomize.pl - $inputHash->{effectiveUser} = 'red.ted'; - $inputHash->{user} = 'red.ted'; - - # OTHER fundamentals - urls have been handled already... - # form_action_url => $inputHash->{form_action_url}||'http://failure.org', - # base_url => $inputHash->{base_url}||'http://failure.org' - # #psvn => $psvn//'23456', # DEPRECATED - # #forcePortNumber => $credentials{forcePortNumber}//'', + $inputs_ref->{effectiveUser} = 'red.ted'; + $inputs_ref->{user} = 'red.ted'; - my $pg_start = - time; # this is Time::HiRes's time, which gives floating point values + my $pg_start = time; + my $memory_use_start = get_current_process_memory(); my ( $error_flag, $formatter, $error_string ) = - process_problem( $file_path, $inputHash ); + process_problem( $problem, $inputs_ref ); my $pg_stop = time; my $pg_duration = $pg_stop - $pg_start; + my $log_file_path = $problem->path() || 'source provided without path'; + my $memory_use_end = get_current_process_memory(); + my $memory_use = $memory_use_end - $memory_use_start; + writeRenderLogEntry( + sprintf( "(duration: %.3f sec) ", $pg_duration ) + . sprintf( "{memory: %6d bytes} ", $memory_use ) + . "file: $log_file_path" + ); # format result my $html = $formatter->formatRenderedProblem; @@ -122,15 +120,15 @@ sub process_pg_file { problem_state => $pg_obj->{problem_state}, flags => $pg_obj->{flags}, resources => { - regex => $pg_obj->{resources}, - tags => $pg_obj->{pgResources}, + regex => $pg_obj->{pgResources}, + alias => $pg_obj->{resources}, js => $pg_obj->{js}, css => $pg_obj->{css}, }, - form_data => $inputHash, + form_data => $inputs_ref, raw_metadata_text => $pg_obj->{raw_metadata_text}, JWT => { - problem => $inputHash->{problemJWT}, + problem => $inputs_ref->{problemJWT}, session => $pg_obj->{sessionJWT}, answer => $pg_obj->{answerJWT} }, @@ -145,7 +143,7 @@ sub process_pg_file { if $json_rh->{flags}{compoundProblem}{grader}; - $json_rh->{tags} = WeBWorK::Utils::Tags->new($file_path, $inputHash->{problemSource}) if ( $inputHash->{includeTags} ); + $json_rh->{tags} = WeBWorK::Utils::Tags->new('', $problem->source) if ( $inputs_ref->{includeTags} ); my $coder = JSON::XS->new->ascii->pretty->allow_unknown->convert_blessed; my $json = $coder->encode($json_rh); return $json; @@ -156,101 +154,36 @@ sub process_pg_file { ####################################################################### sub process_problem { - my $file_path = shift; + my $problem = shift; my $inputs_ref = shift; - my $adj_file_path; - my $source; - - # obsolete if using JSON return format - # These can FORCE display of AnsGroup AnsHash PGInfo and ResourceInfo - # $inputs_ref->{showAnsGroupInfo} = 1; #$print_answer_group; - # $inputs_ref->{showAnsHashInfo} = 1; #$print_answer_hash; - # $inputs_ref->{showPGInfo} = 1; #$print_pg_hash; - # $inputs_ref->{showResourceInfo} = 1; #$print_resource_hash; - - ### stash inputs that get wiped by PG - my $problem_seed = $inputs_ref->{problemSeed}; - die "problem seed not defined in Controller::RenderProblem::process_problem" - unless $problem_seed; - - # if base64 source is provided, use that over fetching problem path - if ( $inputs_ref->{problemSource} && $inputs_ref->{problemSource} =~ m/\S/ ) - { - # such hackery - but Mojo::Promises are so well-built that they are invisible - # ... until you leave the Mojo space - $inputs_ref->{problemSource} = $inputs_ref->{problemSource}{results}[0] if $inputs_ref->{problemSource} =~ /Mojo::Promise/; - # sanitize the base64 encoded source - $inputs_ref->{problemSource} =~ s/\s//gm; - # while ($source =~ /([^A-Za-z0-9+])/gm) { - # warn "invalid character found: ".sprintf( "\\u%04x", ord($1) )."\n"; - # } - $source = Encode::decode("UTF-8", decode_base64( $inputs_ref->{problemSource} ) ); - } - else { - ( $adj_file_path, $source ) = get_source($file_path); + my $source = $problem->{problem_contents}; + my $file_path = $inputs_ref->{sourceFilePath}; - # WHY are there so many fields in which to stash the file path? - #$inputs_ref->{fileName} = $adj_file_path; - #$inputs_ref->{probFileName} = $adj_file_path; - #$inputs_ref->{sourceFilePath} = $adj_file_path; - #$inputs_ref->{pathToProblemFile} = $adj_file_path; - } - my $raw_metadata_text = $1 if ($source =~ /(.*?)DOCUMENT\(\s*\)\s*;/s); $inputs_ref->{problemUUID} = md5_hex(Encode::encode_utf8($source)); - # TODO verify line ending are LF instead of CRLF - - # included (external) pg content is not recorded by PGalias + # external dependencies on pg content is not recorded by PGalias # record the dependency separately -- TODO: incorporate into PG.pl or PGcore? - my $pgResources = []; + my @pgResources; while ($source =~ m/includePG(?:problem|file)\(["'](.*)["']\);/g ) { warn "PG asset reference found: $1\n" if $UNIT_TESTS_ON; - push @$pgResources, $1; + push @pgResources, $1; } - # # this does not capture _all_ image asset references, unfortunately... - # # asset filenames may be stored as variables before image() is called - # while ($source =~ m/image\(\s*("[^\$]+?"|'[^\$]+?')\s*[,\)]/g) { - # warn "Image asset reference found!\n" . $1 . "\n" if $UNIT_TESTS_ON; - # my $image = $1; - # $image =~ s/['"]//g; - # $image = dirname($file_path) . '/' . $image if ($image =~ /^[^\/]*\.(?:gif|jpg|jpeg|png)$/i); - # warn "Recording image asset as: $image\n" if $UNIT_TESTS_ON; - # push @$assets, $image; - # } - - # $inputs_ref->{pathToProblemFile} = $adj_file_path - # if ( defined $adj_file_path ); - ################################################## # Process the pg file ################################################## - ### store the time before we invoke the content generator - my $cg_start = - time; # this is Time::HiRes's time, which gives floating point values - - ############################################ - # Call server via standaloneRenderer to render problem - ############################################ - our ( $return_object, $error_flag, $error_string ); $error_flag = 0; $error_string = ''; - my $memory_use_start = get_current_process_memory(); - # can include @args as third input below $return_object = standaloneRenderer( \$source, $inputs_ref ); # stash assets list in $return_object - $return_object->{pgResources} = $pgResources; - - # stash raw metadata text in $return_object - $return_object->{raw_metadata_text} = $raw_metadata_text; + $return_object->{pgResources} = \@pgResources; # generate sessionJWT to store session data and answerJWT to update grade store - # only occurs if problemJWT exists! my ($sessionJWT, $answerJWT) = generateJWTs($return_object, $inputs_ref); $return_object->{sessionJWT} = $sessionJWT // ''; $return_object->{answerJWT} = $answerJWT // ''; @@ -262,7 +195,7 @@ sub process_problem { print "\n\n Result of renderProblem \n\n" if $UNIT_TESTS_ON; print pretty_print_rh($return_object) if $UNIT_TESTS_ON; if ( not defined $return_object ) - { #FIXME make sure this is the right error message if site is unavailable + { $error_string = "0\t Could not process $file_path problem file \n"; } elsif ( defined( $return_object->{flags}->{error_flag} ) @@ -279,36 +212,14 @@ sub process_problem { # Create FormatRenderedProblems object ################################################## - # my $encoded_source = encode_base64($source); # create encoding of source_file; my $formatter = RenderApp::Controller::FormatRenderedProblem->new( return_object => $return_object, - encoded_source => '', #encode_base64($source), - sourceFilePath => $file_path, + sourceFilePath => $inputs_ref->{sourceFilePath}, url => $inputs_ref->{baseURL}, form_action_url => $inputs_ref->{formURL}, maketext => sub {return @_}, - courseID => 'blackbox', - userID => 'Motoko_Kusanagi', - course_password => 'daemon', inputs_ref => $inputs_ref, - problem_seed => $problem_seed - ); - - ################################################## - # log elapsed time - ################################################## - my $scriptName = 'standalonePGproblemRenderer'; - my $log_file_path = $file_path // 'source provided without path'; - my $cg_end = time; - my $cg_duration = $cg_end - $cg_start; - my $memory_use_end = get_current_process_memory(); - my $memory_use = $memory_use_end - $memory_use_start; - writeRenderLogEntry( - "", - "{script:$scriptName; file:$log_file_path; " - . sprintf( "duration: %.3f sec;", $cg_duration ) - . sprintf( " memory: %6d bytes;", $memory_use ) . "}", - '' + problem_seed => $inputs_ref->{problemSeed}, ); ####################################################################### @@ -331,21 +242,12 @@ sub standaloneRenderer { my $processAnswers = $inputs_ref->{processAnswers} // 1; print "NOT PROCESSING ANSWERS" unless $processAnswers == 1; - unless (ref $problemFile) { - # In this case the source file name is passed - print "standaloneProblemRenderer: setting source_file = $problemFile"; - } - # Attempt to match old parameters. my $isInstructor = $inputs_ref->{isInstructor} // ($inputs_ref->{permissionLevel} // 0) >= 10; my $pg = WeBWorK::PG->new( - ref $problemFile - ? ( - sourceFilePath => $inputs_ref->{sourceFilePath} // '', - r_source => $problemFile, - ) - : (sourceFilePath => $problemFile), + sourceFilePath => $inputs_ref->{sourceFilePath} // '', + r_source => $problemFile, problemSeed => $inputs_ref->{problemSeed}, processAnswers => $processAnswers, showHints => $inputs_ref->{showHints}, # default is to showHint (set in PG.pm) @@ -377,8 +279,8 @@ sub standaloneRenderer { my ( $internal_debug_messages, $pgwarning_messages, $pgdebug_messages ); if ( ref( $pg->{pgcore} ) ) { $internal_debug_messages = $pg->{pgcore}->get_internal_debug_messages; - $pgwarning_messages = $pg->{pgcore}->get_warning_messages(); - $pgdebug_messages = $pg->{pgcore}->get_debug_messages(); + $pgwarning_messages = $pg->{pgcore}->get_warning_messages; + $pgdebug_messages = $pg->{pgcore}->get_debug_messages; } else { $internal_debug_messages = @@ -405,19 +307,12 @@ sub standaloneRenderer { $out2; } -sub display_html_output { #display the problem in a browser - my $file_path = shift; - my $formatter = shift; - my $output_text = $formatter->formatRenderedProblem; - return $output_text; -} - ################################################## # utilities ################################################## sub get_current_process_memory { - state $pt = Proc::ProcessTable->new; + CORE::state $pt = Proc::ProcessTable->new; my %info = map { $_->pid => $_ } @{ $pt->table }; return $info{$$}->rss; } @@ -429,9 +324,18 @@ sub generateJWTs { my $inputs_ref = shift; my $sessionHash = {'answersSubmitted' => 1, 'iss' =>$ENV{SITE_HOST}, problemJWT => $inputs_ref->{problemJWT}}; my $scoreHash = {}; - - # if no problemJWT exists, then why bother? - return unless $inputs_ref->{problemJWT}; + + # TODO: sometimes student_ans causes JWT corruption in PHP - why? + # proposed restructuring of the answerJWT -- prepare with LibreTexts + # my %studentKeys = qw(student_value value student_formula formula student_ans answer original_student_ans original); + # my %previewKeys = qw(preview_text_string text preview_latex_string latex); + # my %correctKeys = qw(correct_value value correct_formula formula correct_ans ans); + # my %messageKeys = qw(ans_message answer error_message error); + # my @resultKeys = qw(score weight); + my %answers = %{unbless($pg->{answers})}; + + # once the correct answers are shown, this setting is permanent + $sessionHash->{showCorrectAnswers} = 1 if $inputs_ref->{showCorrectAnswers}; # store the current answer/response state for each entry foreach my $ans (keys %{$pg->{answers}}) { @@ -440,19 +344,20 @@ sub generateJWTs { $sessionHash->{ 'previous_' . $ans } = $inputs_ref->{$ans}; $sessionHash->{ 'MaThQuIlL_' . $ans } = $inputs_ref->{ 'MaThQuIlL_' . $ans } if ($inputs_ref->{ 'MaThQuIlL_' . $ans}); - # $scoreHash->{ans_id} = $ans; - # $scoreHash->{answer} = unbless($pg->{answers}{$ans}) // {}, - # $scoreHash->{score} = $pg->{answers}{$ans}{score} // 0, - - # TODO see why this key is causing JWT corruption in PHP - delete( $pg->{answers}{$ans}{student_ans}); + # More restructuring -- confirm with LibreTexts + # $scoreHash->{$ans}{student} = { map {exists $answers{$ans}{$_} ? ($studentKeys{$_} => $answers{$ans}{$_}) : ()} keys %studentKeys }; + # $scoreHash->{$ans}{preview} = { map {exists $answers{$ans}{$_} ? ($previewKeys{$_} => $answers{$ans}{$_}) : ()} keys %previewKeys }; + # $scoreHash->{$ans}{correct} = { map {exists $answers{$ans}{$_} ? ($correctKeys{$_} => $answers{$ans}{$_}) : ()} keys %correctKeys }; + # $scoreHash->{$ans}{message} = { map {exists $answers{$ans}{$_} ? ($messageKeys{$_} => $answers{$ans}{$_}) : ()} keys %messageKeys }; + # $scoreHash->{$ans}{result} = { map {exists $answers{$ans}{$_} ? ($_ => $answers{$ans}{$_}) : ()} @resultKeys }; } $scoreHash->{answers} = unbless($pg->{answers}); # update the number of correct/incorrect submissions if answers were 'submitted' - $sessionHash->{numCorrect} = (defined $inputs_ref->{submitAnswers}) ? + # but don't update either if the problem was already correct + $sessionHash->{numCorrect} = (defined $inputs_ref->{submitAnswers} && $inputs_ref->{numCorrect} == 0) ? $pg->{problem_state}{num_of_correct_ans} : ($inputs_ref->{numCorrect} // 0); - $sessionHash->{numIncorrect} = (defined $inputs_ref->{submitAnswers}) ? + $sessionHash->{numIncorrect} = (defined $inputs_ref->{submitAnswers} && $inputs_ref->{numCorrect} == 0) ? $pg->{problem_state}{num_of_incorrect_ans} : ($inputs_ref->{numIncorrect} // 0); # include the final result of the combined scores @@ -474,40 +379,11 @@ sub generateJWTs { # Can instead use alg => 'PBES2-HS512+A256KW', enc => 'A256GCM' for JWE my $answerJWT = encode_jwt(payload=>$responseHash, alg => 'HS256', key => $ENV{problemJWTsecret}, auto_iat => 1); + warn("answerJWT claims: ".encode_json($scoreHash)); return ($sessionJWT, $answerJWT); } -# Get problem template source and adjust file_path name -sub get_source { - my $file_path = shift; - my $source; - die "Unable to read file $file_path \n" - unless $file_path eq '-' or -r $file_path; - eval { #File::Slurp would be faster (see perl monks) - local $/ = undef; - if ( $file_path eq '-' ) { - $source = ; - } else { - # To support proper behavior with UTF-8 files, we need to open them with "<:encoding(UTF-8)" - # as otherwise, the first HTML file will render properly, but when "Preview" "Submit answer" - # or "Show correct answer" is used it will make problems, as in process_problem() the - # encodeSource() method is called on a data which is still UTF-8 encoded, and leads to double - # encoding and gibberish. - # NEW: - open( FH, "<:encoding(UTF-8)", $file_path ) - or die "Couldn't open file $file_path: $!"; - - # OLD: - #open(FH, "<" ,$file_path) or die "Couldn't open file $file_path: $!"; - $source = ; #slurp input - close FH; - } - }; - die "Something is wrong with the contents of $file_path\n" if $@; - return $file_path, $source; -} - sub pretty_print_rh { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $rh = shift; @@ -557,18 +433,15 @@ sub pretty_print_rh { return $out . " "; } -sub writeRenderLogEntry($$$) { - my ( $function, $details, $beginEnd ) = @_; - $beginEnd = - ( $beginEnd eq "begin" ) ? ">" : ( $beginEnd eq "end" ) ? "<" : "-"; +sub writeRenderLogEntry($) { + my $message = shift; local *LOG; if ( open LOG, ">>", $path_to_log_file ) { print LOG "[", time2str( "%a %b %d %H:%M:%S %Y", time ), - "] $$ " . time . " $beginEnd $function [$details]\n"; + "] $message\n"; close LOG; - } - else { + } else { warn "failed to open $path_to_log_file for writing: $!"; } }