diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..ee82116646 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +max_line_length = 120 +trim_trailing_whitespace = true +indent_style = tab +indent_size = 4 + +[*.yml] +indent_style = space +indent_size = 2 + +[*.pg] +trim_trailing_whitespace = false diff --git a/.github/workflows/check-formats.yml b/.github/workflows/check-formats.yml new file mode 100644 index 0000000000..a77b12446c --- /dev/null +++ b/.github/workflows/check-formats.yml @@ -0,0 +1,50 @@ +--- +name: Check Formatting of Code Base + +# UBC adaptations from the upstream check-formats.yml (kept otherwise verbatim so +# it stays in sync with upstream): declare least-privilege permissions to satisfy +# CodeQL, and add UBC's default branch `ubc` to the push branches-ignore list. +permissions: + contents: read + +defaults: + run: + shell: bash + +on: + push: + branches-ignore: [main, develop, ubc] + pull_request: + +jobs: + perltidy: + name: Check Perl file formatting with perltidy + runs-on: ubuntu-24.04 + container: + image: perl:5.38 + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install dependencies + run: cpanm -n Perl::Tidy@20240903 + - name: Run perltidy + shell: bash + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + shopt -s extglob globstar nullglob + perltidy --pro=./.perltidyrc -b -bext='/' ./**/*.p[lm] ./**/*.t && git diff --exit-code + + prettier: + name: Check JavaScript, style, and HTML file formatting with prettier + runs-on: ubuntu-24.04 + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install Dependencies + run: cd htdocs && npm ci --ignore-scripts + - name: Check formatting with prettier + run: cd htdocs && npm run prettier-check diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml deleted file mode 100644 index c26ffee98c..0000000000 --- a/.github/workflows/linter.yml +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Lint Code Base - -defaults: - run: - shell: bash - -on: - push: - branches-ignore: [main, develop, ubc] - pull_request: - -jobs: - perltidy: - name: Run perltidy on Perl Files - runs-on: ubuntu-22.04 - container: - image: perl:5.34 - steps: - - uses: actions/checkout@v3 - - name: perl -V - run: perl -V - - name: Install dependencies - run: cpanm -n Perl::Tidy@20220613 - - name: perltidy --version - run: perltidy --version - - name: Run perltidy - shell: bash - run: | - git config --global --add safe.directory "$GITHUB_WORKSPACE" - shopt -s extglob globstar nullglob - perltidy --pro=./.perltidyrc -b -bext='/' ./**/*.p[lm] ./**/*.t && git diff --exit-code diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8fae44b2df..4027646e37 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,5 +1,8 @@ name: CI to Docker Hub +permissions: + contents: read + # Controls when the workflow will run on: # Triggers the workflow on push or pull request events but only for the master branch diff --git a/.perltidyrc b/.perltidyrc index 4620751c6f..8314b7c173 100644 --- a/.perltidyrc +++ b/.perltidyrc @@ -20,4 +20,3 @@ -nlop # No logical padding (this causes mixed tabs and spaces) -wn # Weld nested containers -xci # Extended continuation indentation --vxl='q' # No vertical alignment of qw quotes diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..b21dab0657 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "printWidth": 120, + "semi": true, + "singleQuote": true, + "trailingComma": "none" +} diff --git a/Dockerfile b/Dockerfile index 349bb61816..7c808ab3f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,10 +30,10 @@ RUN echo Cloning branch $PG_BRANCH branch from $PG_GIT_URL \ # We need to change FROM before setting the ENV variables. -FROM ubuntu:22.04 +FROM ubuntu:24.04 ENV WEBWORK_URL=/webwork2 \ - WEBWORK_ROOT_URL=http://localhost::8080 \ + WEBWORK_ROOT_URL=http://localhost:8080 \ WEBWORK_SMTP_SERVER=localhost \ WEBWORK_SMTP_SENDER=webwork@example.com \ WEBWORK_TIMEZONE=America/New_York \ @@ -71,6 +71,7 @@ RUN apt-get update \ imagemagick \ iputils-ping \ jq \ + libarchive-extract-perl \ libarchive-zip-perl \ libarray-utils-perl \ libc6-dev \ @@ -94,23 +95,21 @@ RUN apt-get update \ libextutils-helpers-perl \ libextutils-installpaths-perl \ libextutils-xsbuilder-perl \ + libfile-copy-recursive-perl \ libfile-find-rule-perl-perl \ libfile-sharedir-install-perl \ libfuture-asyncawait-perl \ + libgd-barcode-perl \ libgd-perl \ libhtml-scrubber-perl \ libhtml-template-perl \ libhttp-async-perl \ libiterator-perl \ libiterator-util-perl \ - libjson-maybexs-perl \ - libjson-perl \ - libjson-xs-perl \ liblocale-maketext-lexicon-perl \ - libmail-sender-perl \ - libmail-sender-perl \ libmariadb-dev \ libmath-random-secure-perl \ + libmime-base32-perl \ libmime-tools-perl \ libminion-backend-sqlite-perl \ libminion-perl \ @@ -124,13 +123,12 @@ RUN apt-get update \ libnet-oauth-perl \ libossp-uuid-perl \ libpadwalker-perl \ + libpandoc-wrapper-perl \ libpath-class-perl \ libpath-tiny-perl \ - libpandoc-wrapper-perl \ libphp-serialization-perl \ libpod-wsdl-perl \ libsoap-lite-perl \ - libsql-abstract-classic-perl \ libsql-abstract-perl \ libstring-shellquote-perl \ libsub-uplevel-perl \ @@ -176,7 +174,7 @@ RUN apt-get update \ texlive-xetex \ tzdata \ zip $ADDITIONAL_BASE_IMAGE_PACKAGES \ - && curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \ + && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y --no-install-recommends --no-install-suggests nodejs \ && apt-get clean \ && rm -fr /var/lib/apt/lists/* /tmp/* @@ -184,7 +182,12 @@ RUN apt-get update \ # ================================================================== # Phase 4 - Install additional Perl modules from CPAN that are not packaged for Ubuntu or are outdated in Ubuntu. -RUN cpanm install Statistics::R::IO DBD::MariaDB Mojo::SQLite@3.002 Perl::Tidy@20220613 \ +RUN cpanm install -n \ + Statistics::R::IO \ + DBD::MariaDB \ + Perl::Tidy@20240903 \ + Archive::Zip::SimpleZip \ + Net::SAML2 \ && rm -fr ./cpanm /root/.cpanm /tmp/* # ================================================================== @@ -211,7 +214,7 @@ COPY --from=base /opt/base/pg $APP_ROOT/pg # 7. Apply patches # Patch files that are applied below -COPY docker-config/imagemagick-allow-pdf-read.patch /tmp +COPY docker-config/pgfsys-dvisvmg-bbox-fix.patch /tmp RUN echo "PATH=$PATH:$APP_ROOT/webwork2/bin" >> /root/.bashrc \ && mkdir /run/webwork2 /etc/ssl/local \ @@ -228,8 +231,8 @@ RUN echo "PATH=$PATH:$APP_ROOT/webwork2/bin" >> /root/.bashrc \ && npm install \ && cd $PG_ROOT/htdocs \ && npm install \ - && patch -p1 -d / < /tmp/imagemagick-allow-pdf-read.patch \ - && rm /tmp/imagemagick-allow-pdf-read.patch + && patch -p1 -d / < /tmp/pgfsys-dvisvmg-bbox-fix.patch \ + && rm /tmp/pgfsys-dvisvmg-bbox-fix.patch # ================================================================== # Phase 7 - Final setup and prepare docker-entrypoint.sh diff --git a/Dockerfile-prod b/Dockerfile-prod index 335872f5b5..c1777e99c4 100644 --- a/Dockerfile-prod +++ b/Dockerfile-prod @@ -8,7 +8,10 @@ FROM alpine/git AS base ARG WEBWORK2_GIT_URL ARG WEBWORK2_BRANCH ARG PG_GIT_URL=https://github.com/openwebwork/pg.git -ARG PG_BRANCH=PG-2.18+ +# PG releases in lockstep with webwork2; 2.20 needs PG-2.20 (PG 2.18 lacks +# getSampleProblemCode, the reorganized Plots::* modules, and WeBWorK::PG::Localize, +# which breaks the PG Problem Editor and plot rendering). +ARG PG_BRANCH=PG-2.20 # CUSTOM: allow customized OPL repo & branch ARG OPL_GIT_URL=https://github.com/ubc/webwork-open-problem-library.git ARG OPL_BRANCH=ubc @@ -30,7 +33,7 @@ RUN echo Cloning branch $OPL_BRANCH branch from $OPL_GIT_URL \ ############################### CUSTOM ############################# # Webwork Node Stuff, separated here for caching purposes -FROM node:20 AS npmww +FROM node:26 AS npmww WORKDIR /npmww @@ -50,7 +53,7 @@ RUN npm --maxsockets 2 ci ############################### CUSTOM ############################# # PG Node Stuff, separated here for caching purposes -FROM node:20 AS npmpg +FROM node:26 AS npmpg WORKDIR /npmpg COPY --from=base /opt/base/pg/htdocs/package.json . @@ -65,7 +68,7 @@ RUN npm --maxsockets 2 ci # We need to change FROM before setting the ENV variables. -FROM ubuntu:22.04 +FROM ubuntu:24.04 ENV WEBWORK_URL=/webwork2 \ WEBWORK_ROOT_URL=http://localhost::8080 \ @@ -116,6 +119,7 @@ RUN apt-get update \ imagemagick \ iputils-ping \ jq \ + libarchive-extract-perl \ libarchive-zip-perl \ libarray-utils-perl \ libc6-dev \ @@ -130,7 +134,6 @@ RUN apt-get update \ libdatetime-perl \ libdbd-mysql-perl \ libdevel-checklib-perl \ - #libdbd-mariadb-perl \ libemail-address-xs-perl \ libemail-date-format-perl \ libemail-sender-perl \ @@ -140,23 +143,21 @@ RUN apt-get update \ libextutils-helpers-perl \ libextutils-installpaths-perl \ libextutils-xsbuilder-perl \ + libfile-copy-recursive-perl \ libfile-find-rule-perl-perl \ libfile-sharedir-install-perl \ libfuture-asyncawait-perl \ + libgd-barcode-perl \ libgd-perl \ libhtml-scrubber-perl \ libhtml-template-perl \ libhttp-async-perl \ libiterator-perl \ libiterator-util-perl \ - libjson-maybexs-perl \ - libjson-perl \ - libjson-xs-perl \ liblocale-maketext-lexicon-perl \ - libmail-sender-perl \ - libmail-sender-perl \ libmariadb-dev \ libmath-random-secure-perl \ + libmime-base32-perl \ libmime-tools-perl \ #libminion-backend-sqlite-perl \ # we're using mysql instead libminion-perl \ @@ -170,13 +171,12 @@ RUN apt-get update \ libnet-oauth-perl \ libossp-uuid-perl \ libpadwalker-perl \ + libpandoc-wrapper-perl \ libpath-class-perl \ libpath-tiny-perl \ - libpandoc-wrapper-perl \ libphp-serialization-perl \ libpod-wsdl-perl \ libsoap-lite-perl \ - libsql-abstract-classic-perl \ libsql-abstract-perl \ libstring-shellquote-perl \ libsub-uplevel-perl \ @@ -230,23 +230,27 @@ RUN apt-get update \ texlive-xetex \ tzdata \ zip $ADDITIONAL_BASE_IMAGE_PACKAGES \ - && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && curl -fsSL https://deb.nodesource.com/setup_26.x | bash - \ && apt-get install -y --no-install-recommends --no-install-suggests nodejs \ vim # ================================================================== # Phase 4 - Install additional Perl modules from CPAN that are not packaged for Ubuntu or are outdated in Ubuntu. -RUN cpanm install Statistics::R::IO DBD::MariaDB Mojo::SQLite Perl::Tidy@20220613 \ +RUN cpanm install -n \ + Statistics::R::IO \ + DBD::MariaDB \ + Perl::Tidy@20240903 \ + Archive::Zip::SimpleZip \ + Net::SAML2 \ && rm -fr ./cpanm /root/.cpanm /tmp/* # App::Genpass - used by LTI1p3's CourseUpdater # Data::ObjectDriver - used by DelayedJob # Bytes::Random::Secure::Tiny - used by LTI1p3 for secure random string # Minion::Backend::mysql - let us reuse webwork db for Minion job queue database -# Net::SAML2 - for Shibboleth auth RUN cpanm install App::Genpass Data::ObjectDriver Bytes::Random::Secure::Tiny \ - Minion::Backend::mysql Net::SAML2 && rm -fr ./cpanm /root/.cpanm /tmp/* + Minion::Backend::mysql && rm -fr ./cpanm /root/.cpanm /tmp/* # ================================================================== # Phase 5 - Install webwork2 and pg which were downloaded to /opt/base/ in phase 1 @@ -275,7 +279,7 @@ COPY --from=base /opt/base/webwork-open-problem-library $APP_ROOT/libraries/webw # 7. Apply patches # Patch files that are applied below -COPY docker-config/imagemagick-allow-pdf-read.patch /tmp +COPY docker-config/pgfsys-dvisvmg-bbox-fix.patch /tmp RUN echo "PATH=$PATH:$APP_ROOT/webwork2/bin" >> /root/.bashrc \ && mkdir /run/webwork2 /etc/ssl/local \ @@ -288,8 +292,8 @@ RUN echo "PATH=$PATH:$APP_ROOT/webwork2/bin" >> /root/.bashrc \ && debconf-set-selections /tmp/preseed.txt \ && rm /etc/localtime /etc/timezone && echo "Etc/UTC" > /etc/timezone \ && dpkg-reconfigure -f noninteractive tzdata \ - && patch -p1 -d / < /tmp/imagemagick-allow-pdf-read.patch \ - && rm /tmp/imagemagick-allow-pdf-read.patch + && patch -p1 -d / < /tmp/pgfsys-dvisvmg-bbox-fix.patch \ + && rm /tmp/pgfsys-dvisvmg-bbox-fix.patch # ================================================================== # Phase 7 - Final setup and prepare docker-entrypoint.sh diff --git a/DockerfileStage1 b/DockerfileStage1 index d468ec0785..4c946e017f 100644 --- a/DockerfileStage1 +++ b/DockerfileStage1 @@ -1,7 +1,7 @@ # This is the Stage 1 Dockerfile, which builds a base OS image (webwork-base) # on top of which the WeBWorK parts will be installed by the Stage 2 Dockerfile. -FROM ubuntu:22.04 +FROM ubuntu:24.04 # ================================================================== # Phase 1 - Set base OS image install stage ENV variables @@ -33,6 +33,7 @@ RUN apt-get update \ imagemagick \ iputils-ping \ jq \ + libarchive-extract-perl \ libarchive-zip-perl \ libarray-utils-perl \ libc6-dev \ @@ -56,23 +57,21 @@ RUN apt-get update \ libextutils-helpers-perl \ libextutils-installpaths-perl \ libextutils-xsbuilder-perl \ + libfile-copy-recursive-perl \ libfile-find-rule-perl-perl \ libfile-sharedir-install-perl \ libfuture-asyncawait-perl \ + libgd-barcode-perl \ libgd-perl \ libhtml-scrubber-perl \ libhtml-template-perl \ libhttp-async-perl \ libiterator-perl \ libiterator-util-perl \ - libjson-maybexs-perl \ - libjson-perl \ - libjson-xs-perl \ liblocale-maketext-lexicon-perl \ - libmail-sender-perl \ - libmail-sender-perl \ libmariadb-dev \ libmath-random-secure-perl \ + libmime-base32-perl \ libmime-tools-perl \ libminion-backend-sqlite-perl \ libminion-perl \ @@ -92,7 +91,6 @@ RUN apt-get update \ libphp-serialization-perl \ libpod-wsdl-perl \ libsoap-lite-perl \ - libsql-abstract-classic-perl \ libsql-abstract-perl \ libstring-shellquote-perl \ libsub-uplevel-perl \ @@ -138,7 +136,7 @@ RUN apt-get update \ texlive-xetex \ tzdata \ zip $ADDITIONAL_BASE_IMAGE_PACKAGES \ - && curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \ + && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y --no-install-recommends --no-install-suggests nodejs \ && apt-get clean \ && rm -fr /var/lib/apt/lists/* /tmp/* @@ -146,7 +144,12 @@ RUN apt-get update \ # ================================================================== # Phase 3 - Install additional Perl modules from CPAN that are not packaged for Ubuntu or are outdated in Ubuntu. -RUN cpanm install -n Statistics::R::IO DBD::MariaDB Mojo::SQLite@3.002 Perl::Tidy@20220613 \ +RUN cpanm install -n \ + Statistics::R::IO \ + DBD::MariaDB \ + Perl::Tidy@20240903 \ + Archive::Zip::SimpleZip \ + Net::SAML2 \ && rm -fr ./cpanm /root/.cpanm /tmp/* # ================================================================== diff --git a/DockerfileStage2 b/DockerfileStage2 index f14bf34b5b..4706f5bfb5 100644 --- a/DockerfileStage2 +++ b/DockerfileStage2 @@ -33,10 +33,10 @@ RUN echo Cloning branch $PG_BRANCH branch from $PG_GIT_URL \ # We need to change FROM before setting the ENV variables. -FROM webwork-base:forWW218 +FROM webwork-base:forWW220 ENV WEBWORK_URL=/webwork2 \ - WEBWORK_ROOT_URL=http://localhost::8080 \ + WEBWORK_ROOT_URL=http://localhost:8080 \ WEBWORK_SMTP_SERVER=localhost \ WEBWORK_SMTP_SENDER=webwork@example.com \ WEBWORK_TIMEZONE=America/New_York \ @@ -74,7 +74,7 @@ COPY --from=base /opt/base/pg $APP_ROOT/pg # 7. Apply patches # Patch files that are applied below -COPY docker-config/imagemagick-allow-pdf-read.patch /tmp +COPY docker-config/pgfsys-dvisvmg-bbox-fix.patch /tmp RUN echo "PATH=$PATH:$APP_ROOT/webwork2/bin" >> /root/.bashrc \ && mkdir /run/webwork2 /etc/ssl/local \ @@ -91,8 +91,8 @@ RUN echo "PATH=$PATH:$APP_ROOT/webwork2/bin" >> /root/.bashrc \ && npm install \ && cd $PG_ROOT/htdocs \ && npm install \ - && patch -p1 -d / < /tmp/imagemagick-allow-pdf-read.patch \ - && rm /tmp/imagemagick-allow-pdf-read.patch + && patch -p1 -d / < /tmp/pgfsys-dvisvmg-bbox-fix.patch \ + && rm /tmp/pgfsys-dvisvmg-bbox-fix.patch # ================================================================== # Phase 5 - Final setup and prepare docker-entrypoint.sh diff --git a/LICENSE b/LICENSE index 4b31d3c4ce..821f53097f 100644 --- a/LICENSE +++ b/LICENSE @@ -2,7 +2,7 @@ Online Homework Delivery System Version 2.* - Copyright 2000-2023, The WeBWorK Project + Copyright 2000-2025, The WeBWorK Project All rights reserved. diff --git a/README.md b/README.md index d5f10fbd23..db8e9a149c 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ Version 2.* Branch: github.com/openwebwork - https://webwork.maa.org/wiki/Release_notes_for_WeBWorK_2.18 - Copyright 2000-2023, The WeBWorK Project + https://webwork.maa.org/wiki/Release_notes_for_WeBWorK_2.20 + Copyright 2000-2025, The WeBWorK Project https://openwebwork.org/ All rights reserved. @@ -26,8 +26,6 @@ New users interested in getting started with their own WeBWorK server, or instru ## Information for Downloading -* The current version is WeBWorK-2.18 and its companion PG-2.18 - * Installation manuals can be found at https://webwork.maa.org/wiki/Category:Installation_Manuals ## Information For Developers diff --git a/VERSION b/VERSION index ac85d97229..450fad3c87 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ -$WW_VERSION = '2.18'; -$WW_COPYRIGHT_YEARS = '1996-2023'; +$WW_VERSION = '2.20'; +$WW_COPYRIGHT_YEARS = '1996-2025'; 1; diff --git a/assets/pg/PGMLLab/PGML-lab.pg b/assets/pg/PGMLLab/PGML-lab.pg new file mode 100644 index 0000000000..b58c76614c --- /dev/null +++ b/assets/pg/PGMLLab/PGML-lab.pg @@ -0,0 +1,1209 @@ +DOCUMENT(); + +loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); + +sub EscapeHTML { + my $s = shift; + $s =~ s/&/~~&/g; + $s =~ s//~~>/g; + $s =~ s/"/~~"/g; + return $s; +} + +# Make a reference menu +sub Menu { + return tag( + 'select', + aria_labelledby => 'reference-label', + style => 'width:11em', + join('', + tag('option', selected => undef, shift), + map { tag('option', disabled => undef, value => $_, $_) } @_) + ); +} + +# Make an example menu +sub Examples { + my ($title, @examples) = @_; + + return tag( + 'select', + class => 'example-selector', + id => $title =~ s/ /_/gr, + aria_labelledby => 'examples-label', + style => 'width:11em', + tag('option', value => '', selected => undef, $title) . join( + '', + map { + tag( + 'option', + value => $_->[0], + data_vars => EscapeHTML($_->[1][0]), + data_pgml => EscapeHTML($_->[1][1]), + $_->[0] + ) + } @examples + ) + ); +} + +TEXT(tag('div', style => 'text-align:center', tag('h2', 'Interactive PGML Lab'))); + +$vars = $inputs_ref->{vars} // ''; +$pgml = ($inputs_ref->{pgml} // '') =~ s/~~r?~~n/~~n/gr; +$result = ''; + +if ($vars ne '') { + ($vresult, $verror) = PG_restricted_eval($vars); + if ($verror) { + $verror =~ s/ at ~~(eval ~~d+~~) line ~~d+(, at EOF)?//; + $verror = EscapeHTML($verror); + $verror =~ s/~~n/
/g; + $verror = tag('span', style => 'color:#c00', 'Error processing variables: ' . tag('i', $verror)); + } +} else { + $vars = ''; +} + +if ($pgml ne '') { + $PGML::warningsFatal = 1; + ($result, $error) = PG_restricted_eval('PGML::Format($pgml)'); + if ($error) { + $result = $error; + $result =~ s/ at ~~(eval ~~d+~~) line ~~d+//; + $result = EscapeHTML($result); + $result =~ s/~~n/
/g; + $result = tag('span', style => 'color:#c00', $result); + } + warn join('', @PGML::warnings) . "~~n" if scalar(@PGML::warnings); + if ($inputs_ref->{showTeX}) { + $oldDisplay = $displayMode; + $displayMode = 'TeX'; + + # The variables need to be processed again before processing the problem. This redefines all of the variables + # as new objects. If this is not done, then errors occur for many of the examples with MathObjects because the + # the problem has already been processed above. Ignore the errors this time. Those have already been caught + # above. + PG_restricted_eval($vars) if $vars ne ''; + + ($tex, $error) = PG_restricted_eval('PGML::Format($pgml)'); + $displayMode = $oldDisplay; + } + if ($inputs_ref->{showPTX}) { + $oldDisplay = $displayMode; + $displayMode = 'PTX'; + + # The variables need to be processed again before processing the problem. This redefines all of the variables + # as new objects. If this is not done, then errors occur for many of the examples with MathObjects because the + # the problem has already been processed above. Ignore the errors this time. Those have already been caught + # above. + PG_restricted_eval($vars) if $vars ne ''; + + ($ptx, $error) = PG_restricted_eval('PGML::Format($pgml)'); + $displayMode = $oldDisplay; + } + $pgml = EscapeHTML($pgml); +} + +$prows = scalar(split(/~~n/, $pgml)); +$prows = 10 unless $prows >= 10; +$vrows = scalar(split(/~~n/, $vars)); +$vrows = 3 unless $vrows >= 3; + +RECORD_FORM_LABEL('vars'); +RECORD_FORM_LABEL('pgml'); +RECORD_FORM_LABEL('showHTML'); +RECORD_FORM_LABEL('showTeX'); +RECORD_FORM_LABEL('showPTX'); + +TEXT(tag( + 'div', + style => 'display:flex; flex-wrap:wrap; gap:1rem;', + tag( + 'div', + style => 'margin:1rem auto 0;width:fit-content;' + . 'display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:1rem;', + tag( + 'fieldset', + style => 'border:1px solid #5555;border-radius:4px;padding:1rem;' + . 'display:flex;flex-direction:column;gap:0.25rem', + join( + '', + tag('legend', id => 'examples-label', style => 'font-size:20px', tag('h3', 'Examples')), + Examples( + 'Math', + [ + 'LaTeX syntax' => [ + '', + <<~'ENDPGML' + Inline math with LaTeX syntax like [`\frac{1}{6}+\frac{1}{3}=\frac{1}{2}`]. + + This can use display style like [``\frac{1}{6}+\frac{1}{3}=\frac{1}{2}``]. + + Or you can have actual display math like: [```\frac{1}{6}+\frac{1}{3}=\frac{1}{2}```] + ENDPGML + ] + ], + [ + 'Math Object syntax' => [ + '', + <<~'ENDPGML' + Inline math that is parsed using the Typeset context like [:1/6+1/3=1/2:]. + + This can use display style like [::1/6+1/3=1/2::]. + + Or you can have actual display math like: [:::1/6+1/3=1/2:::] + ENDPGML + ] + ], + [ + 'Specify context' => [ + q!$context = Context('Vector');!, + <<~'ENDPGML' + Inline math that is parsed using a specified context like [:<1,2x>:]{'Vector'}. + + Or using a context object: [:<1,2x>:]{$context}. + + Or use the active context [:<1,2x>:]*. + + These all have display style and display mode variants. + ENDPGML + ] + ], + ), + Examples( + 'Answers', + [ + 'Fixed number' => [ + '', + <<~'ENDPGML' + The number twelve is [_______]{12} where width + is specified by how many underscores, + or it is [_]{12}{7} where width is specified directly. + + (Width is not relevant when MathQuill input is in use.) + ENDPGML + ] + ], + [ + 'Formula string' => [ + '', + <<~'ENDPGML' + The formula is [_]{'1+x'}{16}. + + The argument should be a string that is understood by Compute() in the current context. + ENDPGML + ] + ], + [ + 'PG variable' => [ + <<~'ENDPG', + $f = Formula('1+x^2'); + $Df = $f->D; + Context()->functions->disable('Trig'); + $g = Formula('sqrt(x^2-1)')->with(limits => [1,2]); + ENDPG + <<~'ENDPGML' + Suppose [`f(x) = [$f]`]. Then [`f'(x) =`] [_]{$Df}{16}. + + [`\arctan\sec(x) =`] [_]{$g}{16} + + The argument can be a variable representing a number, a string, a Math Object, or a cmp() routine. + ENDPGML + ] + ], + [ + 'Math Object' => [ + '', + <<~'ENDPGML' + Twelve is [_]{Real(12)}{16}. + + Something that is equivalent to [`2`] mod [`10`] is [_]{Real(2)->with(period => 10)}{16}. + + The argument can be a Math Object constructor. + ENDPGML + ] + ], + [ + 'Checker options' => [ + q!$cmp = Formula('x^2')->cmp(upToConstant => 1);!, + <<~'ENDPGML' + [::Int(x,2x)::] = [_]{Formula('x^2')->cmp(upToConstant => 1)}{16} [`+C`] + + [::Int(x,2x)::] = [_]{$cmp}{16} [`+C`] + + The argument can be a Math Object's cmp() method with options passed. + ENDPGML + ] + ], + [ + 'Answer array' => [ + <<~'ENDPG', + $M = Matrix([1, 2], [3, 4]); + Context('Vector'); + ENDPG + <<~'ENDPGML' + [`[$M] =`] [___]*{$M} + + [`\langle1, 2\rangle =`] [_]*{'<1, 2>'}{5} + + [`\begin{bmatrix}1\\ 2\end{bmatrix} =`] [_]*{ColumnVector(1, 2)}{5} + + [`(1, 2) =`] [_]*{Point(1, 2)}{5} + + For an answer array, use an asterisk. + This is appropriate for Math Object Matrix, Vector, ColumnVector, or Point objects. + The answer blank size applies to each input field. + ENDPGML + ] + ], + [ + 'MultiAnswer' => [ + <<~'ENDPG', + loadMacros('parserMultiAnswer.pl'); + $mp = MultiAnswer(12, 6)->with(checker => sub {1}, singleResult => 1); + ENDPG + <<~'ENDPGML' + Twelve and six are [_____]{$mp} and [_____]{$mp}. + (This MultiAnswer object returns correct no matter what you enter.) + + For a MultiAnswer object, use the object as the argument repeatedly. + ENDPGML + ] + ], + [ + 'Options format' => [ + '', + <<~'ENDPGML' + The number twelve is [____]{answer => 12, width => 10}. + + The answer and width may be declared as a key-value hash. + ENDPGML + ] + ], + [ + 'External ANS' => [ + <<~'ENDPG', + Context('Vector'); + ANS(Vector(1, 2, 3)->cmp(showCoodinateHints => 0)); + ENDPG + <<~'ENDPGML' + [:<1,2,3>:]* = [__________] + + The answer may be declared outside of the PGML markup, by passing ANS() a cmp() routine. + ENDPGML + ] + ], + ), + Examples( + 'Lists', + [ + Enumerated => [ + '', + <<~'ENDPGML' + Enumerated lists can use arabic, alphabetic, Alphabetic, roman, or Roman enumeration. + 1. This is the first item. + 2. This is the second item. + + A dot or right parenthesis can be used. + a) This is the first item. + b) This is the second item. + + The actual number/letter used does not matter. + A) This is the first item. + A) This is the second item. + + And we have roman: + i. This is the first item. + i. This is the second item. + + And we have Roman: + I) This is the first item. + II) This is the second item. + ENDPGML + ] + ], + [ + Bulleted => [ + '', + <<~'ENDPGML' + Bullet items can be indicated with a star: + * Apple + * Banana + + Or with a plus: + + Apple + + Banana + + Or with an o: + o Apple + o Banana + + Or with an dash: + - Apple + - Banana + ENDPGML + ] + ], + [ + Sublists => [ + '', + <<~'ENDPGML' + 1. A list + a. with a sublist + a. of three items + i. deep + i. nesting + a. (indent sublist items with four spaces) + 2. Back to the main list + * it works with + * bullets too + ENDPGML + ] + ], + [ + 'Finer points' => [ + '', + <<~'ENDPGML' + 1. A list item can continue + onto a new line of markup, + even with indentation, + 1. but it's still the same line of output. + + A new paragraph ends the enumeration... + 1. And if another item comes, it starts a new list. + 1. However if you indent that next paragraph, + + then it is part of the previous list item + 1. and the next item continues the enumeration. + 1. If you end a line with three spaces as this line + 1. that ends the enumeration so any more items start over + ENDPGML + ] + ], + ), + Examples( + 'Substitutions', + [ + Variables => [ + <<~'ENDPG', + $a = 1; + $y = Formula('(x + 1)/(x - 1)'); + ENDPG + <<~'ENDPGML' + Here we substitute variables into the body: a = [$a], y = [$y] + + Inside math: [`y = [$y]`] (TeX inserted automatically), + + Or parsed: [:y = [$y]:] (string inserted automatically). + ENDPGML + ] + ], + [ + 'Variable-like' => [ + <<~'ENDPG', + @b = (1,2); + %c = (taco => 3, pizza => 4); + $f = Formula('x^2'); + ENDPG + <<~'ENDPGML' + The first entry of @b is [$b[0]]. + + And %c's value for 'taco' is [$c{taco}]. + + We can apply a method: [`[$f->substitute(x => 'x + 1')]`]. + ENDPGML + ] + ], + [ + Commands => [ + <<~'ENDPG', + sub F {return (shift)+1}; + $x = 5; + $g = Compute('x^2'); + ENDPG + <<~'ENDPGML' + Here we execute perl/PG commands inside the body. + + Add one to five: [@ F($x) @] + + Add one to five: [@ 1 + 5 @] + + The derivative of [`[$g]`] is [`[@ $g->D->TeX @]`]. + ENDPGML + ] + ], + [ + Comments => [ + '', + <<~'ENDPGML' + This [% text %] is removed. + + So are these [% partial [@ and incomplete %] commands. + + Comments can be nested: [% one [% and two %] and three %] + ENDPGML + ] + ], + [ + 'Processing control' => [ + '$x = "has PGML markup [:x+1:] and ${BBOLD}bold${EBOLD}";', + <<~'ENDPGML' + Contents of substitutions may have HTML control characters, and will be escaped: + [$x] + + Unless followed by a star: + [$x]* + + Two stars will further cause any PGML to be processed: + [$x]** + ENDPGML + ] + ], + [ + 'Tags' => [ + <<~ 'END_PG', + loadMacros('parserMultiAnswer.pl', 'parserPopUp.pl'); + $ma = MultiAnswer(DropDown([ [ 'minimum', 'maximum' ] ], 1), 4)->with( + singleResult => 1, + checker => sub { + my ($cor, $stu) = @_; + return $cor->[0] == $stu->[0] && $cor->[1] == $stu->[1] + ? 1 + : 0; + } + ); + END_PG + <<~'ENDPGML' + A tag is a "div" by default. HTML attributes can be set via an array in the + first option. The only other allowed tag type is a span. To switch to a + span add 'span' to the beginning of the attribute array. The second tex + option and the third ptx option are used to set the output for the TeX and + PTX display modes. The format of the tex option is an array containing two + strings. The first string is the TeX code to insert before the content, + and the second the TeX code to insert after the content. The format of the + ptx option is similar to the format for the first html. It is an array with + the tag name (required), optionally followed by an array of attributes, and + optionally a separator. + + []{ + [ 'span', class => 'p-1 alert alert-danger', role => 'alert' ] + }{ + [ '{\color{red}', '}' ] + }{ + ['alert'] + } + + Tags may contain other PGML content and answers. Note that a span tag may + not contain new lines, tables, or anything else that would be invalid in a + span. Basically only text elements are valid. However, div tags may + contain pretty much anything. + + [< + This is an equation [`x + 3 = 5`]. + + The solution to the above equation is [_]{2}. + >]{ [ style => 'border: 1px solid black; padding: 1rem;' ] } + + One useful application is when using the parserMultiAnswer.pl macro with + singleResult answers. Wrap the answers in a div tag with the + "ww-feedback-container" class to tell PG where to place the feedback + button. The feedback button will be placed at the end of the containing div + tag. + + [< + The [_]{$ma} value of [`f(x)`] is [_]{$ma} for the function + [`f(x) = 4 - x^2`]. + >]{ [ class => 'ww-feedback-container' ] } + ENDPGML + ] + ], + ), + Examples( + 'Inline formatting', + [ + Emphasis => [ + '', + <<~'ENDPGML' + These words are in *bold* or _italic_. + + Stars can be used in*side* a word, + but underlines_don't_work_that_way. + ENDPGML + ] + ], + [ + 'Smart Quotes' => [ + '', + <<~'ENDPGML' + Quotes are "smart" ("even here"), and don't worry about other 'quotes' like apostrophes. + + If you need a plain quote, escape with a backslash: \"dumb quotes\". + ENDPGML + ] + ], + [ + Verbatim => [ + '', + <<~'ENDPGML' + Text that includes commands can be enclosed to prevent interpretation: + [|This math markup [:x+1:] is not processed.|] + + You can use more vertical bars if you have need to make the verbatim markup unprocessed. + [||This is [|verbatim|].||] + + With a star, [|verbatim content|]* will have code formatting. + ENDPGML + ] + ], + [ + Escaping => [ + '', + <<~'ENDPGML' + Use backslashes to escape command characters if you need to: + For example, something occurred in the year + 1\. (Prevent accidental list). + + Or don't make this a comment: \[% you will see this %]. + ENDPGML + ] + ], + ), + Examples( + 'Block formatting', + [ + Headings => [ + '', + <<~'ENDPGML' + # Heading level 1 # + ## Heading level 2 ## + ### Heading level 3 ### + #### Heading level 4 #### + ##### Heading level 5 ##### + ###### Heading level 6 ###### + ### Two separate lines ### + ### are combined ### + + ### A whole paragraph + can be a heading ### + + ### End with two spaces ### + ### for two lines separately ### + + ### The trailing hashes are optional. + >> ## centered heading ## << + >> ## right-justified ## + ENDPGML + ] + ], + [ + 'Breaks' => [ + '', + <<~'ENDPGML' + A blank line is a paragraph break + + Even if it contains nonempty white space + + Force a line break by + ending a line with two spaces + + ## even in a heading ## + ## that runs over two lines + ENDPGML + ] + ], + [ + Indentation => [ + '', + <<~'ENDPGML' + Indent a section by using four spaces or a tab + This is indented, + and continues on a second line. + Another four spaces indents again. + Go back to four to end the inner indenting. + Note, however, that you only need to indent + the first line of a paragraph to have all of it + be indented. (That may need to be changed.) + + End the paragraph to go back to no indenting + or use _three_ spaces to end the line + and that will end the indenting + ENDPGML + ] + ], + [ + Rules => [ + '', + <<~'ENDPGML' + Three or more dashes or equals on a line by itself forms a rule. + + ----- + You can specify the width and height if you want: + ----{200} + ===={'50%'} + ----{'3in'}{5} + ===={height => 5} + + You can center and right-justify rules: + >> ----{100} << + >> ----{100} + ENDPGML + ] + ], + [ + Centering => [ + '', + <<~'ENDPGML' + Use angle brackets to center a phrase: + + >> This is centered << + + You can center several lines as a paragraph: + >> These lines will << + >> be combined << + + Or you can force line breaks with two spaced at the end: + >> These lines will << + >> be centered separately << + + A whole paragraph can be centered: + >> This is a paragraph + that will be centered << + ENDPGML + ] + ], + [ + 'Right justify' => [ + '', + <<~'ENDPGML' + Use right angle brackets to force a line or paragraph + to be right-justified: + >> At the right + + >> Several lines combined. + >> right justfied + + >> Or a whole paragaph + that is pushed to the right + + >> Or two lines + >> justified separately. + ENDPGML + ] + ], + [ + Preformatted => [ + q!$s = 'substitutions'!, + <<~'ENDPGML' + Preformatted text starts with a colon and three spaces: + : This is preformatted, + : and can include any text, e.g., <, >, $, etc., + : but [@ "commands" @], [$s], and other *markup* are performed normally. + : Use verbatim mode like [|[@ "commands" @]|] if you want to include commands/substitutions literally, + : or use a slash to escape them: \[$s]. + Preformatted text can be indented, too: + Here is some indenting + : with preformatting + : on several lines. + Now back to normal, but indented. + ENDPGML + ] + ], + ), + Examples( + 'Images', + [ + 'Image file' => [ + '', + <<~'ENDPGML' + The image file should have a path relative to the location of the PG file. + + [!The WeBWorK logo (this is the alt text)!]{'webwork_logo.png'} + + You can specify width and height as pixel counts. If only one is provided, aspect ratio is preserved. + + [!The WeBWorK logo!]{'webwork_logo.png'}{300} + + [!The WeBWorK logo!]{'webwork_logo.png'}{300}{200} + + [!The WeBWorK logo!]{'webwork_logo.png'}{height => 200} + ENDPGML + ], + ], + [ + 'Tikz image' => [ + <<~'ENDPG', + loadMacros('PGtikz.pl'); + $circle = createTikZImage(); + $circle->tex('\draw (0,0) circle[radius=1.5];'); + ENDPG + <<~'ENDPGML' + [!A circle!]{$circle} + + [!A circle!]{$circle}{100} + ENDPGML + ], + ], + [ + 'LaTeX image' => [ + <<~'ENDPG', + loadMacros('PGlateximage.pl'); + $diagram = createLaTeXImage(); + $diagram->texPackages([['xy','all']]); + $diagram->tex('\xymatrix{ A \ar[r] & B \ar[d] \\\\\ D \ar[u] & C \ar[l] }'); + ENDPG + <<~'ENDPGML' + [!A diagram!]{$diagram} + + [!A diagram!]{$diagram}{100} + ENDPGML + ], + ], + [ + 'PGgraphmacros image' => [ + <<~'ENDPG', + loadMacros('PGgraphmacros.pl'); + $graph = init_graph(-1, -1, 4, 4, + axes => [0,0], + grid => [5,5], + size=>[200, 200] + ); + add_functions($graph, "x^2 for x in <-1,2> using color:blue and weight:2"); + ENDPG + <<~'ENDPGML' + [!A graph!]{$graph} + + [!A graph!]{$graph}{100} + ENDPGML + ], + ] + ), + Examples( + 'Tables', + [ + 'Data table' => [ + '', + <<~'ENDPGML' + Tables use bracket-hash delimiters for the entire table, and bracket-dot delimiters for each cell. + A cell that is starred indicates the end of a row. (A star on the last cell is optional.) + + [# + [. A .] + [. B .]* + [. C .] + [. D .]* + [. E .] + [. F .] + #] + + Cells can have PGML markup. + + [# + [. [`x`] .] + [. [`x^2`] .]* + [. [`2`] .] + [. [_]{4} .]* + [. [_]{3} .] + [. [`9`] .] + #] + ENDPGML + ] + ], + [ + 'Table options' => [ + '', + <<~'ENDPGML' + There are many options for cells and the table as a whole. Here are just a few examples. + + [# + [. A .] + [. B .]*{headerrow => 1} + [. C .] + [. D .]* + [. E .] + [. F .] + #]{padding => [0,1]} + + These are all the options. For details about how they work, see the niceTables.pl documentation. + [# + [.[# + [.Option .] [.Default .] [.General .]*{headerrow => 1} + [.caption .] [. .] [.string .]* + [.horizontalrules.] [.0 .] [.boolean .]* + [.texalignment .] [. .] [.string .]* + [.align .] [. .] [.string .]* + [.Xratio .] [.0.97 .] [.number .]* + [.encase .] [.\[,\] .] [.array ref.]* + [.rowheaders .] [.0 .] [.boolean .]* + [.headerrules .] [.1 .] [.boolean .]* + [.valign .] [.\'top\' .] [.string .]* + [.padding .] [.\[0,0.5\].] [.array ref.]* + [.tablecss .] [. .] [.string .]* + [.captioncss .] [. .] [.string .]* + [.columnscss .] [. .] [.string .]* + [.datacss .] [. .] [.string .]* + [.headercss .] [. .] [.string .]* + [.allcellcss .] [. .] [.string .]* + [.booktabs .] [.1 .] [.boolean .]* + #]{caption => 'Options for the whole table', horizontalrules => 1, align => 'lcl'}.] + + [.[# + [.Option .] [.Default .] [.General .]*{headerrow => 1} + [.halign .] [. .] [.string .]* + [.header .] [. .] [.string .]* + [.color .] [. .] [.string .]* + [.bgcolor .] [. .] [.string .]* + [.b .] [.0 .] [.boolean .]* + [.i .] [.0 .] [.boolean .]* + [.m .] [.0 .] [.boolean .]* + [.noencase .] [.0 .] [.boolean .]* + [.colspan .] [. .] [.number .]* + [.top .] [. .] [.string .]* + [.bottom .] [. .] [.string .]* + [.cellcss .] [. .] [.string .]* + [.texpre .] [. .] [.string .]* + [.texencase .] [.\[,\] .] [.array ref.]* + #]{caption => 'Options for a cell', horizontalrules => 1, align => 'lcl'}.] + + [.[# + [.Option .] [.Default .] [.General .]*{headerrow => 1} + [.rowcolor .] [. .] [.string .]* + [.rowcss .] [. .] [.string .]* + [.color .] [. .] [.string .]* + [.headerrow .] [.0 .] [.boolean .]* + [.rowtop .] [. .] [.string .]* + [.rowbottom .] [. .] [.string .]* + [.valign .] [.\'top\' .] [.string .]* + #]{caption => 'Options for a cell that affect a row', horizontalrules => 1, align => 'lcl'}.] + #]* + ENDPGML + ] + ], + [ + 'Layout table' => [ + '', + <<~'ENDPGML' + A layout table has mostly the same markup as a data table, but it is starred. + Layout tables are for laying out content visually when it's not important + for a reader to know what component is in which row and which column. + + [# + [. Some text .] + [. [!The WeBWorK logo!]{'webwork_logo.png'} .]* + + [. + [# + [. [`x`] .] + [. [`x^2`] .]*{headerrow => 1} + [. [`2`] .] + [. [_]{4} .]* + [. [_]{3} .] + [. [`9`] .] + #] + .] + #]* + ENDPGML + ] + ] + ), + Examples( + 'Problems', + [ + Algebra => [ + 'Context("Interval"); $a = random(1,8,1); $b = random(8,15,1); $min = $a-$b; $max = $a+$b;', + "Solve the following inequality and enter your answer using interval notation:\n\n" + . ' [``|x-[$a]| > [$b]``]' . "\n\n" + . 'Answer: [`x`] must be in [____________________________]{"(-inf,$min)U($max,inf)"}' + ] + ], + [ + Composition => [ + '$b=non_zero_random(-3,1,1)+1; # b=1 makes answers equal' . "\n" + . '$f = Formula("x+$b"); $g = Formula("(x-2)^2");' . "\n" + . '$F = "$f for x in [-1,5] using color:blue and weight:2";' . "\n" + . '$G = "$g for x in [0,4] using color:red and weight:2";' . "\n\n" + . 'loadMacros("PGgraphmacros.pl");' . "\n" + . '$graph = init_graph(-2,-4,6,8,axes => [0,0],grid => [8,12],size => [200,200]);' + . "\n" + . 'plot_functions($graph,$F,$G);' . "\n" + . '$lf = new Label (5.3,$f->eval(x => 5)+.3,"f","blue","left","bottom");' . "\n" + . '$lg = new Label (.3,$g->eval(x => 0)+.3,"g","red","left","bottom");' . "\n" + . '$graph->lb($lf,$lg);', + "Let [`f`] be the linear function (in blue) and let [`g`] be the\n" + . "parabolic function (in red) below.\n\n" + . ' [@ image(insertGraph($graph),' . "\n" + . ' width => 200,height => 200,tex_size => 480) @]*' . "\n\n" + . ' 1. [:(f o g)(2):] = [____]{$b}' . "\n" + . ' 2. [:(g o f)(2):] = [____]{$b**2}' . "\n" + . ' 3. [:(f o f)(2):] = [____]{2+2*$b}' . "\n" + . ' 4. [:(g o g)(2):] = [____]{4}' . "\n\n" + ] + ], + [ + Derivative => [ + '$aa = random(3,8,1);' . "\n" + . '$f = Formula("atan(sqrt(${aa}x^2-1))");' . "\n" + . '$Df = $f->D->with(limits => [1/sqrt($aa),1]);', + q!Let [`f(x) = [$f]`]. Find [`f'(x)`].! . "\n\n" + . q![`f'(x)`] = [____________________________________]{$Df}! + ] + ], + [ + Logarithm => [ + '$a = random(3,5,1); $b = random(2,20,1); $c = random(2,20,1);', + 'Use the laws of logarithms to rewrite the expression' . "\n\n" + . ' [::ln(root [$a] of xy)::]' . "\n\n" + . 'in a form that does not contain any logarithm of a product,' . "\n" + . 'quotient or power.' . "\n\n" + . 'After rewriting, we have' . "\n\n" + . ' [::ln(root [$a] of xy) = A ln x + B ln y::]' . "\n\n" + . 'with constants' . "\n\n" + . ' [`A`] = [_______________]{1/$a} and ' . "\n" + . ' [`B`] = [_______________]{1/$b}.' . "\n\n" + ] + ], + [ + Optimization => [ + 'loadMacros("parserMultiAnswer.pl"); $a = random(200, 320, 10); $b = random(3, 6, 1); $c = random(12, 16, 1);' + . "\n\n" + . '$length = sqrt($a*($b+$c)/(2*$b)); $width = sqrt(2*$b*$a/($b+$c));' . "\n\n" + . '$mp = MultiAnswer(Real($length), Real($width))->with(' . "\n" + . ' singleResult => 1, separator => " x ", tex_separator => "\\\\times",' . "\n" + . ' checker => sub {' . "\n" + . ' my ($correct, $student) = @_;' . "\n" + . ' my ($a,$b) = @$correct; my ($A,$B) = @$student;' . "\n" + . ' return ($a == $A && $b == $B) || ($a == $B && $b == $A);' . "\n" . ' }' + . "\n" + . ');', + 'A fence is to be built to enclose a rectangular area of [$a] square' . "\n" + . 'feet. The fence along three sides is to be made of material that' . "\n" + . 'costs [$b] dollars per foot, and the material for the fourth side' . "\n" + . 'costs [$c] dollars per foot. Find the dimensions of the enclosure' . "\n" + . 'that is most economical to construct.' . "\n\n" + . 'Dimensions: [___________]{$mp} x [___________]{$mp} feet' + ] + ], + ) + ) + ) + . tag( + 'fieldset', + style => 'border:1px solid #5555;border-radius:4px;padding:1rem;' + . 'display:flex;flex-direction:column;gap:0.25rem', + join( + '', + tag('legend', id => 'reference-label', style => 'font-size:20px', tag('h3', 'Reference')), + Menu( + '- Math -', + '[`tex`]', + '[``display-tex``]', + '[:parsed-math:]', + '[::parsed-display-math::]', + '[:parsed-math:]{context}', + '[:parsed-math:]* (uses current context)', + ), + Menu( + '- Answers -', + '[______] (# of _ is width)', + '[___]{answer}', + '[___]{answer}{width}', + '[___]{answer}{width}{name}', + '[___]{answer => ...,width => ...,name => ...}', + '[___]* (ans_array not ans_rule)', + ), + Menu( + '- Lists -', + '1. (numeric list)', + 'a. (alpha list)', + 'A. (capital alphas)', + 'i. (roman numerals)', + 'I. (capital roman)', + '* (bullet list)', + '- (bullet list)', + '+ (square bullets)', + 'o (circle bullets)', + ), + Menu( + '- Substitutions -', + '[$variable]', + '[$variable]* (no escaping)', + '[$variable]** (parse results)', + '[@ perl-command @]', + '[@ perl-command @]* (no escaping)', + '[@ perl-command @]** (parse results)', + '[% comment %]', + '[<tag>]{html}{tex}{ptx}', + '[[url]] (not implemented)', + '[!image!]{source}{width}{height}', + ), + Menu( + '- Formatting -', + "$SP$SP\n (line break)", + "$SP$SP$SP\n (format break)", + 'blankline (par break)', + "$SP$SP$SP$SP or \t (indent)", + '>> ... << (center)', + '>> ... (right justify)', + '--- (hrule)', + '---{width}', + '---{width}{size}', + '*bold*', + '_italic_', + '*_bold-italic_*', + ":$SP$SP$SP (preformatted)", + '[|verbatim|]', + ), + Menu( + '- Headings -', + '# heading 1 #', + '## heading 2 ##', + '### heading 3 ###', + '#### heading 4 ####', + '##### heading 5 #####', + '###### heading 6 ######', + ) + ) + ) + ) + + # Input textareas + . tag( + 'div', + style => 'width:fit-content;max-width:100%;text-align:left;margin:auto', + tag('label', for => 'vars', tag('small', tag('i', style => 'color:#555', 'PG setup code'))) + . tag( + 'textarea', + name => 'vars', + id => 'vars', + rows => $vrows, + cols => 75, + style => 'display:block;font-family:DejaVu Sans Mono,Courier New,monospace;', + $vars + ) + . tag('label', for => 'pgml', tag('small', tag('i', style => 'color:#555', 'PGML body markup'))) + . tag( + 'textarea', + name => 'pgml', + id => 'pgml', + rows => $prows, + cols => 75, + style => 'display:block;font-family:DejaVu Sans Mono,Courier New,monospace;', + $pgml + ) + . tag( + 'div', + style => 'margin-top:0.25rem;display:flex;justify-content:space-between;align-items:center', + tag( + 'div', + tag( + 'div', + tag( + 'label', + tag( + 'input', + type => 'checkbox', + name => 'showHTML', + value => 1, + $inputs_ref->{showHTML} ? (checked => undef) : () + ) + . ' Show HTML code ' + ) + ) + . tag( + 'div', + tag( + 'label', + tag( + 'input', + type => 'checkbox', + name => 'showTeX', + value => 1, + $inputs_ref->{showTeX} ? (checked => undef) : () + ) + . ' Show TeX code' + ) + ) + . tag( + 'div', + tag( + 'label', + tag( + 'input', + type => 'checkbox', + name => 'showPTX', + value => 1, + $inputs_ref->{showPTX} ? (checked => undef) : () + ) + . ' Show PTX code' + ) + ) + ) + . tag('div', tag('input', type => 'submit', name => 'action', value => 'Process this Text')) + ) + ) +)); + +$SP = "␣"; +TEXT(tag('script', <<~'ENDSCRIPT')); + window.addEventListener('DOMContentLoaded', () => { + const unescapeHTML = (html) => { + return html + .replace(/>/g, '>') + .replace(/</g, '<') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/\n/g, '~~n') + .replace(/\\/g, '\'); + }; + for (const select of document.querySelectorAll('.example-selector')) { + select.addEventListener('change', () => { + if (select.value === '') return; + const selectedExample = select.options[select.selectedIndex]; + const dataType = { vars: 3, pgml: 10 }; + for (const id in dataType) { + const el = document.getElementById(id); + el.value = unescapeHTML(selectedExample.dataset[id]); + el.rows = Math.max(dataType[id], selectedExample.dataset[id].split(/~~n/).length); + } + for (const otherselect of document.querySelectorAll('.example-selector')) { + if (otherselect != select) { + otherselect.value = otherselect.firstChild.value; + } + } + document.getElementById('resultsBox').style.display = 'none'; + }); + } + }); + ENDSCRIPT + +TEXT(tag( + 'div', + id => 'resultsBox', + ($verror ? $HR . $verror . $HR : '') + . ( + (defined $result && $result ne '') ? tag( + 'div', + style => + 'margin:1rem auto; padding:1rem; border:1px solid black; border-radius:4px; background-color:#e8e8e8;', + $result + ) : '' + ) + . ( + ($inputs_ref->{showHTML}) + ? (tag('hr') . tag('small', tag('pre', EscapeHTML($result) =~ s!~~n!
!gr)) . tag('hr')) + : '' + ) + . ( + ($inputs_ref->{showTeX}) + ? (tag('hr') . tag('small', tag('pre', EscapeHTML($tex) =~ s!~~n!
!gr)) . tag('hr')) + : '' + ) + . ( + ($inputs_ref->{showPTX}) + ? (tag('hr') . tag('small', tag('pre', EscapeHTML($ptx) =~ s!~~n!
!gr)) . tag('hr')) + : '' + ) +)); + +ENDDOCUMENT(); diff --git a/assets/pg/Student_Orientation/decimals.pg b/assets/pg/Student_Orientation/decimals.pg new file mode 100644 index 0000000000..d4f617f47b --- /dev/null +++ b/assets/pg/Student_Orientation/decimals.pg @@ -0,0 +1,54 @@ +## DESCRIPTION +## Decimal Tolerance +## ENDDESCRIPTION + +DOCUMENT(); + +loadMacros(qw( + PGstandard.pl + PGML.pl + contextFraction.pl + PGcourse.pl +)); + +BEGIN_PGML +## Decimal Approximations + +Sometimes, your instructor will require you to enter an answer _exactly_. In that case your only option is to enter +[:1/3:] as a fraction: [`\frac13={}`][_]{Context("LimitedFraction"), Fraction(1/3)}{4} (Try [|1/3|]*, [|0.33|]*, +[|0.333|]*, [|0.3333|]*, [|2/6|]*, etc.) + +Sometimes, you will be allowed to use decimal _approximations_ to the real answer. So for instance, in the next answer +blank we can get away with typing [|0.3333|]* even though that is slightly different from [:1/3:]. +[`\frac13={}`][_]{Context("Numeric"), Real(1/3)}{4} (Try [|0.33|]*, [|0.333|]*, [|0.3333|]*.) Why is [|0.3333|]* +accepted and [|0.33|]* is not? _If_ a decimal approximation is acceptable at all, then you need to use enough +significant digits so your answer is "close enough" to the actual correct answer (which was [:1/3:] in this case). In +general, using _four_ significant digits in your decimals will be enough. You will often be able to get away with fewer, +but using four is recommended. + +Use a calculator to find decimal approximations for these values. At first, round your calculator's output to just two +significant digits. Then move up to three. If that is still not enough, move up to four. + + [`\sqrt{110} =`] [_]{sqrt(110)} (Try [|10|]*, [|10.4|]*, [|10.5|]*, [|10.48|]*, [|10.49|]*, etc.) + + [`\frac{1}{491} =`] [_]{1/491}{4} (Try [|0.0020|]*, [|0.00203|]*, [|0.00204|]*, +[|0.002036|]*, etc.) + + [`20380.2 =`] [_]{20380.2}{4} (Try [|20000|]*, [|20300|]*, [|20400|]*, [|20380|]*, etc.) + +With [`\frac{1}{491}`], students sometimes feel they should be able to get away with [`0.002`], since that is only off +by a tiny amount. However, percentage-wise this would be off by [`1.8%`], which is generally considered too much. + +With [`20380.2`], students mistakenly believe they must enter the entire number. While that's fine, it's also OK to +round to four significant digits. So you may simply enter [`20380`] for this answer. + +Decimal tolerance settings may vary from problem to problem and the above is only describing default decimal tolerance. +For example, if a problem has a monetary answer, it might expect you to answer correctly all the way to the hundredths +place, even if it is a large amount in thousands of dollars. Watch out for any specific instructions in each problem +that tell you how precise you should be. + + +[@ MODES(HTML => 'When you are ready, click "Next Problem".', TeX => '') @] +END_PGML + +ENDDOCUMENT(); diff --git a/assets/pg/Student_Orientation/emailInstructor.pg b/assets/pg/Student_Orientation/emailInstructor.pg new file mode 100644 index 0000000000..796a49e037 --- /dev/null +++ b/assets/pg/Student_Orientation/emailInstructor.pg @@ -0,0 +1,40 @@ +## DESCRIPTION +## Email Instructor +## ENDDESCRIPTION + +DOCUMENT(); + +loadMacros(qw( + PGstandard.pl + PGML.pl + PGcourse.pl +)); + +$a = random(5, 10); +$button_name = $envir{feedback_button_name}; + +BEGIN_PGML +## [$button_name] + +If you feel stuck on a problem, do not understand what the problem asks for, suspect there is a bug with the problem, +do not understand what syntax to use to enter the answer, or all of the above, there is an "[$button_name]" button +below the problem that you may use. This button appears on other pages in WeBWorK too. + +When you use this button, you should write a message to your instructor (or TA) explaining what you have tried. It will +help a lot if you write down the steps of the math that you have worked out too. You can even attach a file, for example +a picture of your hand-written work. Then the instructor gets your message along with a link to the problem from _your +perspective_, and they can also see all of your previously attempted answers. This way your instructor can write you +back with good help/hints. + +This answer blank is expecting a certain mystery answer: [_]{Real(112358)->cmp(tolType=>'absolute',tolerance=>0.1)}{4}. +The only way to get the answer is to use the "[$button_name]" button and ask for the answer to this Orientation Problem +[$envir{probNum}]. The instructor will reply to your email as soon as they are able to. [@ if ($isInstructor) +{'(Instructors: you can Check/Submit an answer, then view the feedback to reveal the expected correct answer.)'} @] + +A reply will come in the form of an email. You must check your official email account to receive the reply. Then return +to this problem to submit your answer. + +[@ MODES(HTML => 'When you are ready, click "Next Problem".', TeX => '') @] +END_PGML + +ENDDOCUMENT(); diff --git a/assets/pg/Student_Orientation/enteringMath.pg b/assets/pg/Student_Orientation/enteringMath.pg new file mode 100644 index 0000000000..8db6992a59 --- /dev/null +++ b/assets/pg/Student_Orientation/enteringMath.pg @@ -0,0 +1,104 @@ +## DESCRIPTION +## Entering Math +## ENDDESCRIPTION + +DOCUMENT(); + +loadMacros(qw( + PGstandard.pl + PGML.pl + PGcourse.pl +)); + +$MathQuill = <strings->add(pirate => {}), 'pirate'}{16}. + +The palette tool might be a distraction, especially to keyboard-only users. You can disable it by right-clicking (or +control-clicking) in an answer blank. To "right click" without a mouse: +* on Windows or Linux, use [|shift F10|]*. +* on a Mac, you must first enable Mouse Keys in System Settings, and then use [|control m|]*. + +Try to disable the palette tool now. Once the tool is disabled, your device and web browser should "remember" this +setting when you visit a new problem or log in again. To bring the palette tool back, right-click (or control-click) +again in an answer blank. Unless you are certain you do not want to use the palette tool, you should bring it back now. +END_BODY + +$MathView = < ' ', + TeX => '' + ) +@]* you can use to reveal a palette with tools for constructing math expressions as well as a preview of what the +expression will look like. Try using the palette to help enter the expression [::pi/sqrt(x+1)::]. For this particular +expression: +1. You should start with the fraction building button for [`\frac{a}{b}`]. It will insert [|()/()|]* into the answer +blank. +2. Now place the cursor inside the first set of parentheses. Use the "Operations" list to visit "Others", where you can +click the [`\pi`] button. This will insert [|pi|]* and your answer blank should have [|(pi)/()|]*. +3. Now place the cursor inside the second set of parentheses. Use the "Operations" list to visit "Exponents", where you +can click the [`\sqrt{a}`] button. Now your answer blank looks like [|(pi)/(sqrt())|]*. +4. Finish by typing the [|x+1|]* in the appropriate place. + + [_]{Context("Numeric"), 'pi/sqrt(x+1)'}{16} + +Of course, you might be comfortable directly typing your answer and you do not need to use the palette button. +END_BODY + +$None = <cmp(formatStudentAnswer=>'parsed')}{16} + +Of course you could also simplify this to [`32`]. Now try entering [:1/x:]: + + [_]{'1/x'}{16} + +Raising to a power is typed using the caret symbol [|^|]*, which is usually shift-6 on a keyboard. Try entering [:x^6:]: + + [_]{'x^6'}{16} + +Another common operation is the square root, which is written [`\sqrt{\phantom{x}}`] on paper. There is no square root +character on most keyboards, so we do something else with this operation. We use [|sqrt()|]* where the parentheses +should surround the same content covered by the radical in [`\sqrt{\phantom{x}}`]. Try entering [:sqrt(x+1):]: + + [_]{'sqrt(x+1)'}{16} + +Complex expressions will require you to understand the order of operations and use grouping symbols. Teaching the order +of operations might be part of the course you are taking or it might be something you are expected to already know. Here +is an example of a complex math expression: [::(x^(x+1)+2)/(x+3)::]. Naively, you might type this as [|x^x+1+2/x+3|]*, +but the order of operations would make that come out as [::x^x+1+2/x+3::]. We need to use grouping symbols to (1) make +sure all of the [:x+1:] is included in the exponent, and (2) group the entire numerator and denominator together. We +would need something like [|(x^(x+1)+2)/(x+3)|]*. Try entering [::3^(x+1)/(2(x+2)^2)::]. + + [_]{'3^(x+1)/(2(x+2)^2)'}{16} + +END_BODY + +BEGIN_PGML +## Entering Math + +[@ if ($envir{entryAssist} eq 'MathQuill') {$MathQuill} elsif ($envir{entryAssist} eq 'MathView') {$MathView} else {$None} @]** + +[@ MODES(HTML => 'When you are ready, click "Next Problem".', TeX => '') @] +END_PGML + +ENDDOCUMENT(); diff --git a/assets/pg/Student_Orientation/explorerfull.png b/assets/pg/Student_Orientation/explorerfull.png new file mode 100644 index 0000000000..6b52703fcc Binary files /dev/null and b/assets/pg/Student_Orientation/explorerfull.png differ diff --git a/assets/pg/Student_Orientation/explorerpiece.png b/assets/pg/Student_Orientation/explorerpiece.png new file mode 100644 index 0000000000..a4fb755b36 Binary files /dev/null and b/assets/pg/Student_Orientation/explorerpiece.png differ diff --git a/assets/pg/Student_Orientation/feedback.pg b/assets/pg/Student_Orientation/feedback.pg new file mode 100644 index 0000000000..63d71a87e2 --- /dev/null +++ b/assets/pg/Student_Orientation/feedback.pg @@ -0,0 +1,78 @@ +## DESCRIPTION +## Accessing Feedback +## ENDDESCRIPTION + +DOCUMENT(); + +loadMacros(qw( + PGstandard.pl + PGML.pl + PGcourse.pl +)); + +$random = random(100, 900); +$answer = Real($random)->cmp( + checker => sub { + my ($c, $s, $a) = @_; + Value::Error("The correct answer is $random.") + unless ($s == $random); + return 1; + } +); + +BEGIN_PGML +## Feedback + +When you Submit an answer, a feedback button appears near the answer blank. + +* If you answer correctly, you see a green checkmark[@ + MODES( + HTML => ': ', + TeX => '' + )@]*. +* If you answer incorrectly, you see a red alert[@ + MODES( + HTML => ': ', + TeX => '' + )@]*. +* If you earn partial credit, you see a yellow warning[@ + MODES( + HTML => ': ', + TeX => '' + )@]*. + +Each of these buttons is something you can click to see more information about the answer you tried. And if there is an +actual feedback message, you will see a small circle in the upper right corner of the button[@ + MODES( + HTML => ': ', + TeX => '' + )@]*. + +For example, try answering the following with any answer. Almost certainly, you will be marked incorrect, but the +feedback message will tell you the correct answer. What number am I thinking of? [_]{$answer}{4} + +If you type an answer and click to "Preview my Answers", you will see an info button[@ + MODES( + HTML => ': ', + TeX => '' + ) +@]* instead of the correct/incorrect buttons. + +In addition to automated feedback, your instructor can leave messages for you in WeBWorK. To see these messages, you +must visit the problem for which a message has been left. The message will appear above the exercise. If your course +uses essay questions, there is no automated feedback, and these messages from the instructor will be your only feedback. + +[@ MODES(HTML => 'When you are ready, click "Next Problem".', TeX => '') @] +END_PGML + +ENDDOCUMENT(); diff --git a/assets/pg/Student_Orientation/hardcopy.pg b/assets/pg/Student_Orientation/hardcopy.pg new file mode 100644 index 0000000000..bb9d64a6e0 --- /dev/null +++ b/assets/pg/Student_Orientation/hardcopy.pg @@ -0,0 +1,38 @@ +## DESCRIPTION +## Hardcopies +## ENDDESCRIPTION + +DOCUMENT(); + +loadMacros(qw( + PGstandard.pl + PGML.pl + PGcourse.pl +)); + +$a = random(100, 900); + +BEGIN_PGML +## PDF Hardcopy ## + +From the *Assignments* page (which you may or may not have permission to visit), you may push a download button[@ + MODES( + HTML => ' ', + TeX => '' + ) +@]* to download a PDF version. Alternatively, when you are at the page listing all of one set's exercises, there is a +"Download Hardcopy for Current Set" button. + +This file is something that you can read onscreen while you are offline. You can even print it off and take it to a +tutoring center or somewhere comfortable to work on. If you would like a Braille file for the assignment, that may be +possible with some assistance from your institutional staff. + +To check that you understand how this works, download the PDF version of this Orientation assignment. At the end of +this problem in the PDF, you will find the answer that is expected here: [_]{Compute("$a")}{4} + +[@ if ($displayMode eq 'TeX') {"The answer is $a."} @] + +[@ MODES(HTML => 'When you are ready, click "Next Problem".', TeX => '') @] +END_PGML + +ENDDOCUMENT(); diff --git a/assets/pg/Student_Orientation/mathInteraction.pg b/assets/pg/Student_Orientation/mathInteraction.pg new file mode 100644 index 0000000000..3cf56a7111 --- /dev/null +++ b/assets/pg/Student_Orientation/mathInteraction.pg @@ -0,0 +1,97 @@ +## DESCRIPTION +## Features of MathJax/images displaymode +## ENDDESCRIPTION + +DOCUMENT(); + +loadMacros(qw( + PGstandard.pl + PGML.pl + parserRadioButtons.pl + PGcourse.pl +)); + +$continue = RadioButtons(['I am ready to continue. Submit this answer.'], 0); + +$MathJax = <> [!MathJax contextual menu!]{'mathjaxmenu.png'} << + +There are many features that help you to engage with math content. Explore the menu options to survey what is available. + +We will now point out a few important features. In the main menu, there is a "Math Settings" submenu. The "Zoom Trigger" +and "Zoom Factor" items allow you to control if/how math content is magnified. Magnification may help users with some +vision disabilities see the content better. And it may help all users to see some details of math notation better. Take +a moment to explore these settings and select options that you would be comfortable with. (Of course you can change +these settings at any time.) + +Also in the main menu, there is an "Accessibility" submenu. In that menu, if accessibility is not already activated, +select "Activate". After activating this, you may need to refresh the web page to see the math expression again. Now you +have the option to see math content verbalized. To do this, place focus onto a math expression and hit [|enter|]*. +* At first, the entire expression is highlighted and there will be a verbal rendering of the expression. +[!Speech string for the quadratic formula!]{'explorerfull.png'}{600} +* Use the down arrow to navigate "down" into a smaller piece of the math expression. +* Use the left/right arrows to navigate to similar pieces of the math expression. +* At any time you can navigate back "up" to a larger part of the expression, or "down" into smaller pieces. For example, +you can see a verbalization for just this part of the expression above: +[!Speech string for the radicand of the quadratic formula!]{'explorerpiece.png'}{600} +* Return to the MathJax menu, Accessibility submenu, to explore options for how this explorer tool works. +* Under "Speech" you will find options to use MathSpeak, ClearSpeak, or ChromeVox rules. The default is to use +"MathSpeak verbose" rules, which try try to read math "literally" without context. For example, it reads [`(1,3)`] as +"left parenthesis 1 comma 3 right parenthesis". Other speech rules can produce more meaningful verbal renderings. For +example with the right ClearSpeak settings, the same math expression produces "the point with coordinates 1 comma 3" +or "the interval from 1 to 3 not including 1 or 3". + +Some keyboard-navigating users might find it undesirable for each piece of math content to be tab-indexed. If this is +the case, then in the "Accessibility" sub menu you can uncheck "Include in Tab Order". Just note that in order to undo +this and make math content tabbable again, you will need to access the menu, and so you will need some way other than +tabbing to bring focus back to a piece of math content. +END_BODY + +$images = <variables->are(a => 'Real', b => 'Real'), 'piab'} + +This concludes the WeBWorK student orientation. +END_PGML + +ENDDOCUMENT(); diff --git a/assets/pg/Student_Orientation/navigating.pg b/assets/pg/Student_Orientation/navigating.pg new file mode 100644 index 0000000000..136cc544a0 --- /dev/null +++ b/assets/pg/Student_Orientation/navigating.pg @@ -0,0 +1,74 @@ +## DESCRIPTION +## Navigating +## ENDDESCRIPTION + +DOCUMENT(); + +loadMacros(qw( + PGstandard.pl + PGML.pl + PGcourse.pl +)); + +$a = random(5, 10); + +BEGIN_PGML +## Navigating WeBWorK ## +By now you've had experience with the "Next Problem" button. The "Previous Problem" button takes you to the previous +problem, and the "Problem List" button takes you to a listing of all problems in the set. + +At the bottom of a problem screen, you can see how many times you have attempted a problem, and how many attempts you +have left. Sometimes you also see whether or not partial credit is possible, and if so how much do you have. This +particular problem is lying about how many attempts you have used. How many attempts does it _say_ that you have used +so far? [_]{$a}{4} + +To the left of the screen there are some panels. The top panel lets you find your way to the following, although +depending on how your course is set up, any of these pages might not be available. +* *Assignments*: this is the "home" screen where you can see all of the assignments that have been assigned to you and +when they are due. +* *Account Settings*: at this page you may (or may not) be able to change things like your password, your email address, +how math is rendered for you, whether or not your previous answers will be visible to you, and whether or not you want +to use the math editing tool. Some of these things may not be available depending on how your instructor or institution +has configured WeBWorK. +* *Grades*: go here to see how you have performed in your assignments. +* *Achievements*: if your instructor is using Achievements, then you can earn badges and level up as you complete +homework problems. Go here to view your level and badges. Also, if you have earned any items that can be applied to +your homework sets, you will see them here. + +[@ MODES(HTML => 'When you are ready, click "Next Problem".', TeX => '') @] +END_PGML + +install_problem_grader(sub { + my ($result, $state) = std_problem_grader(@_); + my $time = time(); + my $open = $time >= $openDate && $time <= $dueDate; + my $submit = $inputs_ref->{submitAnswers}; + my $attempts = + $state->{num_of_correct_ans} + $state->{num_of_incorrect_ans}; + $attempts-- if $attempts && !$submit; + + my @msg = (); + push(@msg, "Your score was " . ($open ? "" : "not ") . "recorded.") + if $submit; + push(@msg, + "You have attempted this problem $a time" . ($a == 1 ? "." : "s.")); + push(@msg, "You have unlimited attempts remaining."); + if ($submit) { + if ($result->{score} == 1) { + push(@msg, "You received a score of 100% for this attempt."); + push(@msg, "Your overall recorded score is 100%."); + } else { + push(@msg, "Your answers are not yet fully correct."); + } + } + unless ($open) { + push(@msg, "The homework set is not yet open.") + if $time < $openDate; + push(@msg, "The homework set is closed.") if $time > $dueDate; + } + + $state->{state_summary_msg} = join('
', @msg); + return ($result, $state); +}); + +ENDDOCUMENT(); diff --git a/assets/pg/Student_Orientation/setStudent_Orientation.def b/assets/pg/Student_Orientation/setStudent_Orientation.def new file mode 100644 index 0000000000..37f6c23eaf --- /dev/null +++ b/assets/pg/Student_Orientation/setStudent_Orientation.def @@ -0,0 +1,112 @@ +assignmentType = default +openDate = 01/01/2024 at 12:00am +reducedScoringDate = 12/31/2045 at 11:59pm +dueDate = 12/31/2045 at 11:59pm +answerDate = 12/31/2045 at 11:59pm +enableReducedScoring = N +paperHeaderFile = defaultHeader +screenHeaderFile = defaultHeader +description = Student orientation assignment covering the basics of using WeBWorK +restrictProbProgression = 0 +emailInstructor = 0 + +problemListV2 +problem_start +problem_id = 1 +source_file = Student_Orientation/welcome.pg +value = 1 +max_attempts = -1 +showMeAnother = -1 +showHintsAfter = -2 +prPeriod = -1 +counts_parent_grade = 0 +att_to_open_children = 0 +problem_end +problem_start +problem_id = 2 +source_file = Student_Orientation/feedback.pg +value = 1 +max_attempts = -1 +showMeAnother = -1 +showHintsAfter = -2 +prPeriod = -1 +counts_parent_grade = 0 +att_to_open_children = 0 +problem_end +problem_start +problem_id = 3 +source_file = Student_Orientation/enteringMath.pg +value = 1 +max_attempts = -1 +showMeAnother = -1 +showHintsAfter = -2 +prPeriod = -1 +counts_parent_grade = 0 +att_to_open_children = 0 +problem_end +problem_start +problem_id = 4 +source_file = Student_Orientation/decimals.pg +value = 1 +max_attempts = -1 +showMeAnother = -1 +showHintsAfter = -2 +prPeriod = -1 +counts_parent_grade = 0 +att_to_open_children = 0 +problem_end +problem_start +problem_id = 5 +source_file = Student_Orientation/navigating.pg +value = 1 +max_attempts = -1 +showMeAnother = -1 +showHintsAfter = -2 +prPeriod = -1 +counts_parent_grade = 0 +att_to_open_children = 0 +problem_end +problem_start +problem_id = 6 +source_file = Student_Orientation/emailInstructor.pg +value = 1 +max_attempts = -1 +showMeAnother = -1 +showHintsAfter = -2 +prPeriod = -1 +counts_parent_grade = 0 +att_to_open_children = 0 +problem_end +problem_start +problem_id = 7 +source_file = Student_Orientation/mathInteraction.pg +value = 1 +max_attempts = -1 +showMeAnother = -1 +showHintsAfter = -2 +prPeriod = -1 +counts_parent_grade = 0 +att_to_open_children = 0 +problem_end +problem_start +problem_id = 8 +source_file = Student_Orientation/hardcopy.pg +value = 1 +max_attempts = -1 +showMeAnother = -1 +showHintsAfter = -2 +prPeriod = -1 +counts_parent_grade = 0 +att_to_open_children = 0 +problem_end +problem_start +problem_id = 9 +source_file = Student_Orientation/miscellaneous.pg +value = 1 +max_attempts = -1 +showMeAnother = -1 +showHintsAfter = -2 +prPeriod = -1 +counts_parent_grade = 0 +att_to_open_children = 0 +problem_end diff --git a/assets/pg/Student_Orientation/welcome.pg b/assets/pg/Student_Orientation/welcome.pg new file mode 100644 index 0000000000..0fff30fe71 --- /dev/null +++ b/assets/pg/Student_Orientation/welcome.pg @@ -0,0 +1,34 @@ +## DESCRIPTION +## Welcome to WeBWorK; Purpose of this Orientation +## ENDDESCRIPTION + +DOCUMENT(); + +loadMacros(qw( + PGstandard.pl + PGML.pl + PGcourse.pl +)); + +$isOpen = time() > $envir{openDate} && time() < $envir{dueDate}; +$instruction = + ($isOpen) + ? 'To get started, click in the answer blank, type the correct answer, and click the "Submit Answers" button.' + : 'This set is not currently open. You may want to ask your instructor to check if the open and close dates for ' + . 'this set are correctly set. In the meantime, you can click in the answer blank, type the correct answer, ' + . 'and click the "Check Answers" button to see if your answer is correct.'; + +BEGIN_PGML +## Welcome + +Welcome to the WeBWorK online homework platform. This orientation will familiarize you with basic features and usage of +WeBWorK. + +[$instruction]** + +[`2+2={}`][_]{Context("LimitedNumeric"), 4}{4} + +[@ MODES(HTML => 'When you are ready, click "Next Problem".', TeX => '') @] +END_PGML + +ENDDOCUMENT(); diff --git a/assets/stop-words-en.txt b/assets/stop-words-en.txt new file mode 100644 index 0000000000..cc09be2ec7 --- /dev/null +++ b/assets/stop-words-en.txt @@ -0,0 +1,1320 @@ +# Stop words from https://github.com/Alir3z4/stop-words. + +'ll +'tis +'twas +'ve +a +a's +able +ableabout +about +above +abroad +abst +accordance +according +accordingly +across +act +actually +ad +added +adj +adopted +ae +af +affected +affecting +affects +after +afterwards +ag +again +against +ago +ah +ahead +ai +ain't +aint +al +all +allow +allows +almost +alone +along +alongside +already +also +although +always +am +amid +amidst +among +amongst +amoungst +amount +an +and +announce +another +any +anybody +anyhow +anymore +anyone +anything +anyway +anyways +anywhere +ao +apart +apparently +appear +appreciate +appropriate +approximately +aq +ar +are +area +areas +aren +aren't +arent +arise +around +arpa +as +aside +ask +asked +asking +asks +associated +at +au +auth +available +aw +away +awfully +az +b +ba +back +backed +backing +backs +backward +backwards +bb +bd +be +became +because +become +becomes +becoming +been +before +beforehand +began +begin +beginning +beginnings +begins +behind +being +beings +believe +below +beside +besides +best +better +between +beyond +bf +bg +bh +bi +big +bill +billion +biol +bj +bm +bn +bo +both +bottom +br +brief +briefly +bs +bt +but +buy +bv +bw +by +bz +c +c'mon +c's +ca +call +came +can +can't +cannot +cant +caption +case +cases +cause +causes +cc +cd +certain +certainly +cf +cg +ch +changes +ci +ck +cl +clear +clearly +click +cm +cmon +cn +co +co. +com +come +comes +computer +con +concerning +consequently +consider +considering +contain +containing +contains +copy +corresponding +could +could've +couldn +couldn't +couldnt +course +cr +cry +cs +cu +currently +cv +cx +cy +cz +d +dare +daren't +darent +date +de +dear +definitely +describe +described +despite +detail +did +didn +didn't +didnt +differ +different +differently +directly +dj +dk +dm +do +does +doesn +doesn't +doesnt +doing +don +don't +done +dont +doubtful +down +downed +downing +downs +downwards +due +during +dz +e +each +early +ec +ed +edu +ee +effect +eg +eh +eight +eighty +either +eleven +else +elsewhere +empty +end +ended +ending +ends +enough +entirely +er +es +especially +et +et-al +etc +even +evenly +ever +evermore +every +everybody +everyone +everything +everywhere +ex +exactly +example +except +f +face +faces +fact +facts +fairly +far +farther +felt +few +fewer +ff +fi +fifteen +fifth +fifty +fify +fill +find +finds +fire +first +five +fix +fj +fk +fm +fo +followed +following +follows +for +forever +former +formerly +forth +forty +forward +found +four +fr +free +from +front +full +fully +further +furthered +furthering +furthermore +furthers +fx +g +ga +gave +gb +gd +ge +general +generally +get +gets +getting +gf +gg +gh +gi +give +given +gives +giving +gl +gm +gmt +gn +go +goes +going +gone +good +goods +got +gotten +gov +gp +gq +gr +great +greater +greatest +greetings +group +grouped +grouping +groups +gs +gt +gu +gw +gy +h +had +hadn't +hadnt +half +happens +hardly +has +hasn +hasn't +hasnt +have +haven +haven't +havent +having +he +he'd +he'll +he's +hed +hell +hello +help +hence +her +here +here's +hereafter +hereby +herein +heres +hereupon +hers +herself +herse” +hes +hi +hid +high +higher +highest +him +himself +himse” +his +hither +hk +hm +hn +home +homepage +hopefully +how +how'd +how'll +how's +howbeit +however +hr +ht +htm +html +http +hu +hundred +i +i'd +i'll +i'm +i've +i.e. +id +ie +if +ignored +ii +il +ill +im +immediate +immediately +importance +important +in +inasmuch +inc +inc. +indeed +index +indicate +indicated +indicates +information +inner +inside +insofar +instead +int +interest +interested +interesting +interests +into +invention +inward +io +iq +ir +is +isn +isn't +isnt +it +it'd +it'll +it's +itd +itll +its +itself +itse” +ive +j +je +jm +jo +join +jp +just +k +ke +keep +keeps +kept +keys +kg +kh +ki +kind +km +kn +knew +know +known +knows +kp +kr +kw +ky +kz +l +la +large +largely +last +lately +later +latest +latter +latterly +lb +lc +least +length +less +lest +let +let's +lets +li +like +liked +likely +likewise +line +little +lk +ll +long +longer +longest +look +looking +looks +low +lower +lr +ls +lt +ltd +lu +lv +ly +m +ma +made +mainly +make +makes +making +man +many +may +maybe +mayn't +maynt +mc +md +me +mean +means +meantime +meanwhile +member +members +men +merely +mg +mh +microsoft +might +might've +mightn't +mightnt +mil +mill +million +mine +minus +miss +mk +ml +mm +mn +mo +more +moreover +most +mostly +move +mp +mq +mr +mrs +ms +msie +mt +mu +much +mug +must +must've +mustn't +mustnt +mv +mw +mx +my +myself +myse” +mz +n +na +name +namely +nay +nc +nd +ne +near +nearly +necessarily +necessary +need +needed +needing +needn't +neednt +needs +neither +net +netscape +never +neverf +neverless +nevertheless +new +newer +newest +next +nf +ng +ni +nine +ninety +nl +no +no-one +nobody +non +none +nonetheless +noone +nor +normally +nos +not +noted +nothing +notwithstanding +novel +now +nowhere +np +nr +nu +null +number +numbers +nz +o +obtain +obtained +obviously +of +off +often +oh +ok +okay +old +older +oldest +om +omitted +on +once +one +one's +ones +only +onto +open +opened +opening +opens +opposite +or +ord +order +ordered +ordering +orders +org +other +others +otherwise +ought +oughtn't +oughtnt +our +ours +ourselves +out +outside +over +overall +owing +own +p +pa +page +pages +part +parted +particular +particularly +parting +parts +past +pe +per +perhaps +pf +pg +ph +pk +pl +place +placed +places +please +plus +pm +pmid +pn +point +pointed +pointing +points +poorly +possible +possibly +potentially +pp +pr +predominantly +present +presented +presenting +presents +presumably +previously +primarily +probably +problem +problems +promptly +proud +provided +provides +pt +put +puts +pw +py +q +qa +que +quickly +quite +qv +r +ran +rather +rd +re +readily +really +reasonably +recent +recently +ref +refs +regarding +regardless +regards +related +relatively +research +reserved +respectively +resulted +resulting +results +right +ring +ro +room +rooms +round +ru +run +rw +s +sa +said +same +saw +say +saying +says +sb +sc +sd +se +sec +second +secondly +seconds +section +see +seeing +seem +seemed +seeming +seems +seen +sees +self +selves +sensible +sent +serious +seriously +seven +seventy +several +sg +sh +shall +shan't +shant +she +she'd +she'll +she's +shed +shell +shes +should +should've +shouldn +shouldn't +shouldnt +show +showed +showing +shown +showns +shows +si +side +sides +significant +significantly +similar +similarly +since +sincere +site +six +sixty +sj +sk +sl +slightly +sm +small +smaller +smallest +sn +so +some +somebody +someday +somehow +someone +somethan +something +sometime +sometimes +somewhat +somewhere +soon +sorry +specifically +specified +specify +specifying +sr +st +state +states +still +stop +strongly +su +sub +substantially +successfully +such +sufficiently +suggest +sup +sure +sv +sy +system +sz +t +t's +take +taken +taking +tc +td +tell +ten +tends +test +text +tf +tg +th +than +thank +thanks +thanx +that +that'll +that's +that've +thatll +thats +thatve +the +their +theirs +them +themselves +then +thence +there +there'd +there'll +there're +there's +there've +thereafter +thereby +thered +therefore +therein +therell +thereof +therere +theres +thereto +thereupon +thereve +these +they +they'd +they'll +they're +they've +theyd +theyll +theyre +theyve +thick +thin +thing +things +think +thinks +third +thirty +this +thorough +thoroughly +those +thou +though +thoughh +thought +thoughts +thousand +three +throug +through +throughout +thru +thus +til +till +tip +tis +tj +tk +tm +tn +to +today +together +too +took +top +toward +towards +tp +tr +tried +tries +trillion +truly +try +trying +ts +tt +turn +turned +turning +turns +tv +tw +twas +twelve +twenty +twice +two +tz +u +ua +ug +uk +um +un +under +underneath +undoing +unfortunately +unless +unlike +unlikely +until +unto +up +upon +ups +upwards +us +use +used +useful +usefully +usefulness +uses +using +usually +uucp +uy +uz +v +va +value +various +vc +ve +versus +very +vg +vi +via +viz +vn +vol +vols +vs +vu +w +want +wanted +wanting +wants +was +wasn +wasn't +wasnt +way +ways +we +we'd +we'll +we're +we've +web +webpage +website +wed +welcome +well +wells +went +were +weren +weren't +werent +weve +wf +what +what'd +what'll +what's +what've +whatever +whatll +whats +whatve +when +when'd +when'll +when's +whence +whenever +where +where'd +where'll +where's +whereafter +whereas +whereby +wherein +wheres +whereupon +wherever +whether +which +whichever +while +whilst +whim +whither +who +who'd +who'll +who's +whod +whoever +whole +wholl +whom +whomever +whos +whose +why +why'd +why'll +why's +widely +width +will +willing +wish +with +within +without +won +won't +wonder +wont +words +work +worked +working +works +world +would +would've +wouldn +wouldn't +wouldnt +ws +www +x +y +ye +year +years +yes +yet +you +you'd +you'll +you're +you've +youd +youll +young +younger +youngest +your +youre +yours +yourself +yourselves +youve +yt +yu +z +za +zero +zm +zr + +# Additional specific stop words specific to POD and sample problem documentation. +constructor +description +error +errors +macro +macros +pod +podlink +problink +synopsis +usage +funciton +functions +method +methods +option +options +todo +fixme +_ diff --git a/assets/tex/webwork2.sty b/assets/tex/webwork2.sty index 7329447e25..fba9ab382d 100644 --- a/assets/tex/webwork2.sty +++ b/assets/tex/webwork2.sty @@ -1,5 +1,5 @@ \NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{webwork2}[2023/06/26 version 2.18] +\ProvidesPackage{webwork2}[2024/08/28 version 3] % packages that are used by webwork2 itself, probably by Hardcopy.pm \usepackage{path} @@ -85,23 +85,59 @@ % These macros declare copyright in the footer of the last page. -\newcommand{\webworkSetCopyrightFooter}{% -\@ifpackageloaded{fancyhdr}% + +% Define the footer components + +\newcommand{\webworkSetCopyrightFooterLeft}{% +\raisebox{-0.325cm}{\includegraphics[width=3cm]{webwork_logo.png}}% +} +\newcommand{\webworkSetCopyrightFooterCenter}{% +\small\sffamily Generated by WeBWorK, \copyright~The~WeBWorK~Project.% +} +\newcommand{\webworkSetCopyrightFooterRight}{% +\url{openwebwork.org}% +} + +% Define the macro that declares the copyright +% A format if fancyhdr is available +% A format if exam class is available +% Empty otherwise + +\AtBeginDocument{% +\newcommand{\webworkSetCopyrightFooter}{\relax} +\makeatletter% + +\@ifpackageloaded{fancyhdr}{% +\@ifpackageloaded{tcolorbox}{% +\renewcommand{\webworkSetCopyrightFooter}{% +\fancyfoot[L]{\webworkSetCopyrightFooterLeft}% +\fancyfoot[C]{\webworkSetCopyrightFooterCenter}% +\fancyfoot[R]{\webworkSetCopyrightFooterRight}% +\pagestyle{fancy}% +}% +}% {% -\fancyfoot[L]{\raisebox{-0.325cm}{\includegraphics[width=3cm]{webwork_logo.png}}}% -\fancyfoot[C]{\small\sffamily Generated by WeBWorK, \copyright~The~WeBWorK~Project.}% -\fancyfoot[R]{\url{openwebwork.org}}% +\renewcommand{\webworkSetCopyrightFooter}{% +\fancyfoot[L]{\webworkSetCopyrightFooterLeft}% +\fancyfoot[C]{\webworkSetCopyrightFooterCenter}% +\fancyfoot[R]{\webworkSetCopyrightFooterRight}% \pagestyle{fancy}% -\@ifpackageloaded{tcolorbox}{}{\clearpage}% +\clearpage% +}% }% {}% +}% + \@ifclassloaded{exam}% {% +\renewcommand{\webworkSetCopyrightFooter}{% \footer% -{\raisebox{-0.325cm}{\includegraphics[width=3cm]{webwork_logo.png}}}% -{\small\sffamily Generated by WeBWorK, \copyright~The~WeBWorK~Project.}% -{\url{openwebwork.org}}% +{\webworkSetCopyrightFooterLeft}% +{\webworkSetCopyrightFooterCenter}% +{\webworkSetCopyrightFooterRight}% \clearpage }% +}% {}% +\makeatother% } diff --git a/bin/OPL-update b/bin/OPL-update index cd8cf60666..6c36134b94 100755 --- a/bin/OPL-update +++ b/bin/OPL-update @@ -33,9 +33,6 @@ my $ce = WeBWorK::CourseEnvironment->new({ webwork_dir => $ENV{WEBWORK_ROOT} }); print "\nDownloading the latest OPL release.\n"; runScript("$ENV{WEBWORK_ROOT}/bin/download-OPL-metadata-release.pl"); -# Generate set definition list files. -runScript("$ENV{WEBWORK_ROOT}/bin/generate-OPL-set-def-lists.pl"); - if ($ce->{problemLibrary}{showLibraryLocalStats} || $ce->{problemLibrary}{showLibraryGlobalStats}) { print "\nUpdating Library Statistics.\n"; diff --git a/bin/OPL-update-legacy b/bin/OPL-update-legacy index ceda374cb3..39232570c9 100755 --- a/bin/OPL-update-legacy +++ b/bin/OPL-update-legacy @@ -28,7 +28,6 @@ use DBI; my $taxo={}; #my $taxsubs = []; - ### Data for creating the database tables my %OPLtables = ( @@ -539,8 +538,6 @@ if($canopenfile) { } #### End of taxonomy/taxonomy2 -use JSON; - #### Save the official taxonomy in json format my $webwork_htdocs = $ce->{webworkDirs}{htdocs}; my $file = "$webwork_htdocs/DATA/tagging-taxonomy.json"; @@ -924,7 +921,4 @@ if ($ce->{problemLibrary}{showLibraryLocalStats} || do $ENV{WEBWORK_ROOT}.'/bin/load-OPL-global-statistics.pl'; } -# Generate set definition list files. -do $ENV{WEBWORK_ROOT} . '/bin/generate-OPL-set-def-lists.pl'; - print "\nDone.\n"; diff --git a/bin/OPLUtils.pm b/bin/OPLUtils.pm index 6d041abe5e..18d4140695 100644 --- a/bin/OPLUtils.pm +++ b/bin/OPLUtils.pm @@ -1,4 +1,3 @@ - package OPLUtils; use base qw(Exporter); @@ -17,7 +16,7 @@ use warnings; use File::Find::Rule; use File::Basename; use open qw/:std :utf8/; -use JSON; +use Mojo::JSON qw(encode_json); our @EXPORT = (); our @EXPORT_OK = @@ -373,7 +372,7 @@ sub build_library_textbook_tree { sub writeJSONtoFile { my ($data, $filename) = @_; - my $json = JSON->new->utf8->encode($data); + my $json = encode_json($data); open my $fh, ">", $filename or die "Cannot open $filename"; print $fh $json; close $fh; diff --git a/bin/addcourse b/bin/addcourse index 3a545090ed..42ea11ac5d 100755 --- a/bin/addcourse +++ b/bin/addcourse @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME @@ -32,11 +18,6 @@ be granted professor privileges. =over -=item B<--db-layout>=I - -The specified database layout will be used in place of the default specified in -F. - =item B<--users>=I The users listed in the comma-separated text file I will be added to the @@ -77,29 +58,26 @@ BEGIN { use lib "$ENV{WEBWORK_ROOT}/lib"; use WeBWorK::CourseEnvironment; - -# Grab course environment (by reading webwork2/conf/defaults.config) -my $ce = WeBWorK::CourseEnvironment->new; - -use WeBWorK::DB; use WeBWorK::File::Classlist; use WeBWorK::Utils qw(runtime_use cryptPassword); use WeBWorK::Utils::CourseManagement qw(addCourse); +use WeBWorK::File::Classlist qw(parse_classlist); +use WeBWorK::DB::Record::User; +use WeBWorK::DB::Record::Password; +use WeBWorK::DB::Record::PermissionLevel; sub usage_error { warn "@_\n"; warn "usage: $0 [options] COURSEID\n"; warn "Options:\n"; - warn " [--db-layout=LAYOUT]\n"; warn " [--users=FILE [--professors=USERID[,USERID]...] ]\n"; exit; } -my ($dbLayout, $users, $templates_from) = ('', '', ''); +my ($users, $templates_from) = ('', ''); my @professors; GetOptions( - "db-layout=s" => \$dbLayout, "users=s" => \$users, "professors=s" => \@professors, "templates-from=s" => \$templates_from, @@ -109,33 +87,16 @@ my $courseID = shift; usage_error('The COURSEID must be provided.') unless $courseID; -$ce = WeBWorK::CourseEnvironment->new({ courseName => $courseID }); +my $ce = WeBWorK::CourseEnvironment->new({ courseName => $courseID }); die "Aborting addcourse: Course ID cannot exceed $ce->{maxCourseIdLength} characters." if length($courseID) > $ce->{maxCourseIdLength}; -if ($dbLayout) { - die "Database layout $dbLayout does not exist in the course environment.", - " (It must be defined in defaults.config.)\n" - unless exists $ce->{dbLayouts}{$dbLayout}; -} else { - $dbLayout = $ce->{dbLayoutName}; -} - usage_error("Can't specify --professors without also specifying --users.") if @professors && !$users; my @users; if ($users) { - # This is a hack to create records without bringing up a DB object - my $userClass = $ce->{dbLayouts}{$dbLayout}{user}{record}; - my $passwordClass = $ce->{dbLayouts}{$dbLayout}{password}{record}; - my $permissionClass = $ce->{dbLayouts}{$dbLayout}{permission}{record}; - - runtime_use($userClass); - runtime_use($passwordClass); - runtime_use($permissionClass); - my @classlist = parse_classlist($users); for my $record (@classlist) { my %record = %$record; @@ -144,14 +105,17 @@ if ($users) { # Set the default status if the status field is not set. $record{status} = $ce->{statuses}{Enrolled}{abbrevs}[0] unless $record{status}; - # Set the password if the password field is empty. - if (!defined $record{password} || $record{password} !~ /\S/) { - if (defined $record{student_id} && $record{student_id} =~ /\S/) { - # Use the student ID if it is non-empty. - $record{password} = cryptPassword($record{student_id}); - } else { - # An empty password field in the database disables password login. - $record{password} = ''; + # Determine what to use for the password (if anything). + if (!$record{password}) { + if (defined $record{unencrypted_password} && $record{unencrypted_password} =~ /\S/) { + $record{password} = cryptPassword($record{unencrypted_password}); + } elsif ($ce->{fallback_password_source} + && { user_id => 1, first_name => 1, last_name => 1, student_id => 1 } + ->{ $ce->{fallback_password_source} } + && $record{ $ce->{fallback_password_source} } + && $record{ $ce->{fallback_password_source} } =~ /\S/) + { + $record{password} = cryptPassword($record{ $ce->{fallback_password_source} }); } } @@ -164,9 +128,9 @@ if ($users) { push @users, [ - $userClass->new(%record), - $passwordClass->new(user_id => $user_id, password => $record{password}), - $permissionClass->new( + WeBWorK::DB::Record::User->new(%record), + WeBWorK::DB::Record::Password->new(user_id => $user_id, password => $record{password}), + WeBWorK::DB::Record::PermissionLevel->new( user_id => $user_id, permission => defined $professors{$user_id} ? $ce->{userRoles}{professor} @@ -181,19 +145,12 @@ if ($users) { } my %optional_arguments; -if ($templates_from ne "") { - $optional_arguments{templatesFrom} = $templates_from; +if ($templates_from) { + $optional_arguments{copyFrom} = $templates_from; + $optional_arguments{copyTemplatesHtml} = 1; } -eval { - addCourse( - courseID => $courseID, - ce => $ce, - courseOptions => { dbLayoutName => $dbLayout }, - users => \@users, - %optional_arguments, - ); -}; +eval { addCourse(courseID => $courseID, ce => $ce, users => \@users, %optional_arguments,); }; die "$@\n" if $@; diff --git a/bin/change_user_id b/bin/change_user_id index 793fef2ef2..2ec44a5034 100755 --- a/bin/change_user_id +++ b/bin/change_user_id @@ -30,7 +30,6 @@ use lib "$ENV{WEBWORK_ROOT}/lib"; use WeBWorK::CourseEnvironment; use WeBWorK::DB; -use WeBWorK::Utils qw(runtime_use readFile); use Data::Dumper; if((scalar(@ARGV) != 3)) { @@ -48,7 +47,7 @@ my $ce = WeBWorK::CourseEnvironment->new({ courseName => $courseID }); -my $db = new WeBWorK::DB($ce->{dbLayout}); +my $db = WeBWorK::DB->new($ce); die "Error: $old_user_id does not exist!" unless $db->existsUser($old_user_id); unless($db->existsUser($new_user_id)) { @@ -70,7 +69,6 @@ unless($db->existsPermissionLevel($new_user_id)) { $db->addPermissionLevel($permission); } - my @old_user_sets = $db->listUserSets($old_user_id); foreach(@old_user_sets) { my $set_id = $_; diff --git a/bin/check_database_charsets.pl b/bin/check_database_charsets.pl deleted file mode 100755 index c46feb1475..0000000000 --- a/bin/check_database_charsets.pl +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env perl - -my $host = $ENV{WEBWORK_DB_HOST}; -my $port = $ENV{WEBWORK_DB_PORT}; -my $database_name = $ENV{WEBWORK_DB_NAME}; -my $database_user = $ENV{WEBWORK_DB_USER}; -my $database_password = $ENV{WEBWORK_DB_PASSWORD}; - -print - `mysql -u $database_user -p$database_password $database_name -h $host -e "SHOW VARIABLES WHERE Variable_name LIKE \'character\_set\_%\' OR Variable_name LIKE \'collation%\' or Variable_name LIKE \'init_connect\' "`; diff --git a/bin/check_latex b/bin/check_latex index 447eae10b9..8c5a6ee567 100755 --- a/bin/check_latex +++ b/bin/check_latex @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME @@ -27,9 +13,17 @@ use feature 'say'; BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use YAML::XS qw(LoadFile); + use Env qw(WEBWORK_ROOT PG_ROOT); - $WEBWORK_ROOT = curfile->dirname->dirname; + $WEBWORK_ROOT = curfile->dirname->dirname->to_string; + + # Load the configuration file to obtain the PG root directory. + my $config_file = "$WEBWORK_ROOT/conf/webwork2.mojolicious.yml"; + $config_file = "$WEBWORK_ROOT/conf/webwork2.mojolicious.dist.yml" unless -e $config_file; + my $config = LoadFile($config_file); + + $PG_ROOT = $config->{pg_dir}; } use File::Temp qw(tempdir); @@ -46,20 +40,20 @@ my $temp_dir = eval { tempdir('check_latex_XXXXXXXX') }; die $@ if $@; -my $pdflatex_cmd = +my $latex_cmd = "cd $temp_dir && " . "TEXINPUTS=$ENV{WEBWORK_ROOT}/bin:" . shell_quote($ce->{webworkDirs}{assetsTex}) . ':' - . shell_quote("$ce->{pg}{directories}{assetsTex}") . ': ' - . $ce->{externalPrograms}{pdflatex} + . shell_quote($ce->{pg}{directories}{assetsTex}) . ': ' + . $ce->{externalPrograms}{latex2pdf} . ' -interaction nonstopmode check_latex_article.tex > check_latex.nfo 2>&1 &&' . "TEXINPUTS=$ENV{WEBWORK_ROOT}/bin:" . shell_quote($ce->{webworkDirs}{assetsTex}) . ':' - . shell_quote("$ce->{pg}{directories}{assetsTex}") . ': ' - . $ce->{externalPrograms}{pdflatex} + . shell_quote($ce->{pg}{directories}{assetsTex}) . ': ' + . $ce->{externalPrograms}{latex2pdf} . ' -interaction nonstopmode check_latex_exam.tex >> check_latex.nfo 2>&1'; -if ((system $pdflatex_cmd) >> 8) { +if ((system $latex_cmd) >> 8) { if (open(my $fh, '<', "$temp_dir/check_latex.nfo")) { local $/; my $nfo = <$fh>; diff --git a/bin/check_latex_article.tex b/bin/check_latex_article.tex index 56c1ae8270..76036857ee 100644 --- a/bin/check_latex_article.tex +++ b/bin/check_latex_article.tex @@ -1,18 +1,3 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% WeBWorK Online Homework Delivery System -% Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -% -% This program is free software; you can redistribute it and/or modify it under -% the terms of either: (a) the GNU General Public License as published by the -% Free Software Foundation; either version 2, or (at your option) any later -% version, or (b) the "Artistic License" which comes with this package. -% -% 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. See either the GNU General Public License or the -% Artistic License for more details. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - \documentclass[10pt]{article} \usepackage{webwork2} diff --git a/bin/check_latex_exam.tex b/bin/check_latex_exam.tex index 0a2cec7231..bd48c01ef5 100644 --- a/bin/check_latex_exam.tex +++ b/bin/check_latex_exam.tex @@ -1,18 +1,3 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% WeBWorK Online Homework Delivery System -% Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -% -% This program is free software; you can redistribute it and/or modify it under -% the terms of either: (a) the GNU General Public License as published by the -% Free Software Foundation; either version 2, or (at your option) any later -% version, or (b) the "Artistic License" which comes with this package. -% -% 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. See either the GNU General Public License or the -% Artistic License for more details. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - \documentclass[10pt]{exam} \usepackage{webwork2} diff --git a/bin/check_modules.pl b/bin/check_modules.pl index d36fb01b74..494751d903 100755 --- a/bin/check_modules.pl +++ b/bin/check_modules.pl @@ -1,72 +1,39 @@ #!/usr/bin/env perl -# - -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME -check_modules.pl - check to ensure that all applications and perl modules are installed. +check_modules.pl - Check to ensure that applications and perl modules needed by +webwork2 are installed. =head1 SYNOPSIS check_modules.pl [options] Options: - -m|--modules Lists the perl modules needed to be installed. - -p|--programs Lists the programs/applications that are needed. - -A|--all checks both programs and modules (Default if -m or -p is not selected) + -m|--modules Check that the perl modules needed by webwork2 can be loaded. + -p|--programs Check that the programs needed by webwork2 exist. + +Both programs and modules are checked if no options are given. =head1 DESCRIPTION -Lists all needed applications for webwork as well as a perl modules. +Checks that modules needed by webwork2 can be loaded and are at the sufficient +version, and that applications needed by webwork2 exist. =cut use strict; use warnings; use version; +use feature 'say'; + use Getopt::Long qw(:config bundling); use Pod::Usage; -my @applicationsList = qw( - convert - curl - dvisvgm - mkdir - mv - mysql - node - tar - git - gzip - latex - pandoc - pdf2svg - pdflatex - dvipng - giftopnm - ppmtopgm - pnmtops - pnmtopng - pngtopnm -); - my @modulesList = qw( + Archive::Tar Archive::Zip - Array::Utils + Archive::Zip::SimpleZip Benchmark Carp Class::Accessor @@ -79,17 +46,16 @@ =head1 DESCRIPTION Date::Format Date::Parse DateTime - DBD::mysql DBI Digest::MD5 Digest::SHA Email::Address::XS - Email::Sender::Simple Email::Sender::Transport::SMTP Email::Stuffer Errno Exception::Class File::Copy + File::Copy::Recursive File::Fetch File::Find File::Find::Rule @@ -99,21 +65,18 @@ =head1 DESCRIPTION File::Temp Future::AsyncAwait GD + GD::Barcode::QRcode Getopt::Long Getopt::Std HTML::Entities - HTML::Tagset - HTML::Template HTTP::Async IO::File - IO::Socket::SSL Iterator Iterator::Util - JSON - JSON::MaybeXS Locale::Maketext::Lexicon Locale::Maketext::Simple LWP::Protocol::https + MIME::Base32 MIME::Base64 Math::Random::Secure Minion @@ -122,29 +85,24 @@ =head1 DESCRIPTION Mojolicious::Plugin::NotYAMLConfig Mojolicious::Plugin::RenderFile Net::IP - Net::LDAPS Net::OAuth Net::OAuth2 Net::SMTP Net::SSLeay Opcode - PadWalker Pandoc - Path::Class Perl::Tidy PHP::Serialization Pod::Simple::Search Pod::Simple::XHTML Pod::Usage Pod::WSDL - Safe Scalar::Util SOAP::Lite Socket - Statistics::R::IO + SQL::Abstract String::ShellQuote SVG - Template Text::CSV Text::Wrap Tie::IxHash @@ -158,8 +116,8 @@ =head1 DESCRIPTION XML::Parser::EasyTree XML::Writer YAML::XS - XML::Simple - App::Genpass + XML::Simple + App::Genpass HTTP::Async TheSchwartz Crypt::JWT @@ -170,115 +128,147 @@ =head1 DESCRIPTION 'Future::AsyncAwait' => 0.52, 'IO::Socket::SSL' => 2.007, 'LWP::Protocol::https' => 6.06, - 'Mojolicious' => 9.22, - 'Net::SSLeay' => 1.46, - 'Perl::Tidy' => 20220613 + 'Mojolicious' => 9.34, + 'SQL::Abstract' => 2.000000 +); + +my @programList = qw( + convert + curl + mkdir + mv + mysql + mysqldump + node + npm + tar + git + gzip + latex + latex2pdf + pandoc + dvipng ); -my ($test_programs, $test_modules, $show_help); -my $test_all = 1; +my ($test_modules, $test_programs, $show_help); GetOptions( 'm|modules' => \$test_modules, 'p|programs' => \$test_programs, - 'A|all' => \$test_all, 'h|help' => \$show_help, ); pod2usage(2) if $show_help; -my @PATH = split(/:/, $ENV{PATH}); - -if ($test_all or $test_programs) { - check_apps(@applicationsList); -} +$test_modules = $test_programs = 1 unless $test_programs || $test_modules; -if ($test_all or $test_modules) { - check_modules(@modulesList); -} - -sub check_apps { - my @applicationsList = @_; - print "\nChecking your \$PATH for executables required by WeBWorK...\n"; - print "\$PATH="; - print join("\n", map(" $_", @PATH)), "\n\n"; - - foreach my $app (@applicationsList) { - my $found = which($app); - if ($found) { - print " $app found at $found\n"; - } else { - print "** $app not found in \$PATH\n"; - } - } +my @PATH = split(/:/, $ENV{PATH}); - ## Check that the node version is sufficient. - my $node_version_str = qx/node -v/; - my ($node_version) = $node_version_str =~ m/v(\d+)\./; - - if ($node_version != 16) { - print "\n\n**The version of node should be 16. You have version $node_version"; - } -} +check_modules() if $test_modules; +say '' if $test_modules && $test_programs; +check_apps() if $test_programs; sub which { - my $app = shift; - foreach my $path (@PATH) { - return "$path/$app" if -e "$path/$app"; + my $program = shift; + for my $path (@PATH) { + return "$path/$program" if -e "$path/$program"; } + return; } sub check_modules { - my @modulesList = @_; + say "Checking for modules required by WeBWorK..."; - print "\nChecking your \@INC for modules required by WeBWorK...\n"; - my @inc = @INC; - print "\@INC="; - print join("\n", map(" $_", @inc)), "\n\n"; + my $moduleNotFound = 0; - no strict 'refs'; + my $checkModule = sub { + my $module = shift; - foreach my $module (@modulesList) { + no strict 'refs'; eval "use $module"; if ($@) { - my $file = $module; - $file =~ s|::|/|g; - $file .= ".pm"; + $moduleNotFound = 1; + my $file = ($module =~ s|::|/|gr) . '.pm'; if ($@ =~ /Can't locate $file in \@INC/) { - print "** $module not found in \@INC\n"; + say "** $module not found in \@INC"; } else { - print "** $module found, but failed to load: $@"; + say "** $module found, but failed to load: $@"; } } elsif (defined($moduleVersion{$module}) && version->parse(${ $module . '::VERSION' }) < version->parse($moduleVersion{$module})) { - print "** $module found, but not version $moduleVersion{$module} or better\n"; + $moduleNotFound = 1; + say "** $module found, but not version $moduleVersion{$module} or better"; } else { - print " $module found and loaded\n"; + say " $module found and loaded"; } + use strict 'refs'; + }; + + for my $module (@modulesList) { + $checkModule->($module); + } + + if ($moduleNotFound) { + say ''; + say 'Some requred modules were not found, could not be loaded, or were not at the sufficient version.'; + say 'Exiting as this is required to check the database driver and programs.'; + exit 0; } - checkSQLabstract(); + + say ''; + say 'Checking for the database driver required by WeBWorK...'; + my $ce = loadCourseEnvironment(); + my $driver = $ce->{database_driver} =~ /^mysql$/i ? 'DBD::mysql' : 'DBD::MariaDB'; + say "Configured to use $driver in site.conf"; + $checkModule->($driver); + + return; } -## this is specialized code to check for either SQL::Abstract or SQL::Abstract::Classic - -sub checkSQLabstract { - print "\n checking for SQL::Abstract\n\n"; - eval "use SQL::Abstract"; - my $sql_abstract = not($@); - my $sql_abstract_version = $SQL::Abstract::VERSION if $sql_abstract; - - eval "use SQL::Abstract::Classic"; - my $sql_abstract_classic = not($@); - - if ($sql_abstract_classic) { - print qq/ You have SQL::Abstract::Classic installed. This package will be used if either - the installed version of SQL::Abstract is version > 1.87 or if that package is not installed.\n/; - } elsif ($sql_abstract && $sql_abstract_version <= 1.87) { - print "You have version $sql_abstract_version of SQL::Abstract installed. This will be used\n"; - } else { - print qq/You need either SQL::Abstract version <= 1.87 or need to install SQL::Abstract::Classic. - If you are using cpan or cpanm, it is recommended to install SQL::Abstract::Classic.\n/; +sub check_apps { + my $ce = loadCourseEnvironment(); + + say 'Checking external programs required by WeBWorK...'; + + push(@programList, $ce->{pg}{specialPGEnvironmentVars}{latexImageSVGMethod}); + + for my $program (@programList) { + if ($ce->{externalPrograms}{$program}) { + # Remove command line arguments (for latex and latex2pdf). + my $executable = $ce->{externalPrograms}{$program} =~ s/ .*$//gr; + if (-e $executable) { + say " $executable found for $program"; + } else { + say "** $executable not found for $program"; + } + } else { + my $found = which($program); + if ($found) { + say " $found found for $program"; + } else { + say "** $program not found in \$PATH"; + } + } } + + # Check that the node version is sufficient. + my $node_version_str = qx/node -v/; + my ($node_version) = $node_version_str =~ m/v(\d+)\./; + + say "\n**The version of node should be at least 18. You have version $node_version." + if $node_version < 18; + + return; +} + +sub loadCourseEnvironment { + eval 'require Mojo::File'; + die "Unable to load Mojo::File: $@" if $@; + my $webworkRoot = Mojo::File->curfile->dirname->dirname; + push @INC, "$webworkRoot/lib"; + eval 'require WeBWorK::CourseEnvironment'; + die "Unable to load WeBWorK::CourseEnvironment: $@" if $@; + return WeBWorK::CourseEnvironment->new({ webwork_dir => $webworkRoot }); } 1; diff --git a/bin/crypt_passwords_in_classlist.pl b/bin/crypt_passwords_in_classlist.pl index 10a6d71cde..1faa331daa 100755 --- a/bin/crypt_passwords_in_classlist.pl +++ b/bin/crypt_passwords_in_classlist.pl @@ -1,55 +1,46 @@ #!/usr/bin/env perl -use open IO => ':encoding(UTF-8)'; +use strict; +use warnings; +use feature 'say'; -# ================================================================== +use Mojo::File qw(curfile path); -# 2 subroutines copied from lib/WeBWorK/Utils.pm from WW 2.16 version +BEGIN { + use Env qw(WEBWORK_ROOT); + $WEBWORK_ROOT = curfile->dirname->dirname; +} -sub cryptPassword($) { - my ($clearPassword) = @_; - #Use an SHA512 salt with 16 digits - my $salt = '$6$'; - for (my $i = 0; $i < 16; $i++) { - $salt .= ('.', '/', '0' .. '9', 'A' .. 'Z', 'a' .. 'z')[ rand 64 ]; - } +use lib "$ENV{WEBWORK_ROOT}/lib"; + +use WeBWorK::Utils qw(cryptPassword); +use WeBWorK::File::Classlist qw(parse_classlist write_classlist); - my $cryptPassword = crypt(trim_spaces($clearPassword), $salt); - return $cryptPassword; +unless (@ARGV == 1) { + say 'Usage: crypt_passwords_in_classlist.pl filename'; + exit 0; } -## Utility function to trim whitespace off the start and end of its input -sub trim_spaces { - my $in = shift; - return '' unless $in; # skip blank spaces - $in =~ s/^\s*|\s*$//g; - return ($in); +my $infile = shift; +my $outfile = "crypted_$infile"; + +if (-e $outfile) { + print qq{The file "$outfile" exists. Do you want to proceed and overwrite "$outfile"? (Y/n) }; + my $input = <>; + chomp $input; + unless ($input eq 'Y') { + say 'Aborting.'; + exit 0; + } } -# ================================================================== -my $inputfile = shift; -my $outfile = "crypted_" . $inputfile; - -if (-e $inputfile && -r $inputfile) { - my $fh; - my $outfh; - open(my $fh, "<", $inputfile) or die "cannot open $inputfile"; - open(my $outfh, ">", $outfile) or die "cannot open $outfile"; - my $line; - my @fields; - while ($line = <$fh>) { - if ($line =~ /^#/) { - # Do not process comment lines - print $outfh $line; - } else { - @fields = split(",", $line); - $fields[9] = cryptPassword($fields[9]); - print $outfh join(",", @fields); - } +if (-e $infile && -r $infile) { + my @classlist = parse_classlist($infile); + for (@classlist) { + $_->{password} = cryptPassword($_->{password} || $_->{user_id}); } - close $outfh or die "cannot close $outfile"; - close $fh or die "cannot close $inputfile"; - print "Output is in the file $outfile\n"; + write_classlist($outfile, @classlist); + say qq{Output written to the file "$outfile".}; } else { - print "Usage: crypt_passwords_in_classlist.pl filename"; + say qq{The file "$infile" is does not exist or is not readable.}; } diff --git a/bin/delcourse b/bin/delcourse index 128e283bd8..a04871be27 100755 --- a/bin/delcourse +++ b/bin/delcourse @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME diff --git a/bin/dev_scripts/PODtoHTML.pm b/bin/dev_scripts/PODtoHTML.pm index a867b81cd3..a922314011 100644 --- a/bin/dev_scripts/PODtoHTML.pm +++ b/bin/dev_scripts/PODtoHTML.pm @@ -1,18 +1,3 @@ -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - package PODtoHTML; use strict; @@ -23,19 +8,29 @@ use Pod::Simple::Search; use Mojo::Template; use Mojo::DOM; use Mojo::Collection qw(c); -use File::Path qw(make_path); -use File::Basename qw(dirname); +use File::Path qw(make_path); +use File::Basename qw(dirname); use IO::File; use POSIX qw(strftime); use WeBWorK::Utils::PODParser; our @sections = ( - bin => 'Scripts', - conf => 'Config Files', doc => 'Documentation', + bin => 'Scripts', + macros => 'Macros', lib => 'Libraries', - macros => 'Macros' +); +our %macro_names = ( + answers => 'Answers', + contexts => 'Contexts', + core => 'Core', + deprecated => 'Deprecated', + graph => 'Graph', + math => 'Math', + misc => 'Miscellaneous', + parsers => 'Parsers', + ui => 'User Interface' ); sub new { @@ -52,6 +47,7 @@ sub new { idx => {}, section_hash => $section_hash, section_order => $section_order, + macros_hash => {}, }; return bless $self, $class; } @@ -131,7 +127,14 @@ sub update_index { $subdir =~ s|/.*$||; my $idx = $self->{idx}; my $sections = $self->{section_hash}; - if (exists $sections->{$subdir}) { + if ($subdir eq 'macros') { + $idx->{macros} = []; + if ($pod_name =~ m!^(.+)/(.+)$!) { + push @{ $self->{macros_hash}{$1} }, [ $html_rel_path, $2 ]; + } else { + push @{ $idx->{doc} }, [ $html_rel_path, $pod_name ]; + } + } elsif (exists $sections->{$subdir}) { push @{ $idx->{$subdir} }, [ $html_rel_path, $pod_name ]; } else { warn "no section for subdir '$subdir'\n"; @@ -152,6 +155,9 @@ sub write_index { pod_index => $self->{idx}, sections => $self->{section_hash}, section_order => $self->{section_order}, + macros => $self->{macros_hash}, + macros_order => [ sort keys %{ $self->{macros_hash} } ], + macro_names => \%macro_names, date => strftime('%a %b %e %H:%M:%S %Z %Y', localtime) } ); diff --git a/bin/dev_scripts/generate-ww-pg-pod.pl b/bin/dev_scripts/generate-ww-pg-pod.pl index dca0f6e2fd..f1591ae485 100755 --- a/bin/dev_scripts/generate-ww-pg-pod.pl +++ b/bin/dev_scripts/generate-ww-pg-pod.pl @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME @@ -65,9 +51,9 @@ =head1 DESCRIPTION use Mojo::Template; use IO::File; use File::Copy; -use File::Path qw(make_path remove_tree); +use File::Path qw(make_path remove_tree); use File::Basename qw(dirname); -use Cwd qw(abs_path); +use Cwd qw(abs_path); use lib dirname(dirname(dirname(__FILE__))) . '/lib'; use lib dirname(__FILE__); diff --git a/bin/dev_scripts/pod-templates/category-index.mt b/bin/dev_scripts/pod-templates/category-index.mt index 78c9bb3724..a5d980d599 100644 --- a/bin/dev_scripts/pod-templates/category-index.mt +++ b/bin/dev_scripts/pod-templates/category-index.mt @@ -23,18 +23,44 @@ % - % my ($index, $content) = ('', ''); + % my ($index, $macro_index, $content, $macro_content) = ('', '', '', ''); + % for my $macro (@$macros_order) { + % my $new_index = begin + <%= $macro_names->{$macro} // $macro %> + % end + % $macro_index .= $new_index->(); + % my $new_content = begin +

<%= $macro_names->{$macro} // $macro %>

+
+ % for my $file (sort { $a->[1] cmp $b->[1] } @{ $macros->{$macro} }) { + <%= $file->[1] %> + % } +
+ % end + % $macro_content .= $new_content->(); + % } % for my $section (@$section_order) { % next unless defined $pod_index->{$section}; % my $new_index = begin <%= $sections->{$section} %> + % if ($section eq 'macros') { + + % } % end % $index .= $new_index->(); % my $new_content = begin

<%= $sections->{$section} %>

- % for my $file (sort { $a->[1] cmp $b->[1] } @{ $pod_index->{$section} }) { - <%= $file->[1] %> + % if ($section eq 'macros') { + <%= $macro_content =%> + % } else { + % for my $file (sort { $a->[1] cmp $b->[1] } @{ $pod_index->{$section} }) { + + <%= $file->[1] %> + + % } % }
% end diff --git a/bin/dev_scripts/run-perltidy.pl b/bin/dev_scripts/run-perltidy.pl index f6df90cae2..37c9463353 100755 --- a/bin/dev_scripts/run-perltidy.pl +++ b/bin/dev_scripts/run-perltidy.pl @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME @@ -28,7 +14,7 @@ =head1 DESCRIPTION =head1 OPTIONS -For this script to work the the .perltidyrc file in the webwork2 root directory +For this script to work the .perltidyrc file in the webwork2 root directory must be readable. Note that the webwork2 root directory is automatically detected. @@ -63,9 +49,8 @@ =head1 OPTIONS my $webwork_root = curfile->dirname->dirname->dirname; -die "Version 20220613 or newer of perltidy is required for this script.\n" - . "The installed version is $Perl::Tidy::VERSION.\n" - unless $Perl::Tidy::VERSION >= 20220613; +die "Version 20240903 of perltidy is required for this script.\nThe installed version is $Perl::Tidy::VERSION.\n" + unless $Perl::Tidy::VERSION == 20240903; die "The .perltidyrc file in the webwork root directory is not readable.\n" unless -r "$webwork_root/.perltidyrc"; diff --git a/bin/dev_scripts/update-copyright b/bin/dev_scripts/update-copyright deleted file mode 100755 index fc8b30dbe8..0000000000 --- a/bin/dev_scripts/update-copyright +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash - -YEAR=$(date +%Y) - -function replace_license -{ - perl -i -0pe 'BEGIN{ undef $/; } -s{([#%/*]*) WeBWorK Online Homework Delivery System\s* -.*? -[ #%/*]* This program is free software; you can redistribute it and/or modify it under\s* -[ #%/*]* the terms of either: \(a\) the GNU General Public License as published by the\s* -[ #%/*]* Free Software Foundation; either version 2, or \(at your option\) any later\s* -[ #%/*]* version, or \(b\) the "Artistic License" which comes with this package.\s* -[#%/*]*\s* -[ #%/*]* This program is distributed in the hope that it will be useful, but WITHOUT\s* -[ #%/*]* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\s* -[ #%/*]* FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the\s* -([ #%/*]*) Artistic License for more details.\s* -}{$1 WeBWorK Online Homework Delivery System -$2 Copyright © 2000-'$YEAR' The WeBWorK Project, https://github.com/openwebwork -$2 -$2 This program is free software; you can redistribute it and/or modify it under -$2 the terms of either: (a) the GNU General Public License as published by the -$2 Free Software Foundation; either version 2, or (at your option) any later -$2 version, or (b) the "Artistic License" which comes with this package. -$2 -$2 This program is distributed in the hope that it will be useful, but WITHOUT -$2 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -$2 FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the -$2 Artistic License for more details. -}ms' $1 -} - -shopt -s extglob globstar nullglob - -for FILE in ./**/* -do - if [[ ! -L $FILE && -f $FILE && -w $FILE ]] - then - replace_license $FILE - fi -done diff --git a/bin/dev_scripts/update-localization-files b/bin/dev_scripts/update-localization-files index 37c62152ee..8ad0cc1c11 100755 --- a/bin/dev_scripts/update-localization-files +++ b/bin/dev_scripts/update-localization-files @@ -39,8 +39,8 @@ do esac done -if [ -z "$WEBWORK_ROOT" ] || [ -z "$PG_ROOT" ]; then - echo >&2 "You need to set both the WEBWORK_ROOT and PG_ROOT environment variables. Aborting." +if [ -z "$WEBWORK_ROOT" ]; then + echo >&2 "You need to set the WEBWORK_ROOT environment variable. Aborting." exit 1 fi @@ -53,10 +53,9 @@ LOCDIR=$WEBWORK_ROOT/lib/WeBWorK/Localize cd $LOCDIR -echo "Updating $WEBWORK_ROOT/webwork2.pot" +echo "Updating $LOCDIR/webwork2.pot" -xgettext.pl -o webwork2.pot -D $WEBWORK_ROOT/lib -D $PG_ROOT/lib -D $PG_ROOT/macros -D $WEBWORK_ROOT/templates \ - $WEBWORK_ROOT/conf/defaults.config $WEBWORK_ROOT/conf/LTIConfigValues.config +xgettext.pl -o webwork2.pot -D $WEBWORK_ROOT/lib -D $WEBWORK_ROOT/templates if $UPDATE_PO; then find $LOCDIR -name '*.po' -exec bash -c "echo \"Updating {}\"; msgmerge -qUN {} webwork2.pot" \; diff --git a/bin/dev_scripts/webwork2-morbo b/bin/dev_scripts/webwork2-morbo index f951d94c35..dde0b82708 100755 --- a/bin/dev_scripts/webwork2-morbo +++ b/bin/dev_scripts/webwork2-morbo @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =encoding utf8 @@ -87,9 +73,10 @@ push(@watch, "$webwork_root/lib", "$webwork_root/templates", "$webwork_root/htdocs/js", "$webwork_root/htdocs/themes", "$webwork_root/conf"); -# Add the pg lib and pg htdocs directory if they are readable. -push(@watch, "$config->{pg_dir}/lib") if -r "$config->{pg_dir}/lib"; -push(@watch, "$config->{pg_dir}/htdocs") if -r "$config->{pg_dir}/htdocs"; +# Add the pg lib and pg htdocs directory and the PG.pl macro if they are readable. +push(@watch, "$config->{pg_dir}/lib") if -r "$config->{pg_dir}/lib"; +push(@watch, "$config->{pg_dir}/htdocs") if -r "$config->{pg_dir}/htdocs"; +push(@watch, "$config->{pg_dir}/macros/PG.pl") if -r "$config->{pg_dir}/macros/PG.pl"; my $morbo = Mojo::Server::Morbo->new(silent => !$verbose); $morbo->daemon->listen(\@listen) if @listen; diff --git a/bin/download-OPL-metadata-release.pl b/bin/download-OPL-metadata-release.pl index 40a48ca9ae..e9bfc9d1b4 100755 --- a/bin/download-OPL-metadata-release.pl +++ b/bin/download-OPL-metadata-release.pl @@ -2,19 +2,20 @@ # This script downloads the latest OPL metadata release, and restores the database dump file in that release. -use feature say; use strict; use warnings; +use feature 'say'; use File::Fetch; use File::Copy; use File::Path; +use Archive::Tar; use Mojo::File; -use JSON; +use Mojo::JSON qw(decode_json); BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } @@ -31,12 +32,12 @@ BEGIN die "The WeBWorK temporary directory $ce->{webworkDirs}{tmp} does not exist or is not writable." if (!-d $ce->{webworkDirs}{tmp} || !-w $ce->{webworkDirs}{tmp}); -$ENV{OPL_REPO_RELEASE_API_URL} = 'https://api.github.com/repos/ubc/webwork-open-problem-library/releases/latest' if (!defined($ENV{OPL_REPO_RELEASE_API_URL})); -my $releaseDataFF = - File::Fetch->new(uri => $ENV{OPL_REPO_RELEASE_API_URL}); -my $file = $releaseDataFF->fetch(to => $ce->{webworkDirs}{tmp}) or die $releaseDataFF->error; -my $path = Mojo::File->new($file); -my $releaseData = JSON->new->utf8->decode($path->slurp); +$ENV{OPL_REPO_RELEASE_API_URL} = 'https://api.github.com/repos/ubc/webwork-open-problem-library/releases/latest' + if (!defined($ENV{OPL_REPO_RELEASE_API_URL})); +my $releaseDataFF = File::Fetch->new(uri => $ENV{OPL_REPO_RELEASE_API_URL}); +my $file = $releaseDataFF->fetch(to => $ce->{webworkDirs}{tmp}) or die $releaseDataFF->error; +my $path = Mojo::File->new($file); +my $releaseData = decode_json($path->slurp); $path->remove; my $releaseTag = $releaseData->{tag_name}; @@ -54,8 +55,14 @@ BEGIN my $releaseFile = $releaseDownloadFF->fetch(to => $ce->{webworkDirs}{tmp}) or die $releaseDownloadFF->error; say 'Downloaded release archive, now extracting.'; -`$ce->{externalPrograms}{tar} xzf $releaseFile -C $ce->{webworkDirs}{tmp}`; -die "There was an error extracting the release: $!" if $?; +my $arch = Archive::Tar->new($releaseFile); +die "An error occurred while creating the tar file: $releaseFile" unless $arch; +$arch->setcwd($ce->{webworkDirs}{tmp}); +$arch->extract; +die "There was an error extracting the metadata release: $arch->error" if $arch->error; + +die "The downloaded archive did not contain the expected files." + unless -e "$ce->{webworkDirs}{tmp}/webwork-open-problem-library"; # Copy the json files into htdocs. for (glob("$ce->{webworkDirs}{tmp}/webwork-open-problem-library/JSON-SAVED/*.json")) { diff --git a/bin/dump-OPL-tables.pl b/bin/dump-OPL-tables.pl index 6f5ab8b2f4..fa30a4c2ed 100755 --- a/bin/dump-OPL-tables.pl +++ b/bin/dump-OPL-tables.pl @@ -1,20 +1,5 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - # This script dumps the OPL library tables to a dump file. use strict; @@ -22,7 +7,7 @@ BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } diff --git a/bin/dump-past-answers.pl b/bin/dump-past-answers.pl new file mode 100755 index 0000000000..1a1edb739f --- /dev/null +++ b/bin/dump-past-answers.pl @@ -0,0 +1,288 @@ +#!/usr/bin/env perl + +=head1 NAME + +dump-past-answers.pl: This script dumps past answers from courses into a CSV +file. + +=head1 SYNOPSIS + +dump-past-answers.pl [options] + + Options: + -c|--course Course from which to dump past answers + -f|--output-file CSV file name to dump past answers to + -h|--help Show this help + +The C option can be repeated multiple times to dump past answers from +multiple courses into the same file. If no courses are given via this option, +then past answers from all courses will be dumped. + +If the C option is not given then +C will be used for the output file name. + +=head1 DESCRIPTION + +The CSV file that is generated has the following columns: + +ID info + + 0 - Answer ID + 1 - Course ID + 2 - Student ID + 3 - Set ID + 4 - Problem ID + +User Info + + 5 - Permission Level + 6 - User Course Status + +Set Info + + 7 - Set type + 8 - Open Date (unix time) + 9 - Reduced Scoring Date (unix time) + 10 - Due Date (unix time) + 11 - Answer Date (unix time) + 12 - Final Set Grade (percentage) + +Problem Info + + 13 - Problem Path + 14 - Problem Value + 15 - Problem Max Attempts + 16 - Problem Seed + 17 - Attempted + 18 - Final Incorrect Attempts + 19 - Final Correct Attempts + 20 - Final Status + +OPL Info + + 21 - Subject + 22 - Chapter + 23 - Section + 24 - Keywords + +Answer Info + + 25 - Answer timestamp (unix time) + 26 - Attempt Number + 27 - Raw status of attempt (percentage of correct blanks) + 28 - Number of Answer Blanks + 29/30 etc... - The following columns will come in pairs. The first will be + the text of the answer contained in the answer blank + and the second will be the binary 0/1 status of the answer + blank. There will be as many pairs as answer blanks. + +=cut + +use strict; +use warnings; +use feature 'say'; + +BEGIN { + use Mojo::File qw(curfile); + use Env qw(WEBWORK_ROOT); + $WEBWORK_ROOT = curfile->dirname->dirname; +} + +use lib "$ENV{WEBWORK_ROOT}/lib"; + +use Getopt::Long qw(:config bundling); +use Pod::Usage; +use Text::CSV; + +use WeBWorK::CourseEnvironment; +use WeBWorK::DB; +use WeBWorK::Utils::CourseManagement qw(listCourses); +use WeBWorK::Utils::Tags; + +# Get options. +my @courses; +my $output_file = "past-answers-" . time . ".csv"; +my $show_help; +GetOptions('c|course=s' => \@courses, 'f|output-file=s' => \$output_file, 'h|help' => \$show_help); + +pod2usage(2) if $show_help; + +my $minimal_ce = WeBWorK::CourseEnvironment->new({ webwork_dir => $ENV{WEBWORK_ROOT} }); +@courses = listCourses($minimal_ce) unless @courses; + +sub write_past_answers_csv { + my $outFH = shift; + + my $csv = Text::CSV->new({ binary => 1, eol => "\n" }) or die "Cannot use CSV: " . Text::CSV->error_diag(); + + # Cache OPL tag data when it is looked up instead of looking up each file every time it appears as the source file + # for a past answer. This considerably speeds up this script. + my %OPL_tag_data; + + for my $courseID (@courses) { + next if $courseID eq ($minimal_ce->{admin_course_id} // 'admin') || $courseID eq 'modelCourse'; + + my $ce = WeBWorK::CourseEnvironment->new({ webwork_dir => $ENV{WEBWORK_ROOT}, courseName => $courseID }); + my $db = WeBWorK::DB->new($ce); + + my %permissionLabels = reverse %{ $ce->{userRoles} }; + + unless (defined $ce && defined $db) { + warn("Unable to load course environment and database for $courseID"); + next; + } + + say "Dumping past answers for $courseID"; + + # Get all past answers for this course sorted by answer_id and organize them by user, set, and problem. + my %pastAnswers; + for ($db->getPastAnswersWhere({}, 'answer_id')) { + push(@{ $pastAnswers{ $_->user_id }{ $_->set_id }{ $_->problem_id } }, $_); + } + + my @row; + + $row[1] = $courseID; + + my @users = $db->getUsersWhere({ user_id => { not_like => 'set_id:%' } }); + + for my $user (@users) { + my $userID = $user->user_id; + + $row[2] = $userID; + $row[5] = $permissionLabels{ $db->getPermissionLevel($userID)->permission }; + $row[6] = $ce->status_abbrev_to_name($user->{status}); + + my @sets; + for ($db->getMergedSetsWhere({ user_id => $userID }, 'set_id')) { + if (defined $_->assignment_type && $_->assignment_type =~ /gateway/) { + my $setID = $_->set_id; + my @versions = $db->listSetVersions($userID, $setID); + for my $version (@versions) { + push(@sets, $db->getUserSet($userID, "$setID,v$version")); + } + } else { + push(@sets, $_); + } + } + + for my $set (@sets) { + my $setID = $set->set_id; + + $row[3] = $setID; + $row[7] = $set->assignment_type; + $row[8] = $set->open_date; + $row[9] = $set->reduced_scoring_date; + $row[10] = $set->due_date; + $row[11] = $set->answer_date; + + my @problems = + $set->assignment_type =~ /gateway/ + ? $db->getMergedProblemVersionsWhere({ user_id => $userID, set_id => $setID }, 'problem_id') + : $db->getMergedProblemsWhere({ user_id => $userID, set_id => $setID }, 'problem_id'); + + # Compute set score + my $total = 0; + my $correct = 0; + for my $problem (@problems) { + $total += $problem->value; + $correct += $problem->value * $problem->status; + } + $row[12] = $total ? $correct / $total : 0; + + for my $problem (@problems) { + my $problemID = $problem->problem_id; + + $row[4] = $problemID; + $row[13] = $problem->source_file; + $row[14] = $problem->value; + $row[15] = $problem->max_attempts; + $row[16] = $problem->problem_seed; + $row[17] = $problem->attempted; + $row[18] = $problem->num_incorrect; + $row[19] = $problem->num_correct; + $row[20] = $problem->status; + + # Get OPL tag data. + if ($row[13]) { + my $file = "$ce->{courseDirs}{templates}/$row[13]"; + $OPL_tag_data{$file} = WeBWorK::Utils::Tags->new($file) + if !defined $OPL_tag_data{$file} && -e $file; + if (defined $OPL_tag_data{$file}) { + $row[21] = $OPL_tag_data{$file}{DBsubject}; + $row[22] = $OPL_tag_data{$file}{DBchapter}; + $row[23] = $OPL_tag_data{$file}{DBsection}; + $row[24] = + defined($OPL_tag_data{$file}{keywords}) + ? join(',', @{ $OPL_tag_data{$file}{keywords} }) + : ''; + } + } + + my $attempt_number = 0; + for my $answer (@{ $pastAnswers{$userID}{$setID}{ $problem->problem_id } }) { + my $answerID = $answer->answer_id; + ++$attempt_number; + + # If the source file for this answer is different from that of the merged user set, + # then update the row and get the OPL tag data for this file. + if ($row[13] ne $answer->source_file) { + $row[13] = $answer->source_file; + if ($row[13]) { + my $file = "$ce->{courseDirs}{templates}/$row[13]"; + $OPL_tag_data{$file} = WeBWorK::Utils::Tags->new($file) + if !defined $OPL_tag_data{$file} && -e $file; + if (defined $OPL_tag_data{$file}) { + $row[21] = $OPL_tag_data{$file}{DBsubject}; + $row[22] = $OPL_tag_data{$file}{DBchapter}; + $row[23] = $OPL_tag_data{$file}{DBsection}; + $row[24] = + defined($OPL_tag_data{$file}{keywords}) + ? join(',', @{ $OPL_tag_data{$file}{keywords} }) + : ''; + } + } + } + + # Input answer specific info + $row[0] = $answerID; + $row[16] = $answer->problem_seed + if defined $answer->problem_seed && $answer->problem_seed ne ''; + $row[25] = $answer->timestamp; + $row[26] = $attempt_number; + + my @scores = split('', $answer->scores); + my @answers = split("\t", $answer->answer_string, -1); + + # Skip answer processing if the number of scores isn't the same as the number of answers. + next if $#scores != $#answers; + + my $num_blanks = scalar(@scores); + + # Compute the raw status + my $score = 0; + for (@scores) { $score += $_ } + $row[27] = $num_blanks ? $score / $num_blanks : 0; + + $row[28] = $num_blanks; + + for (my $i = 0; $i < $num_blanks; $i++) { + $row[ 29 + 2 * $i ] = $answers[$i]; + $row[ 30 + 2 * $i ] = $scores[$i]; + } + + $csv->print($outFH, \@row) or warn "Couldn't print row"; + } + } + } + } + } + + return; +} + +say "Dumping answer data to $output_file"; +open(my $outFH, '>:encoding(UTF-8)', $output_file) or die("Couldn't open file $output_file"); +write_past_answers_csv($outFH); +close($outFH) or die("Couldn't close $output_file"); +say 'Done dumping data'; diff --git a/bin/dump_past_answers b/bin/dump_past_answers deleted file mode 100755 index d41394bc1d..0000000000 --- a/bin/dump_past_answers +++ /dev/null @@ -1,329 +0,0 @@ -#!/usr/bin/env perl - -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - -# This script dumps the course information from all unarchived courses into -# a single csv file. The csv file has the following columns. -# -# ID Info -# 0 - Answer ID hash -# 1 - Course ID hash -# 2 - Student ID hash -# 3 - Set ID hash -# 4 - Problem ID hash -# 5 - Timestamp -# User Info -# 6 - Permission Level -# 7 - Final Status -# Set Info -# 8 - Set type -# 9 - Open Date (unix time) -# 10 - Due Date (unix time) -# 11 - Answer Date (unix time) -# 12 - Final Set Grade (percentage) -# Problem Info -# 13 - Problem Path -# 14 - Problem Value -# 15 - Problem Max Attempts -# 16 - Problem Seed -# 17 - Attempted -# 18 - Final Incorrect Attempts -# 19 - Final Correct Attempts -# 20 - Final Status -# OPL Info -# 21 - Subject -# 22 - Chapter -# 23 - Section -# 24 - Keywords -# Answer Info -# 25 - Answer timestamp (unix time) -# 26 - Attempt Number -# 27 - Raw status of attempt (percentage of correct blanks) -# 28 - Number of Answer Blanks -# 29/30 etc... - The following columns will come in pairs. The first will be -# the text of the answer contained in the answer blank -# and the second will be the binary 0/1 status of the answer -# blank. There will be as many pairs as answer blanks. - -use strict; - -BEGIN { - use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); - - $WEBWORK_ROOT = curfile->dirname->dirname; -} - -use lib "$ENV{WEBWORK_ROOT}/lib"; - -use WeBWorK::CourseEnvironment; - -use WeBWorK::DB; -use WeBWorK::Utils::CourseIntegrityCheck; -use WeBWorK::Utils::CourseManagement qw/listCourses/; -use WeBWorK::Utils::Tags; -use WeBWorK::PG; - -use Text::CSV; -use Digest::SHA qw(sha256_hex); -use Net::Domain; - -# Deal with options -my $output_file; -my $zip_result = 1; -my $upload_result = 0; - -my $domainname = Net::Domain::domainname; -my $time = time(); - -# define and open the output file. -if (!$output_file) { - $output_file = "$domainname-$time.csv"; -} - -my $salt; -my $SALTFILE; -my $saltfilename = $ENV{WEBWORK_ROOT} . '/.dump_past_answers_salt'; - -if (-e $saltfilename) { - open($SALTFILE, '<', $saltfilename) || die("Couldn't open salt file."); - $salt = <$SALTFILE>; - close $SALTFILE; -} else { - $salt = ''; - for (my $i = 0; $i < 32; $i++) { - $salt .= ('.', '/', '0' .. '9', 'A' .. 'Z', 'a' .. 'z')[ rand 64 ]; - } - - open($SALTFILE, '>', $saltfilename) || die("Couldn't open salt file."); - print $SALTFILE $salt; - close $SALTFILE; -} - -my $OUT; -open($OUT, '>', $output_file) || die("Couldn't open file $output_file"); - -print "Dumping answer data to $output_file\n"; - -# set up various variables and utilities that we will need -my ($db, @wheres); -my $max_answer_blanks = 0; -my $csv = new Text::CSV->new({ binary => 1 }) - or die "Cannot use CSV: " . Text::CSV->error_diag(); -$csv->eol("\n"); - -my $ce = WeBWorK::CourseEnvironment->new({ - webwork_dir => $ENV{WEBWORK_ROOT}, -}); - -my @courses = listCourses($ce); -my %permissionLabels = reverse %{ $ce->{userRoles} }; - -# this is our row array and is the main structure -my @row; - -# go through courses -foreach my $courseID (@courses) { - next if $courseID eq 'admin' || $courseID eq 'modelCourse'; - - $ce = WeBWorK::CourseEnvironment->new({ - webwork_dir => $ENV{WEBWORK_ROOT}, - courseName => $courseID, - }); - $db = new WeBWorK::DB($ce->{dbLayout}); - - unless (defined($ce) && defined($db)) { - warn("Unable to load up database for $courseID"); - next; - } - - print "Dumping $courseID\n"; - - my $templateDir = $ce->{courseDirs}->{templates}; - - my $sCourseID = sha256_hex($salt . $domainname . $courseID); - - $row[1] = $sCourseID; - $row[5] = $time; - - my @userIDs = $db->listUsers(); - my @users = $db->getUsers(@userIDs); - - # go through users - foreach my $user (@users) { - my $userID = $user->user_id; - - #skip proctor users - next if $user->user_id =~ /^set_id:/; - - my $sUserID = sha256_hex($salt . $domainname . $courseID . $userID); - - # get user specific info - $row[2] = $sUserID; - my $permissionLevel = $db->getPermissionLevel($userID); - $row[6] = $permissionLabels{ $permissionLevel->permission }; - $row[7] = $ce->status_abbrev_to_name($user->{status}); - - my @setIDs = $db->listUserSets($userID); - @wheres = map { [ $userID, $_ ] } @setIDs; - my @sets = $db->getMergedSets(@wheres); - - # go through sets - foreach my $set (@sets) { - # skip gateways - if ($set->assignment_type =~ /gateway/ - && $set->set_id !~ /,v\d+$/) - { - next; - } - - my $setID = $set->set_id; - my $sSetID = sha256_hex($salt . $domainname . $courseID . $setID); - - # get set specific info - $row[3] = $sSetID; - $row[8] = $set->assignment_type; - $row[9] = $set->open_date; - $row[10] = $set->due_date; - $row[11] = $set->answer_date; - - my @problemIDs = $db->listUserProblems($userID, $setID); - @wheres = map { [ $userID, $setID, $_ ] } @problemIDs; - my @problems = $db->getMergedProblems(@wheres); - - # compute set score - my $total = 0; - my $correct = 0; - foreach my $problem (@problems) { - $total += $problem->value(); - $correct += $problem->value * $problem->status; - } - $row[12] = $total ? $correct / $total : 0; - - # go through each problem - foreach my $problem (@problems) { - my $problemID = $problem->problem_id; - my $sProblemID = sha256_hex($salt . $domainname . $courseID . $userID . $setID . $problemID); - - # print problem specific info - $row[4] = $sProblemID; - $row[13] = $problem->source_file; - $row[14] = $problem->value; - $row[15] = $problem->max_attempts; - $row[16] = $problem->problem_seed; - $row[17] = $problem->attempted; - $row[18] = $problem->num_incorrect; - $row[19] = $problem->num_correct; - $row[20] = $problem->status; - - # get OPL data - my $file = $templateDir . '/' . $problem->source_file(); - if (-e $file) { - my $tags = WeBWorK::Utils::Tags->new($file); - $row[21] = $tags->{DBsubject}; - $row[22] = $tags->{DBchapter}; - $row[23] = $tags->{DBsection}; - $row[24] = defined($tags->{keywords}) ? join(',', @{ $tags->{keywords} }) : ''; - } - - my @answerIDs = $db->listProblemPastAnswers($courseID, $userID, $setID, $problemID); - my @answers = $db->getPastAnswers(\@answerIDs); - - # go through attempts - my $attempt_number = 0; - foreach my $answer (@answers) { - #reset the row length because it can change; - @row = splice(@row, 0, 28); - my $answerID = $answer->answer_id; - my $sAnswerID = - sha256_hex($salt . $domainname . $courseID . $userID . $setID . $problemID . $answerID); - $attempt_number++; - - # if the source file changed redo that info - if ($row[13] != $answer->source_file) { - $row[13] = $answer->source_file; - $file = $templateDir . '/' . $answer->source_file(); - if (-e $file) { - my $tags = WeBWorK::Utils::Tags->new($file); - $row[21] = $tags->{DBsubject}; - $row[22] = $tags->{DBchapter}; - $row[23] = $tags->{DBsection}; - $row[24] = defined($tags->{keywords}) ? join(',', @{ $tags->{keywords} }) : ''; - } - } - - # input answer specific info - $row[0] = $sAnswerID; - $row[25] = $answer->timestamp; - $row[26] = $attempt_number; - - my @scores = split('', $answer->scores, -1); - my @answers = split("\t", $answer->answer_string, -1); - - # if the number of scores isn't the same as the number of - # answers we should skip - if ($#scores != $#answers) { - next; - } - my $num_blanks = scalar(@scores); - - $max_answer_blanks = $num_blanks - if ($num_blanks > $max_answer_blanks); - - # compute the raw status - my $score = 0; - foreach (@scores) { - $score += $_; - } - - $row[27] = $num_blanks ? $score / $num_blanks : 0; - - # we leave the computed status blank for now. - - $row[28] = $num_blanks; - - for (my $i = 0; $i < $num_blanks; $i++) { - $row[ 29 + 2 * $i ] = $answers[$i]; - $row[ 30 + 2 * $i ] = $scores[$i]; - } - - #form the csv string and print - $csv->print($OUT, \@row) || warn "Couldn't print row"; - } - } - } - } -} - -print "Done dumping data\n"; - -close($OUT) or die("Couldn't close $output_file"); - -if ($zip_result) { - print "Zipping file\n"; - - `gzip $output_file`; - - $output_file = $output_file . ".gz"; -} - -if ($upload_result) { - print "Uploading file\n"; - - `echo "put $output_file" | sftp -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oPort=57281 wwdata\@52.88.32.79`; -} - -1; diff --git a/bin/generate-OPL-set-def-lists.pl b/bin/generate-OPL-set-def-lists.pl index dbfaac2b4b..b0a8bb9b2d 100755 --- a/bin/generate-OPL-set-def-lists.pl +++ b/bin/generate-OPL-set-def-lists.pl @@ -26,7 +26,7 @@ =head1 DESCRIPTION BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } diff --git a/bin/importClassList.pl b/bin/importClassList.pl index a428e34f59..636ea25421 100755 --- a/bin/importClassList.pl +++ b/bin/importClassList.pl @@ -1,22 +1,11 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ + +use strict; +use warnings; BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } @@ -26,14 +15,10 @@ BEGIN use WeBWorK::CourseEnvironment; -use WeBWorK::DB qw(check_user_id); +use WeBWorK::DB; use WeBWorK::File::Classlist; - -#use WeBWorK::Utils qw(readFile readDirectory cryptPassword x); -use WeBWorK::Utils qw(cryptPassword); - -use strict; -use warnings; +use WeBWorK::Utils qw(cryptPassword); +use WeBWorK::File::Classlist qw(parse_classlist); if ((scalar(@ARGV) != 2)) { print "\nSyntax is: importClassList.pl course_id path_to_classlist_file.lst\n\n"; @@ -50,16 +35,14 @@ BEGIN courseName => $courseID }); -my $db = new WeBWorK::DB($ce->{dbLayout}); +my $db = WeBWorK::DB->new($ce); my $createNew = 1; # Always set to true, so add new users my $replaceExisting = "none"; # Always set to "none" so no existing accounts are changed my @replaceList = (); # Empty list my (@replaced, @added, @skipped); -# This was copied with MINOR changes from lib/WeBWorK/ContentGenerator/Instructor/UserList.pm -# FIXME REFACTOR this belongs in a utility class so that addcourse can use it! -# (we need a whole suite of higher-level import/export functions somewhere) +# This was copied with MINOR changes from lib/WeBWorK/ContentGenerator/Instructor/UserList.pm. sub importUsersFromCSV { my ($fileName, $createNew, $replaceExisting, @replaceList) = @_; @@ -110,14 +93,17 @@ sub importUsersFromCSV { $record{status} = $default_status_abbrev unless defined $record{status} and $record{status} ne ""; - # set password from student ID if password field is "empty" - if (not defined $record{password} or $record{password} eq "") { - if (defined $record{student_id} and $record{student_id} =~ /\S/) { - # crypt the student ID and use that - $record{password} = cryptPassword($record{student_id}); - } else { - # an empty password field in the database disables password login - $record{password} = ""; + # Determine what to use for the password (if anything). + if (!$record{password}) { + if (defined $record{unencrypted_password} && $record{unencrypted_password} =~ /\S/) { + $record{password} = cryptPassword($record{unencrypted_password}); + } elsif ($ce->{fallback_password_source} + && { user_id => 1, first_name => 1, last_name => 1, student_id => 1 } + ->{ $ce->{fallback_password_source} } + && $record{ $ce->{fallback_password_source} } + && $record{ $ce->{fallback_password_source} } =~ /\S/) + { + $record{password} = cryptPassword($record{ $ce->{fallback_password_source} }); } } @@ -127,18 +113,18 @@ sub importUsersFromCSV { my $User = $db->newUser(%record); my $PermissionLevel = $db->newPermissionLevel(user_id => $user_id, permission => $record{permission}); - my $Password = $db->newPassword(user_id => $user_id, password => $record{password}); + my $Password = $record{password} ? $db->newPassword(user_id => $user_id, password => $record{password}) : undef; # DBFIXME use REPLACE if (exists $allUserIDs{$user_id}) { $db->putUser($User); $db->putPermissionLevel($PermissionLevel); - $db->putPassword($Password); + $db->putPassword($Password) if $Password; push @replaced, $user_id; } else { $db->addUser($User); $db->addPermissionLevel($PermissionLevel); - $db->addPassword($Password); + $db->addPassword($Password) if $Password; push @added, $user_id; } } @@ -147,6 +133,7 @@ sub importUsersFromCSV { print("Skipped:\n\t", join("\n\t", @skipped), "\n\n"); print("Replaced:\n\t", join("\n\t", @replaced), "\n\n"); + return; } importUsersFromCSV($fileName, $createNew, $replaceExisting, @replaceList); diff --git a/bin/load-OPL-global-statistics.pl b/bin/load-OPL-global-statistics.pl index 8a6e1a6616..e2090af59d 100755 --- a/bin/load-OPL-global-statistics.pl +++ b/bin/load-OPL-global-statistics.pl @@ -1,27 +1,12 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - # This script loads the OPL global statistics, which is often done by bin/update-OPL-statistics but may need to be done # outside of that setting. use strict; BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } diff --git a/bin/newpassword b/bin/newpassword index 290fac18d1..2611690ae7 100755 --- a/bin/newpassword +++ b/bin/newpassword @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME @@ -60,7 +46,7 @@ use lib "$ENV{WEBWORK_ROOT}/lib"; use WeBWorK::CourseEnvironment; use WeBWorK::DB; -use WeBWorK::Utils qw(runtime_use readFile cryptPassword); +use WeBWorK::Utils qw(cryptPassword); if((scalar(@ARGV) != 3)) { print "\nSyntax is: newpassword CourseID User NewPassword"; @@ -94,7 +80,7 @@ my $ce = WeBWorK::CourseEnvironment->new({ courseName => $courseID }); -my $db = new WeBWorK::DB($ce->{dbLayout}); +my $db = WeBWorK::DB->new($ce); dopasswd($db, $user, $newP); print "Changed password for $user in $courseID\n"; diff --git a/bin/old_scripts/timing b/bin/old_scripts/timing deleted file mode 100755 index 1f9ca6fdb1..0000000000 --- a/bin/old_scripts/timing +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - -use strict; -use warnings; - -use constant ID => 0; -use constant TIME => 1; -use constant TASK => 2; -use constant DATA => 3; - -my %processes; - -while (<>) { - - my ($pid, $id, $time, $diff, $task, $data) = - m/^TIMING\s+(\d+)\s+(\d+)\s+([\d\.]+)\s+(\([\d\.]+\))\s+(.*)\s*:\s*(.*)$/; - push @{$processes{$pid}}, [$id, $time, $diff, $task, $data] if $pid; - -} - -foreach my $pid (keys %processes) { - my $indent = -1; - print "Timing data for PID $pid\n\n"; - my @events = sort { $a->[TIME] <=> $b->[TIME] } @{$processes{$pid}}; - foreach my $event (@events) { - $indent++ if $event->[DATA] eq "START"; - - print " "x$indent, join(" \t",@$event), "\n"; - $indent-- if $event->[DATA] eq "FINISH"; - - } - print "\n"; - -} diff --git a/bin/old_scripts/ww-update-config b/bin/old_scripts/ww-update-config deleted file mode 100755 index 9bb414417c..0000000000 --- a/bin/old_scripts/ww-update-config +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env perl - -use strict; -use warnings; - -my $cvs_header_line = '\$' . 'CVSHeader'; - -foreach my $arg (@ARGV) { - my ($conf_file, $dist_file); - - if ($arg =~ /^(.*)\.dist$/) { - $conf_file = $1; - $dist_file = $arg; - } else { - $conf_file = $arg; - $dist_file = "$arg.dist"; - } - - my $conf_version = cvs_version($conf_file) - or die "couldn't find CVS version in $conf_file\n"; - my $dist_version = cvs_version($dist_file) - or die "couldn't find CVS version in $dist_file\n"; - - if ($conf_version eq $dist_version) { - print "$conf_file is up-to-date at version $conf_version.\n"; - next; - } - - #print "conf_version=$conf_version dist_version=$dist_version\n"; - system "cvs diff -r '$conf_version' -r '$dist_version' '$dist_file'" - . "| patch '$conf_file'"; -} - -sub cvs_version { - my ($file) = @_; - open my $fh, "<", $file or die "couldn't open $file for reading: $!\n"; - my $line; - while (my $line = <$fh>) { - if ($line =~ /$cvs_header_line.*?(1(?:\.\d+)+)/) { - return $1; - } - } -} diff --git a/bin/old_scripts/wwaddindexing b/bin/old_scripts/wwaddindexing deleted file mode 100755 index 1ac7fda6a7..0000000000 --- a/bin/old_scripts/wwaddindexing +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - -=head1 NAME - -wwaddindexing - add indices to an existing sql_single course. - -=head1 SYNOPSIS - - wwaddindexing COURSEID - -=head1 DESCRIPTION - -Adds indices to the course named COURSEID. The course must use the sql_single -database layout. - -=cut - -BEGIN { - # hide arguments (there could be passwords there!) - $0 = "$0"; -} - -use strict; -use warnings; -use DBI; - -my $pg_dir; -BEGIN { - die "WEBWORK_ROOT not found in environment.\n" unless exists $ENV{WEBWORK_ROOT}; - $pg_dir = $ENV{PG_ROOT} // "$ENV{WEBWORK_ROOT}/../pg"; - die "The pg directory must be defined in PG_ROOT" unless (-e $pg_dir); -} - -use lib "$ENV{WEBWORK_ROOT}/lib"; -use lib "$pg_dir/lib"; -use WeBWorK::CourseEnvironment; -use WeBWorK::DB; -use WeBWorK::Utils qw/runtime_use/; -use WeBWorK::Utils::CourseManagement qw/dbLayoutSQLSources/; - -sub usage { - print STDERR "usage: $0 COURSEID \n"; - exit; -} - -sub usage_error { - print STDERR "$0: @_\n"; - usage(); -} - -# get command-line options -my ($courseID) = @ARGV; - -# perform sanity check -usage_error("must specify COURSEID.") unless $courseID and $courseID ne ""; - -# bring up a minimal course environment -my $ce = WeBWorK::CourseEnvironment->new({ - webwork_dir => $ENV{WEBWORK_ROOT}, - courseName => $courseID, -}); - -# make sure the course actually uses the 'sql_single' layout -usage_error("$courseID: does not use 'sql_single' database layout.") - unless $ce->{dbLayoutName} eq "sql_single"; - -# get database layout source data -my %sources = dbLayoutSQLSources($ce->{dbLayout}); - -foreach my $source (keys %sources) { - my %source = %{$sources{$source}}; - my @tables = @{$source{tables}}; - my $username = $source{username}; - my $password = $source{password}; - - my $dbh = DBI->connect($source, $username, $password); - - foreach my $table (@tables) { - # this stuff straight out of sql_single.pm - my %table = %{ $ce->{dbLayout}{$table} }; - my %params = %{ $table{params} }; - - my $source = $table{source}; - my $tableOverride = $params{tableOverride}; - my $recordClass = $table{record}; - - runtime_use($recordClass); - my @fields = $recordClass->FIELDS; - my @keyfields = $recordClass->KEYFIELDS; - - if (exists $params{fieldOverride}) { - my %fieldOverride = %{ $params{fieldOverride} }; - foreach my $field (@fields) { - $field = $fieldOverride{$field} if exists $fieldOverride{$field}; - } - } - - my @fieldList; - foreach my $start (0 .. $#keyfields) { - my $line = "ADD INDEX ( "; - $line .= join(", ", map { "`$_`(16)" } @keyfields[$start .. $#keyfields]); - $line .= " )"; - push @fieldList, $line; - } - my $fieldString = join(", ", @fieldList); - - my $tableName = $tableOverride || $table; - my $stmt = "ALTER TABLE `$tableName` $fieldString;"; - - unless ($dbh->do($stmt)) { - die "An error occured while trying to modify the course database.\n", - "It is possible that the course database is in an inconsistent state.\n", - "The DBI error message was:\n\n", - $dbh->errstr, "\n"; - } - } - - $dbh->disconnect; -} - -=head1 AUTHOR - -Written by Sam Hathaway, hathaway at users.sourceforge.net. - -=cut diff --git a/bin/old_scripts/wwdb_addgw b/bin/old_scripts/wwdb_addgw deleted file mode 100755 index 915cb9f75e..0000000000 --- a/bin/old_scripts/wwdb_addgw +++ /dev/null @@ -1,394 +0,0 @@ -#!/usr/bin/env perl -w -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ -# -# wwdb_addgw -# update webwork database tables to add fields for the gateway module -# -# by Gavin LaRose -# -=head1 NAME - -wwdb_addgw - convert SQL databases for WeBWorK 2 to add gateway fields. - -=head1 SYNOPSIS - - wwdb_addgw [-h] [sql|sql_single] - -=head1 DESCRIPTION - -Adds fields to the set and set_user tables in the WeBWorK mysql databases -that are required for the gateway module. The script prompts for which -courses to modify. Adding gateway database fields to existing courses -should have no effect on those courses, even if they are running under a -non-gateway aware version of the WeBWorK system. - -If C<-h> is provided, the script hides the mysql admin password. - -C or C gives the default WeBWorK database format. If -omitted, the script assumes sql_single and prompts to be sure. - -=cut - -use strict; -use DBI; - -# this is necessary on some systems -system("stty erase "); - -my $source = 'DBI:mysql'; - -# fields to add to the set and set_user tables -my %addFields = ( 'assignment_type' => 'text', - 'attempts_per_version' => 'integer', - 'time_interval' => 'integer', - 'versions_per_interval' => 'integer', - 'version_time_limit' => 'integer', - 'version_creation_time' => 'bigint', - 'problem_randorder' => 'integer', - 'version_last_attempt_time' => 'bigint', ); - -# process input data -my $hidepw = 0; -my $dbtype = 'sql_single'; -while ( $_ = shift(@ARGV) ) { - if ( /^-h$/ ) { - $hidepw = 1; - } elsif ( /^-/ ) { - die("Unknown input flag $_.\nUsage: wwdb_addgw [-h] sql|sql_single\n"); - } else { - if ( $_ eq 'sql' || $_ eq 'sql_single' ) { - $dbtype = $_; - } else { - die("Unknown argument $_.\nUsage: wwdb_addgw [-h] " . - "sql|sql_single\n"); - } - } -} - -printHdr( $dbtype ); - -# get database information -my ( $admin, $adminpw ); -( $admin, $adminpw, $dbtype ) = getDBInfo( $hidepw, $dbtype ); - -# connect to database, if we're in sql_single mode; this lets us easily -# get a list of courses to work with. in sql mode, it's harder b/c I can't -# get DBI->data_sources('mysql') to work on my system, so we prompt for -# those separately. if we're in sql single mode, $dbh is a place holder, -# because we have to do the database connects in the subroutines to connect -# to each different database -my $dbh = ''; -if ( $dbtype eq 'sql_single' ) { - $dbh = DBI->connect("$source:webwork", $admin, $adminpw) or - die( $DBI::errstr ); -} - -# get courses list -my @courses = getCourses( $dbtype, $dbh ); - -# now $course{coursename} = format (sql or sql_single) - -# do update -my ( $doneRef, $skipRef ) = updateCourses( $dbtype, $dbh, \@courses, - $admin, $adminpw ); -$dbh->disconnect() if ( $dbh ); - -# all done -confirmUpdate( $dbtype, $doneRef, $skipRef ); - -# end of main -#------------------------------------------------------------------------------- -# subroutines - -sub printHdr { - print < "; - my $admin = ; - chomp( $admin ); - $admin = 'root' if ( ! $admin ); - - print "mySQL login password for $admin > "; - system("stty -echo") if ( $hide ); - my $passwd = ; - if ( $hide ) { system("stty echo"); print "\n"; } - chomp( $passwd ); - die("Error: no password provided\n") if ( ! $passwd ); - - print "WeBWorK database type (sql or sql_single) [$type] > "; - my $dbtype = ; - chomp( $dbtype ); - $dbtype = $type if ( ! $dbtype ); - - return( $admin, $passwd, $dbtype ); -} - -sub getCourses { - my ( $dbtype, $dbh ) = @_; - - my %courses = (); - -# get a course list - if ( $dbtype eq 'sql' ) { - print "courses to update (enter comma separated) > "; - my $crslist = ; - chomp($crslist); - my @crslist = split(/,\s*/, $crslist); - die("Error: no courses specified\n") if ( ! @crslist ); - foreach ( @crslist ) { $courses{$_} = 1; } - - } else { - my $cmd = 'show tables'; - my $st = $dbh->prepare( $cmd ) or die( $dbh->errstr() ); - $st->execute() or die( $st->errstr() ); - my $rowRef = $st->fetchall_arrayref(); - foreach my $r ( @$rowRef ) { - $_ = $r->[0]; - #my ($crs, $tbl) = ( /^([^_]+)_(.*)$/ ); # this fails on courses with underscores in their names - my ($crs) = (/^(.*)_key$/); # match the key table - $courses{$crs} = 1 if ( defined( $crs ) ); - } - die("Error: found now sql_single WeBWorK courses\n") if ( ! %courses ); - } - -# confirm this is correct - print "\nList of courses to update:\n"; - my %nummap = orderedList( %courses ); - printclist( sort keys( %courses ) ); - print "Enter # to edit name, d# to delete from update list, or [cr] to " . - "continue.\n > "; - my $resp = ; - chomp($resp); - while ( $resp ) { - if ( $resp =~ /^\d+$/ ) { - print " old course name $nummap{$resp}; new > "; - delete( $courses{$nummap{$resp}} ); - my $newname = ; - chomp($newname); - $courses{ $newname } = 1; - } elsif ( $resp =~ /^d(\d+)$/ ) { - $resp = $1; - delete( $courses{$nummap{$resp}} ); - } else { - print "unrecognized response: $resp.\n"; - } - %nummap = orderedList( %courses ); - print "Current list of courses to update:\n"; - printclist( sort keys( %courses ) ); - print "Enter #, d# or [cr] > "; - chomp( $resp = ); - } - - my @courses = sort( keys %courses ); - if ( @courses ) { - return @courses; - } else { - die("Error: no courses left to update.\n"); - } -} - -sub orderedList { - my %hash = @_; - my $i=1; - my %nummap = (); - foreach ( sort( keys( %hash ) ) ) { - $nummap{ $i } = $_; - $i++; - } - return %nummap; -} - -sub printclist { - my @list = @_; - -# assumes a 75 column screen - - my $i = 1; - if ( @list <= 3 ) { - foreach ( @list ) { print " $i. $_\n"; $i++ } - } else { - while ( @list >= $i ) { - printf(" %2d. %-19s", $i, $list[$i-1]); - printf(" %2d. %-19s", ($i+1), $list[$i]) if ( @list >= ($i+1) ); - printf(" %2d. %-19s", ($i+2), $list[$i+1]) if ( @list >= ($i+2) ); - print "\n"; - $i+=3; - } - } - return 1; -} - -sub updateCourses { - my ( $dbtype, $dbh, $crsRef, $admin, $adminpw ) = @_; - - my @done = (); - my @skipped = (); - -# give some sense of progress - select STDOUT; $| = 1; # unbuffer output - print "doing update for $dbtype databases.\n"; - -# list of added fields to check for classes that don't need updating - my @newFields = keys( %addFields ); - - foreach my $crs ( @$crsRef ) { - print "updating $crs.\n"; - my $colRef; - - if ( $dbtype eq 'sql' ) { - # we need to get a database handle first - $dbh = DBI->connect("$source:webwork_$crs", $admin, $adminpw) or - die( $DBI::errstr ); - - # now get a list of columns from the set table to check to see if - # we need an update - my $cmd = "show columns from set_not_a_keyword"; - my $st = $dbh->prepare( $cmd ) or die( $dbh->errstr() ); - $st->execute(); - $colRef = $st->fetchall_arrayref(); - - } else { - # for sql_single we already have a database handle; get the set table - # columns and proceed - my $cmd = "show columns from `${crs}_set`"; - print "$cmd\n"; - my $st = $dbh->prepare( $cmd ) or die( $dbh->errstr() ); - $st->execute(); - $colRef = $st->fetchall_arrayref(); - } - - # now, do we have the columns we need already? - my $doneAlready = 0; - foreach my $cols ( @$colRef ) { - if ( inList( $cols->[0], @newFields ) ) { - $doneAlready = 1; - last; - } - } - if ( $doneAlready ) { - push( @skipped, $crs ); - next; - } else { - - # do update for course - my ( $cmd1, $cmd2 ); - if ( $dbtype eq 'sql' ) { - $cmd1 = 'alter table set_not_a_keyword add column'; - $cmd2 = 'alter table set_user add column'; - } else { - $cmd1 = "alter table `${crs}_set` add column"; - $cmd2 = "alter table `${crs}_set_user` add column"; - } - - foreach my $f ( keys %addFields ) { - print "$cmd1 $f $addFields{$f}\n"; - my $st = $dbh->prepare( "$cmd1 $f $addFields{$f}" ) or - die( $dbh->errstr() ); - $st->execute() or die( $st->errstr() ); - } - - foreach my $f ( keys %addFields ) { - print "$cmd2 $f $addFields{$f}\n"; - my $st = $dbh->prepare( "$cmd2 $f $addFields{$f}" ) or - die( $dbh->errstr() ); - $st->execute() or die( $st->errstr() ); - } - - push( @done, $crs ); - } - # if we're doing sql databases, disconnect from this courses' database - $dbh->disconnect() if ( $dbtype eq 'sql' ); - - } # end loop through courses - print "\n"; - - return( \@done, \@skipped ); -} - -sub inList { - my $v = shift(); - foreach ( @_ ) { return 1 if ( $v eq $_ ); } - return 0; -} - -sub confirmUpdate { - my ( $dbtype, $doneRef, $skipRef ) = @_; - - my $s1 = "updated $dbtype courses: "; - my $s2 = "courses not needing updates were skipped: "; - my $l1 = length($s1); - my $l2 = length($s2); - - my $crsList= (@$doneRef) ? join(', ', @$doneRef) : ''; - my $skpList= (@$skipRef) ? join(', ', @$skipRef) : ''; - my $crsString = ( $crsList ) ? - $s1 . hangIndent( $l1, 75, $l1, "$crsList.") . "\n" : ''; - my $skpString = ( $skpList ) ? - $s2 . hangIndent( $l1, 75, $l2, "$skpList." ) : ''; - - print <= $width ) { - $htext .= $line . "\n$ldr"; - $line = "$_ "; - $indent = $hang; - } else { - $line .= "$_ "; - } - } - $htext .= $line if ( $line ); - } - $htext =~ s/\n$ldr$//; - return $htext; -} - -# end of script -#------------------------------------------------------------------------------- diff --git a/bin/old_scripts/wwdb_check b/bin/old_scripts/wwdb_check deleted file mode 100755 index 48dd1df8a2..0000000000 --- a/bin/old_scripts/wwdb_check +++ /dev/null @@ -1,1025 +0,0 @@ -#!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - -=head1 NAME - -wwdb_check - check the schema of an existing WeBWorK database - -=head1 SYNOPSIS - - wwdb_check [-nv] [ COURSE ... ] - -=head1 DESCRIPTION - -Scans an existing WeBWorK database to verify that its structure is correct for -version 0 of the database structure. Version 0 refers to the last version before -automatic database upgrading was added to WeBWorK. This utility should be run -once after upgrading webwork from version 2.2.x to version 2.3.0. - -Once any inconsistencies are fixed using this utility, F should be -run to affect automatic database upgrades to the database version appropriate -for the current version of WeBWorK. - -If no courses are listed on the command line, all courses are checked. Checks -for the following: - -=over - -=item * - -Make sure that the appropriate tables exist for each course. - -=item * - -Make sure that the proper columns exist in each table. - -=item * - -Verify that the proper column type is in use for each column. - -=back - -=head1 OPTIONS - -=over - -=item -n - -Don't offer to fix problems, just report them. - -=item -v - -Verbose output. - -=back - -=cut - -use strict; -use warnings; -use Getopt::Std; -use DBI; -use Data::Dumper; - -my $pg_dir; -BEGIN { - die "WEBWORK_ROOT not found in environment.\n" unless exists $ENV{WEBWORK_ROOT}; - $pg_dir = $ENV{PG_ROOT} // "$ENV{WEBWORK_ROOT}/../pg"; - die "The pg directory must be defined in PG_ROOT" unless (-e $pg_dir); -} - -use lib "$ENV{WEBWORK_ROOT}/lib"; -use lib "$pg_dir/lib"; -use WeBWorK::CourseEnvironment; -use WeBWorK::Utils qw/runtime_use/; -use WeBWorK::Utils::CourseManagement qw/listCourses/; - -our ($opt_n, $opt_v); -getopts("nv"); - -my $noop = sub {}; - -if ($opt_n) { - *maybe_add_table = $noop; - *maybe_add_field = $noop; - *maybe_change_field = $noop; -} else { - *maybe_add_table = \&ask_add_table; - *maybe_add_field = \&ask_add_field; - *maybe_change_field = \&ask_change_field; -} - -if ($opt_v) { - $| = 1; - *verbose = sub { print STDERR @_ }; -} else { - *verbose = $noop; -} - -use constant DB_VERSION => 0; - -# a random coursename we can grab back out later -#my @chars = ('A'..'Z','a'..'z','0'..'9'); -#my $random_courseID = join("", map { $chars[rand(@chars)] } 1..16); -# fixed courseID for "version zero table data" -my $random_courseID = "6SC36NukknC3IT3M"; - -my $ce = WeBWorK::CourseEnvironment->new({ - webwork_dir => $ENV{WEBWORK_ROOT}, - courseName => $random_courseID, -}); - -my $dbh = DBI->connect( - $ce->{database_dsn}, - $ce->{database_username}, - $ce->{database_password}, - { - PrintError => 0, - RaiseError => 1, - }, -); - -=for comment - - %ww_table_data = ( - $table => { - sql_name => "SQL name for this field, probably contains $random_courseID", - field_order => [ ... ], - keyfield_order => [ ... ], - fields => { - $field => { - sql_name => "SQL name for this field, possibly overridden", - sql_type => "type for this field, from SQL_TYPES in record class", - is_keyfield => "boolean, whether or not this field is a keyfield", - }, - ... - }, - }, - ... - ); - -=cut - -# get table data for the current version of webwork -#my %ww_table_data = get_ww_table_data(); -#$Data::Dumper::Indent = 1; -#print Dumper(\%ww_table_data); -#exit; -# get static table data for version zero of the database -my %ww_table_data = get_version_zero_ww_table_data(); - -my %sql_tables = get_sql_tables(); - -if (exists $sql_tables{dbupgrade}) { - print "A 'dbupgrade' table exists in this database. This suggests that this database may already be upgraded beyond db_version 0. If this is the case, running this utility is not necessary. This utility is only needed to make sure that databases are set up correctly to enter into the automatic upgrade regimen.\n"; - exit unless ask_permission("Go ahead with table checks?", 0); - delete $sql_tables{dbupgrade}; -} - -my @ww_courses = @ARGV; -@ww_courses = listCourses($ce) if not @ww_courses; - -foreach my $ww_course_name (@ww_courses) { - my $ce2 = WeBWorK::CourseEnvironment->new({ - webwork_dir => $ENV{WEBWORK_ROOT}, - courseName => $ww_course_name, - }); - - my @diffs = compare_dbLayouts($ce, $ce2); - if (@diffs) { - print "\nThe database layout for course '$ww_course_name' differs from the generic database layout in global.conf. Here's how:\n\n"; - print map("* $_\n", @diffs), "\n"; - next unless ask_permission("Check course '$ww_course_name'?", 0); - } - - print "\nChecking tables for course '$ww_course_name'\n"; - - foreach my $ww_table_name (keys %ww_table_data) { - if ($ce2->{dbLayout}{$ww_table_name}{params}{non_native}) { - verbose("skipping table $ww_table_name for course $ww_course_name -- not a native table.\n"); - } else { - check_table($ww_course_name, $ww_table_name); - } - } -} - -my $qualifier = @ARGV ? " selected" : ""; -print "\nDone checking course tables.\n"; -print "The following tables exist in the database but are not associated with any$qualifier course:\n\n"; -print join("\n", sort keys %sql_tables), "\n\n"; - -exit; - -################################################################################ - -sub get_ww_table_data { - my %result; - - foreach my $table (keys %{$ce->{dbLayout}}) { - my $record_class = $ce->{dbLayout}{$table}{record}; - runtime_use $record_class; - - my @fields = $record_class->FIELDS; - my @types = $record_class->SQL_TYPES; - my @keyfields = $record_class->KEYFIELDS; - my %keyfields; @keyfields{@keyfields} = (); - - my %field_data; - - foreach my $i (0..$#fields) { - my $field = $fields[$i]; - my $field_sql = $ce->{dbLayout}{$table}{params}{fieldOverride}{$field}; - $field_data{$field}{sql_name} = $field_sql || $field; - - my $type = $types[$i]; - $field_data{$field}{sql_type} = $type; - - $field_data{$field}{is_keyfield} = exists $keyfields{$field}; - } - - $result{$table}{fields} = \%field_data; - $result{$table}{field_order} = \@fields; - $result{$table}{keyfield_order} = \@keyfields; - - my $table_sql = $ce->{dbLayout}{$table}{params}{tableOverride}; - $result{$table}{sql_name} = $table_sql || $table; - } - - return %result; -} - -sub get_sql_tables { - my $sql_tables_ref = $dbh->selectcol_arrayref("SHOW TABLES"); - my %sql_tables; @sql_tables{@$sql_tables_ref} = (); - - return %sql_tables; -} - -################################################################################ - -sub check_table { - my ($ww_course_name, $ww_table_name) = @_; - my $sql_table_name = get_sql_table_name($ww_table_data{$ww_table_name}{sql_name}, $ww_course_name); - - verbose("\nChecking '$ww_table_name' table (SQL table '$sql_table_name')\n"); - - if (exists $sql_tables{$sql_table_name}) { - check_fields($ww_course_name, $ww_table_name, $sql_table_name); - delete $sql_tables{$sql_table_name}; - } else { - print "$sql_table_name: table missing\n"; - my $ww_table_rec = $ww_table_data{$ww_table_name}; - if (maybe_add_table($ww_course_name, $ww_table_name)) { - check_fields($ww_course_name, $ww_table_name, $sql_table_name); - delete $sql_tables{$sql_table_name}; - } - } -} - -sub ask_add_table { - my ($ww_course_name, $ww_table_name) = @_; - my $ww_table_rec = $ww_table_data{$ww_table_name}; - my $sql_table_name = get_sql_table_name($ww_table_rec->{sql_name}, $ww_course_name); - - my $stmt = create_table_stmt($ww_table_rec, $sql_table_name); - - print "\nI can add this table to the database with the following SQL statement:\n"; - print "$stmt\n\n"; - print "If this is an upgraded installation, it is possible that '$ww_course_name' is an old GDBM course. If this is the case, you should probably not add this table, as it won't be used.\n"; - return 0 unless ask_permission("Add table '$sql_table_name'?"); - - return unless do_handle_error($dbh, $stmt); - print "Added table '$sql_table_name'.\n\n"; - - return 1; -} - -sub create_table_stmt { - my ($ww_table_rec, $sql_table_name) = @_; - - #print Dumper($ww_table_rec); - - my @field_list; - - # generate a column specification for each field - my @fields = @{$ww_table_rec->{field_order}}; - foreach my $field (@fields) { - my $ww_field_rec = $ww_table_rec->{fields}{$field}; - my $sql_field_name = $ww_field_rec->{sql_name}; - my $sql_field_type = $ww_field_rec->{sql_type}; - - push @field_list, "`$sql_field_name` $sql_field_type"; - } - - # generate an INDEX specification for each all possible sets of keyfields (i.e. 0+1+2, 1+2, 2) - my @keyfields = @{$ww_table_rec->{keyfield_order}}; - foreach my $start (0 .. $#keyfields) { - my @index_components; - - foreach my $component (@keyfields[$start .. $#keyfields]) { - my $ww_field_rec = $ww_table_rec->{fields}{$component}; - my $sql_field_name = $ww_field_rec->{sql_name}; - my $sql_field_type = $ww_field_rec->{sql_type}; - my $length_specifier = ($sql_field_type =~ /int/i) ? "" : "(16)"; - push @index_components, "`$sql_field_name`$length_specifier"; - } - - my $index_string = join(", ", @index_components); - push @field_list, "INDEX ( $index_string )"; - } - - my $field_string = join(", ", @field_list); - my $create_stmt = "CREATE TABLE `$sql_table_name` ( $field_string )"; - - return $create_stmt; -} - -################################################################################ - -sub check_fields { - my ($ww_course_name, $ww_table_name, $sql_table_name) = @_; - - my $describe_data = $dbh->selectall_hashref("DESCRIBE `$sql_table_name`", 1); - - foreach my $ww_field_name (@{$ww_table_data{$ww_table_name}{field_order}}) { - my $ww_field_rec = $ww_table_data{$ww_table_name}{fields}{$ww_field_name}; - my $sql_field_name = $ww_field_rec->{sql_name}; - my $sql_field_rec = $describe_data->{$sql_field_name}; - - verbose("Checking '$ww_field_name' field (SQL field '$sql_table_name.$sql_field_name')\n"); - - #print "$sql_table_name.$sql_field_name:\n"; - #print Dumper($ww_field_rec); - #print Dumper($sql_field_rec); - - if (defined $sql_field_rec) { - my ($sql_base_type) = $sql_field_rec->{Type} =~ /^([^(]*)/; - #print $sql_field_rec->{Type}, " => $sql_base_type\n"; - - my $needs_fixing = 0; - if ($ww_field_name eq "psvn") { - - unless ("int" eq lc($sql_base_type)) { - $needs_fixing = 1; - print "$sql_table_name.$sql_field_name: type should be 'int' but appears to be '", - lc($sql_base_type), "'\n"; - } - - unless (lc($sql_field_rec->{Extra}) =~ /\bauto_increment\b/) { - $needs_fixing = 1; - print "$sql_table_name.$sql_field_name: extra should contain 'auto_increment' but appears to be '", - lc($sql_field_rec->{Extra}), "'\n"; - } - - # FIXME instead of checking this, figure out how to use "SHOW INDEXES FROM `$sql_table_name`" - #unless ("pri" eq lc($sql_field_rec->{Key})) { - # $needs_fixing = 1; - # print "$sql_table_name.$sql_field_name: key should be 'pri' but appears to be '", - # lc($sql_field_rec->{Key}), "'\n"; - #} - - } else { - - unless (lc($ww_field_rec->{sql_type}) eq lc($sql_base_type)) { - $needs_fixing = 1; - print "$sql_table_name.$sql_field_name: type should be '", lc($ww_field_rec->{sql_type}), - "' but appears to be '", lc($sql_base_type), "'\n"; - } - - # FIXME instead of checking this, figure out how to use "SHOW INDEXES FROM `$sql_table_name`" - #unless ( $ww_field_rec->{is_keyfield} == (lc($sql_field_rec->{Key}) eq "mul") ) { - # $needs_fixing = 1; - # print "$sql_table_name.$sql_field_name: key should be '", - # ($ww_field_rec->{is_keyfield} ? "mul" : ""), "' but appears to be '", - # lc($sql_field_rec->{Key}), "'\n"; - #} - } - - $needs_fixing and maybe_change_field($ww_course_name, $ww_table_name, $ww_field_name, $sql_base_type); - - } else { - print "$sql_table_name.$sql_field_name: field missing\n"; - maybe_add_field($ww_course_name, $ww_table_name, $ww_field_name); - } - } -} - -sub ask_add_field { - my ($ww_course_name, $ww_table_name, $ww_field_name) = @_; - my $ww_table_rec = $ww_table_data{$ww_table_name}; - my $sql_table_name = get_sql_table_name($ww_table_rec->{sql_name}, $ww_course_name); - my $sql_field_name = $ww_table_rec->{fields}{$ww_field_name}{sql_name}; - - my $stmt = add_field_stmt($ww_table_rec, $ww_field_name, $sql_table_name); - - print "\nI can add this field to the database with the following SQL statement:\n"; - print "$stmt\n\n"; - return 0 unless ask_permission("Add field '$sql_table_name.$sql_field_name'?"); - - return unless do_handle_error($dbh, $stmt); - print "Added field '$sql_field_name'.\n\n"; - - return 0; -} - -sub add_field_stmt { - my ($ww_table_rec, $ww_field_name, $sql_table_name) = @_; - my $sql_field_name = $ww_table_rec->{fields}{$ww_field_name}{sql_name}; - my $sql_field_type = $ww_table_rec->{fields}{$ww_field_name}{sql_type}; - my $location_modifier = get_location_modifier($ww_table_rec, $ww_field_name); - - return "ALTER TABLE `$sql_table_name` ADD COLUMN `$sql_field_name` $sql_field_type $location_modifier"; -} - -sub get_location_modifier { - my ($ww_table_rec, $ww_field_name) = @_; - - my $field_index = -1; - - for (my $i = 0; $i < @{$ww_table_rec->{field_order}}; $i++) { - if ($ww_table_rec->{field_order}[$i] eq $ww_field_name) { - $field_index = $i; - last; - } - } - - if ($field_index < 0) { - die "field '$ww_field_name' not found in field_order (shouldn't happen!)"; - } elsif ($field_index > 0) { - my $ww_prev_field_name = $ww_table_rec->{field_order}[$field_index-1]; - my $sql_prev_field_name = $ww_table_rec->{fields}{$ww_prev_field_name}{sql_name}; - return "AFTER `$sql_prev_field_name`"; - } else { - return "FIRST"; - } -} - -sub ask_change_field { - my ($ww_course_name, $ww_table_name, $ww_field_name, $sql_curr_base_type) = @_; - my $ww_table_rec = $ww_table_data{$ww_table_name}; - my $sql_table_name = get_sql_table_name($ww_table_rec->{sql_name}, $ww_course_name); - my $sql_field_name = $ww_table_rec->{fields}{$ww_field_name}{sql_name}; - - my @stmts = change_field_stmts($ww_table_rec, $ww_field_name, $sql_table_name, $sql_curr_base_type); - - my $pl = @stmts == 1 ? "" : "s"; - print "\nI can change this field with the following SQL statement$pl:\n"; - print map("$_\n", @stmts), "\n"; - return 0 unless ask_permission("Change field '$sql_table_name.$sql_field_name'?"); - - foreach my $stmt (@stmts) { - return unless do_handle_error($dbh, $stmt); - } - print "Changed field '$sql_field_name'.\n\n"; - - return 0; -} - -sub change_field_stmts { - my ($ww_table_rec, $ww_field_name, $sql_table_name, $sql_curr_base_type) = @_; - my $sql_field_name = $ww_table_rec->{fields}{$ww_field_name}{sql_name}; - my $sql_field_type = $ww_table_rec->{fields}{$ww_field_name}{sql_type}; - - if ($sql_curr_base_type =~ /text/i and $sql_field_type =~ /int/i) { - return ( - "ALTER TABLE `$sql_table_name` CHANGE COLUMN `$sql_field_name` `$sql_field_name` VARCHAR(255)", - "ALTER TABLE `$sql_table_name` CHANGE COLUMN `$sql_field_name` `$sql_field_name` $sql_field_type", - ); - } else { - return "ALTER TABLE `$sql_table_name` CHANGE COLUMN `$sql_field_name` `$sql_field_name` $sql_field_type"; - } -} - -################################################################################ - -sub get_sql_table_name { - my ($template, $course_name) = @_; - - $template =~ s/$random_courseID/$course_name/g; - return $template; -} - -sub ask_permission { - my ($prompt, $default) = @_; - - $default = 1 if not defined $default; - my $options = $default ? "[Y/n]" : "[y/N]"; - - while (1) { - print "$prompt $options "; - my $resp = ; - chomp $resp; - return $default if $resp eq ""; - return 1 if lc $resp eq "y"; - return 0 if lc $resp eq "n"; - $prompt = 'Please enter "y" or "n".'; - } -} - -# no error => returns true -# error, user says continue => returns false -# error, user says don't continue => returns undef -# error, user says exit => exits -sub do_handle_error { - my ($dbh, $stmt) = @_; - - eval { $dbh->do($stmt) }; - if ($@) { - print "SQL statment failed. Here is the error message: $@\n"; - return ask_permission("Continue?", 1); - } else { - return 1; - } -} - -sub compare_dbLayouts { - my ($ce1, $ce2) = @_; - - my $dbLayout1 = $ce1->{dbLayoutName}; - my $dbLayout2 = $ce2->{dbLayoutName}; - #warn "Generic: '$dbLayout1' this course: '$dbLayout2'.\n"; - - # simplisic check for now - if ($dbLayout1 ne $dbLayout2) { - return "\$dbLayoutName differs. Generic: '$dbLayout1' this course: '$dbLayout2'. (If you've created" - . " a modified version of the '$dbLayout1' database layout for use with this course, it's probably" - . " OK to check this course anyway. Just be sure that any fixes this program proposes are" - . " appropriate given your modifications.)"; - } - - return (); -} - -################################################################################ - -sub get_version_zero_ww_table_data { - return ( - 'problem_user' => { - 'fields' => { - 'problem_seed' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'problem_seed' - }, - 'status' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'status' - }, - 'max_attempts' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'max_attempts' - }, - 'value' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'value' - }, - 'last_answer' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'last_answer' - }, - 'source_file' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'source_file' - }, - 'set_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'BLOB', - 'sql_name' => 'set_id' - }, - 'problem_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'INT', - 'sql_name' => 'problem_id' - }, - 'num_incorrect' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'num_incorrect' - }, - 'num_correct' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'num_correct' - }, - 'attempted' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'attempted' - }, - 'user_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'BLOB', - 'sql_name' => 'user_id' - } - }, - 'keyfield_order' => [ - 'user_id', - 'set_id', - 'problem_id' - ], - 'field_order' => [ - 'user_id', - 'set_id', - 'problem_id', - 'source_file', - 'value', - 'max_attempts', - 'problem_seed', - 'status', - 'attempted', - 'last_answer', - 'num_correct', - 'num_incorrect' - ], - 'sql_name' => '6SC36NukknC3IT3M_problem_user' - }, - 'permission' => { - 'fields' => { - 'permission' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'permission' - }, - 'user_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'BLOB', - 'sql_name' => 'user_id' - } - }, - 'keyfield_order' => [ - 'user_id' - ], - 'field_order' => [ - 'user_id', - 'permission' - ], - 'sql_name' => '6SC36NukknC3IT3M_permission' - }, - 'key' => { - 'fields' => { - 'timestamp' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'timestamp' - }, - 'user_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'BLOB', - 'sql_name' => 'user_id' - }, - 'key' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'key_not_a_keyword' - } - }, - 'keyfield_order' => [ - 'user_id' - ], - 'field_order' => [ - 'user_id', - 'key', - 'timestamp' - ], - 'sql_name' => '6SC36NukknC3IT3M_key' - }, - 'password' => { - 'fields' => { - 'password' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'password' - }, - 'user_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'BLOB', - 'sql_name' => 'user_id' - } - }, - 'keyfield_order' => [ - 'user_id' - ], - 'field_order' => [ - 'user_id', - 'password' - ], - 'sql_name' => '6SC36NukknC3IT3M_password' - }, - 'problem' => { - 'fields' => { - 'problem_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'INT', - 'sql_name' => 'problem_id' - }, - 'max_attempts' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'max_attempts' - }, - 'value' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'value' - }, - 'source_file' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'source_file' - }, - 'set_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'BLOB', - 'sql_name' => 'set_id' - } - }, - 'keyfield_order' => [ - 'set_id', - 'problem_id' - ], - 'field_order' => [ - 'set_id', - 'problem_id', - 'source_file', - 'value', - 'max_attempts' - ], - 'sql_name' => '6SC36NukknC3IT3M_problem' - }, - 'user' => { - 'fields' => { - 'email_address' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'email_address' - }, - 'student_id' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'student_id' - }, - 'comment' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'comment' - }, - 'status' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'status' - }, - 'recitation' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'recitation' - }, - 'section' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'section' - }, - 'user_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'BLOB', - 'sql_name' => 'user_id' - }, - 'last_name' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'last_name' - }, - 'first_name' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'first_name' - } - }, - 'keyfield_order' => [ - 'user_id' - ], - 'field_order' => [ - 'user_id', - 'first_name', - 'last_name', - 'email_address', - 'student_id', - 'status', - 'section', - 'recitation', - 'comment' - ], - 'sql_name' => '6SC36NukknC3IT3M_user' - }, - 'set_user' => { - 'fields' => { - 'version_time_limit' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'version_time_limit' - }, - 'set_header' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'set_header' - }, - 'psvn' => { - 'is_keyfield' => '', - 'sql_type' => 'INT NOT NULL PRIMARY KEY AUTO_INCREMENT', - 'sql_name' => 'psvn' - }, - 'hardcopy_header' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'hardcopy_header' - }, - 'version_creation_time' => { - 'is_keyfield' => '', - 'sql_type' => 'BIGINT', - 'sql_name' => 'version_creation_time' - }, - 'open_date' => { - 'is_keyfield' => '', - 'sql_type' => 'BIGINT', - 'sql_name' => 'open_date' - }, - 'problem_randorder' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'problem_randorder' - }, - 'versions_per_interval' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'versions_per_interval' - }, - 'version_last_attempt_time' => { - 'is_keyfield' => '', - 'sql_type' => 'BIGINT', - 'sql_name' => 'version_last_attempt_time' - }, - 'time_interval' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'time_interval' - }, - 'set_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'BLOB', - 'sql_name' => 'set_id' - }, - 'visible' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'visible' - }, - 'assignment_type' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'assignment_type' - }, - 'due_date' => { - 'is_keyfield' => '', - 'sql_type' => 'BIGINT', - 'sql_name' => 'due_date' - }, - 'answer_date' => { - 'is_keyfield' => '', - 'sql_type' => 'BIGINT', - 'sql_name' => 'answer_date' - }, - 'user_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'BLOB', - 'sql_name' => 'user_id' - }, - 'attempts_per_version' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'attempts_per_version' - } - }, - 'keyfield_order' => [ - 'user_id', - 'set_id' - ], - 'field_order' => [ - 'user_id', - 'set_id', - 'psvn', - 'set_header', - 'hardcopy_header', - 'open_date', - 'due_date', - 'answer_date', - 'visible', - 'assignment_type', - 'attempts_per_version', - 'time_interval', - 'versions_per_interval', - 'version_time_limit', - 'version_creation_time', - 'problem_randorder', - 'version_last_attempt_time' - ], - 'sql_name' => '6SC36NukknC3IT3M_set_user' - }, - 'set' => { - 'fields' => { - 'version_last_attempt_time' => { - 'is_keyfield' => '', - 'sql_type' => 'BIGINT', - 'sql_name' => 'version_last_attempt_time' - }, - 'version_time_limit' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'version_time_limit' - }, - 'versions_per_interval' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'versions_per_interval' - }, - 'time_interval' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'time_interval' - }, - 'set_header' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'set_header' - }, - 'set_id' => { - 'is_keyfield' => 1, - 'sql_type' => 'BLOB', - 'sql_name' => 'set_id' - }, - 'hardcopy_header' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'hardcopy_header' - }, - 'visible' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'visible' - }, - 'version_creation_time' => { - 'is_keyfield' => '', - 'sql_type' => 'BIGINT', - 'sql_name' => 'version_creation_time' - }, - 'due_date' => { - 'is_keyfield' => '', - 'sql_type' => 'BIGINT', - 'sql_name' => 'due_date' - }, - 'assignment_type' => { - 'is_keyfield' => '', - 'sql_type' => 'TEXT', - 'sql_name' => 'assignment_type' - }, - 'open_date' => { - 'is_keyfield' => '', - 'sql_type' => 'BIGINT', - 'sql_name' => 'open_date' - }, - 'answer_date' => { - 'is_keyfield' => '', - 'sql_type' => 'BIGINT', - 'sql_name' => 'answer_date' - }, - 'attempts_per_version' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'attempts_per_version' - }, - 'problem_randorder' => { - 'is_keyfield' => '', - 'sql_type' => 'INT', - 'sql_name' => 'problem_randorder' - } - }, - 'keyfield_order' => [ - 'set_id' - ], - 'field_order' => [ - 'set_id', - 'set_header', - 'hardcopy_header', - 'open_date', - 'due_date', - 'answer_date', - 'visible', - 'assignment_type', - 'attempts_per_version', - 'time_interval', - 'versions_per_interval', - 'version_time_limit', - 'version_creation_time', - 'problem_randorder', - 'version_last_attempt_time' - ], - 'sql_name' => '6SC36NukknC3IT3M_set' - } - ); -} diff --git a/bin/old_scripts/wwdb_init b/bin/old_scripts/wwdb_init deleted file mode 100755 index 1966046fe4..0000000000 --- a/bin/old_scripts/wwdb_init +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - -use strict; -use warnings; -use Getopt::Std; -use DBI; -use Data::Dumper; - -my $pg_dir; -BEGIN { - die "WEBWORK_ROOT not found in environment.\n" unless exists $ENV{WEBWORK_ROOT}; - $pg_dir = $ENV{PG_ROOT} // "$ENV{WEBWORK_ROOT}/../pg"; - die "The pg directory must be defined in PG_ROOT" unless (-e $pg_dir); -} -use lib "$ENV{WEBWORK_ROOT}/lib"; -use lib "$pg_dir/lib"; -use WeBWorK::CourseEnvironment; -use WeBWorK::Utils qw/runtime_use/; - - -our ($opt_v); -getopts("v"); - -if ($opt_v) { - $| = 1; - *verbose = sub { print STDERR @_ }; -} else { - *verbose = sub {}; -} - -# global variables, hah hah. -my ($dbh, %sql_tables); - -################################################################################ - -my $i = -1; -our @DB_VERSIONS; - -$DB_VERSIONS[++$i]{desc} = "is the initial version of database, identical to database structure in WeBWorK 2.2.x."; - -$DB_VERSIONS[++$i]{desc} = "adds dbupgrade table to facilitate automatic database upgrades."; -$DB_VERSIONS[ $i]{global_code} = sub { - $dbh->do("CREATE TABLE `dbupgrade` (`name` VARCHAR(255) NOT NULL PRIMARY KEY, `value` TEXT)"); - $dbh->do("INSERT INTO `dbupgrade` (`name`, `value`) VALUES (?, ?)", {}, "db_version", 1); - $sql_tables{dbupgrade} = (); -}; - - -$DB_VERSIONS[++$i]{desc} = "adds depths table to keep track of dvipng depth information."; -$DB_VERSIONS[ $i]{global_code} = sub { - $dbh->do("CREATE TABLE depths (md5 CHAR(33) NOT NULL, depth SMALLINT, PRIMARY KEY (md5))"); - $sql_tables{depths} = (); -}; - -$DB_VERSIONS[++$i]{desc} = "adds locations, location_addresses, set_locations and set_locations_user tables to database, and add restrict_ip to set and set_user."; -$DB_VERSIONS[ $i]{global_code} = sub { - $dbh->do("CREATE TABLE locations (location_id TINYBLOB NOT NULL, description TEXT, PRIMARY KEY (location_id(1000)))"); - $dbh->do("CREATE TABLE location_addresses (location_id TINYBLOB NOT NULL, ip_mask TINYBLOB NOT NULL, PRIMARY KEY (location_id(500),ip_mask(500)))"); -}; - -our $THIS_DB_VERSION = $i; - -################################################################################ - -my $ce = WeBWorK::CourseEnvironment->new({ - webwork_dir => $ENV{WEBWORK_ROOT}, -}); - -$dbh = DBI->connect( - $ce->{database_dsn}, - $ce->{database_username}, - $ce->{database_password}, - { - PrintError => 0, - RaiseError => 1, - }, -); - -{ - verbose("Obtaining dbupgrade lock...\n"); - my ($lock_status) = $dbh->selectrow_array("SELECT GET_LOCK('dbupgrade', 10)"); - if (not defined $lock_status) { - print "Couldn't obtain lock because an error occurred.\n"; - exit 2; - } - if ($lock_status) { - verbose("Got lock.\n"); - } else { - print "Timed out while waiting for lock.\n"; - exit 2; - } -} - -%sql_tables = get_sql_tables(); - -my $db_version = 0; - - -verbose("Initial db_version is $db_version\n"); - -if ($db_version > $THIS_DB_VERSION) { - print "db_version is $db_version, but the current database version is only $THIS_DB_VERSION. This database was probably used with a newer version of WeBWorK.\n"; - exit; -} - -while ($db_version < $THIS_DB_VERSION) { - $db_version++; - unless (upgrade_to_version($db_version)) { - print "\nUpgrading from version ".($db_version-1)." to $db_version failed.\n\n"; - unless (ask_permission("Ignore this error and go on to the next version?", 0)) { - exit 3; - } - } - set_db_version($db_version); -} - -print "\nDatabase is up-to-date at version $db_version.\n"; - -END { - verbose("Releasing dbupgrade lock...\n"); - my ($lock_status) = $dbh->selectrow_array("SELECT RELEASE_LOCK('dbupgrade')"); - if (not defined $lock_status) { - print "Couldn't release lock because the lock does not exist.\n"; - exit 2; - } - if ($lock_status) { - verbose("Released lock.\n"); - } else { - print "Couldn't release lock because the lock is not held by this thread.\n"; - exit 2; - } -} - -################################################################################ - -sub get_sql_tables { - my $sql_tables_ref = $dbh->selectcol_arrayref("SHOW TABLES"); - my %sql_tables; @sql_tables{@$sql_tables_ref} = (); - - return %sql_tables; -} - -sub set_db_version { - my $vers = shift; - $dbh->do("UPDATE `dbupgrade` SET `value`=? WHERE `name`='db_version'", {}, $vers); -} - -sub upgrade_to_version { - my $vers = shift; - my %info = %{$DB_VERSIONS[$vers]}; - - print "\nUpgrading database from version " . ($vers-1) . " to $vers...\n"; - my $desc = $info{desc} || "has no description."; - print "(Version $vers $desc)\n"; - - if (exists $info{global_code}) { - eval { $info{global_code}->() }; - if ($@) { - print "\nAn error occured while running the system upgrade code for version $vers:\n"; - print "$@"; - return 0 unless ask_permission("Ignore this error and keep going?", 0); - } - } - print "Done.\n"; - return 1; -} - -################################################################################ - -sub ask_permission { - my ($prompt, $default) = @_; - - $default = 1 if not defined $default; - my $options = $default ? "[Y/n]" : "[y/N]"; - - while (1) { - print "$prompt $options "; - my $resp = ; - chomp $resp; - return $default if $resp eq ""; - return 1 if lc $resp eq "y"; - return 0 if lc $resp eq "n"; - $prompt = 'Please enter "y" or "n".'; - } -} \ No newline at end of file diff --git a/bin/old_scripts/wwdb_upgrade b/bin/old_scripts/wwdb_upgrade deleted file mode 100755 index 66a9b62780..0000000000 --- a/bin/old_scripts/wwdb_upgrade +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - -use strict; -use warnings; -use Getopt::Std; -use Data::Dumper; - -my $pg_dir; -BEGIN { - die "WEBWORK_ROOT not found in environment.\n" unless exists $ENV{WEBWORK_ROOT}; - $pg_dir = $ENV{PG_ROOT} // "$ENV{WEBWORK_ROOT}/../pg"; - die "The pg directory must be defined in PG_ROOT" unless (-e $pg_dir); -} -use lib "$ENV{WEBWORK_ROOT}/lib"; -use lib "$pg_dir/lib"; -use WeBWorK::CourseEnvironment; -use WeBWorK::Utils::DBUpgrade; - -our ($opt_v); -getopts("v"); - -if ($opt_v) { - $WeBWorK::Debug::Enabled = 1; -} else { - $WeBWorK::Debug::Enabled = 0; -} - -my $ce = new WeBWorK::CourseEnvironment({webwork_dir=>$ENV{WEBWORK_ROOT}}); - -my $upgrader = new WeBWorK::Utils::DBUpgrade( - ce => $ce, - verbose_sub => sub { print STDERR @_ }, -); - -$upgrader->do_upgrade; - diff --git a/bin/remove_stale_images b/bin/remove_stale_images index 214e5b0a08..b5a7064e17 100755 --- a/bin/remove_stale_images +++ b/bin/remove_stale_images @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME @@ -78,7 +64,7 @@ BEGIN { use lib "$ENV{WEBWORK_ROOT}/lib"; use WeBWorK::CourseEnvironment; -use WeBWorK::Utils qw(runtime_use readFile); +use WeBWorK::Utils::Files qw(readFile); use constant ACCESSED => 8; use constant MODIFIED => 9; diff --git a/bin/reset2fa b/bin/reset2fa new file mode 100644 index 0000000000..a440ace56d --- /dev/null +++ b/bin/reset2fa @@ -0,0 +1,19 @@ +warn "Pass users as additional arguments on the command line.\n" + . "Usage: wwsh $ce->{courseName} /opt/webwork/webwork2/bin/reset2fa [users]\n" + unless @ARGV; + +for (@ARGV) { + my $password = eval { $db->getPassword($_) }; + if ($@) { + warn "Unable to retrieve password record for $_ from the database: $@\n"; + next; + } + + $password->otp_secret(''); + eval { $db->putPassword($password) }; + if ($@) { + warn "Unable to reset two factor authentication secret for $_: $@\n"; + } else { + print "Successfully reset two factor authentication for $_.\n"; + } +} diff --git a/bin/restore-OPL-tables.pl b/bin/restore-OPL-tables.pl index 969e24f142..c99babd39d 100755 --- a/bin/restore-OPL-tables.pl +++ b/bin/restore-OPL-tables.pl @@ -1,20 +1,5 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - # This script restores the OPL library tables from a dump file. use strict; @@ -22,7 +7,7 @@ BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } diff --git a/bin/setfilepermissions b/bin/setfilepermissions index b50b40b85a..9b428cb6c1 100755 --- a/bin/setfilepermissions +++ b/bin/setfilepermissions @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME diff --git a/bin/test_library_build.pl b/bin/test_library_build.pl index 7952b810dd..b7b7dc9f96 100755 --- a/bin/test_library_build.pl +++ b/bin/test_library_build.pl @@ -2,7 +2,7 @@ BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } diff --git a/bin/ubc_update_all_lti_admins.pl b/bin/ubc_update_all_lti_admins.pl new file mode 100755 index 0000000000..31dd1d6986 --- /dev/null +++ b/bin/ubc_update_all_lti_admins.pl @@ -0,0 +1,60 @@ +#!/usr/bin/env perl +# +# ubc custom +# +# Update LTI created admin users in existing courses to the current password and MFA secret. + +BEGIN { + use Mojo::File qw(curfile); + use Env qw(WEBWORK_ROOT); + + $WEBWORK_ROOT = curfile->dirname->dirname; +} + +use lib "$ENV{WEBWORK_ROOT}/lib"; + +use WeBWorK::CourseEnvironment; + +use WeBWorK::DB; +use WeBWorK::Utils qw(cryptPassword); +use WeBWorK::Utils::CourseManagement qw(listCourses); +use MIME::Base32 qw(decode_base32); + +sub updateLtiAdminCourse { + my ($upgrade_courseID) = @_; + + my $ce = WeBWorK::CourseEnvironment->new({ + webwork_dir => $ENV{WEBWORK_ROOT}, + courseName => $upgrade_courseID, + }); + my $db = WeBWorK::DB->new($ce); + my $user = 'admin'; + my $newpass = $ENV{LTI_ADMIN_PASSWORD}; + my $newtotp = decode_base32($ENV{LTI_ADMIN_TOTP}); + + my $passwordRecord = eval { $db->getPassword($user) }; + if ($passwordRecord) { + my $cryptedPassword = cryptPassword($newpass); + $passwordRecord->password($cryptedPassword); + $passwordRecord->otp_secret($newtotp); + eval { $db->putPassword($passwordRecord) }; + if ($@) { + die "Errors $@ "; + } + } else { + print "No admin user in course\n"; + } +} + +my $adminCe = WeBWorK::CourseEnvironment->new({ + webwork_dir => $ENV{WEBWORK_ROOT}, + courseName => 'admin', +}); +my @courseIDs = listCourses($adminCe); +use Data::Dumper; + +for my $courseID (@courseIDs) { + print "-----------------------------------------\n"; + print "Updating LTI admin user in $courseID\n"; + updateLtiAdminCourse($courseID); +} diff --git a/bin/ubc_upgrade_all_courses.pl b/bin/ubc_upgrade_all_courses.pl new file mode 100755 index 0000000000..3de8c24730 --- /dev/null +++ b/bin/ubc_upgrade_all_courses.pl @@ -0,0 +1,89 @@ +#!/usr/bin/env perl +# +# ubc custom +# +# Update tables for all courses. +# Use this if the upgrader in the admin course fails to load. + +BEGIN { + use Mojo::File qw(curfile); + use Env qw(WEBWORK_ROOT); + + $WEBWORK_ROOT = curfile->dirname->dirname; +} + +use lib "$ENV{WEBWORK_ROOT}/lib"; + +use WeBWorK::CourseEnvironment; + +use WeBWorK::DB; +use WeBWorK::Utils::CourseManagement qw(listCourses); +use WeBWorK::Utils::CourseDBIntegrityCheck; +use WeBWorK::Utils::CourseDirectoryIntegrityCheck qw( + updateCourseDirectories + updateCourseLinks +); + +sub upgradeCourse { + my ($upgrade_courseID) = @_; + + my $ce = WeBWorK::CourseEnvironment->new({ + webwork_dir => $ENV{WEBWORK_ROOT}, + courseName => $upgrade_courseID, + }); + #warn "do_upgrade_course: updating |$upgrade_courseID| from" , join("|",@upgrade_courseIDs); + ############################################################################# + # Create integrity checker + ############################################################################# + + my @update_report; + my $CIchecker = new WeBWorK::Utils::CourseDBIntegrityCheck($ce); + + ############################################################################# + # Add missing tables and missing fields to existing tables + ############################################################################# + + my ($tables_ok, $dbStatus) = $CIchecker->checkCourseTables($upgrade_courseID); + my @schema_table_names = keys %$dbStatus; # update tables missing from database; + my @tables_to_create = + grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_A() } @schema_table_names; + my @tables_to_alter = + grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseDBIntegrityCheck::DIFFER_IN_A_AND_B() } + @schema_table_names; + push(@update_report, $CIchecker->updateCourseTables($upgrade_courseID, [@tables_to_create])); + + for my $table_name (@tables_to_alter) { + push(@update_report, $CIchecker->updateTableFields($upgrade_courseID, $table_name)); + } + + # update course directories + push(@update_report, @{ updateCourseDirectories($ce) }); + # update course symlinks to libraries + push(@update_report, @{ updateCourseLinks($ce) }); + + if (@update_report) { + for (@update_report) { + if ($_->[1]) { + print "$_->[0]\n"; + } else { + print STDERR "$_->[0]\n"; + } + } + } else { + print "$upgrade_courseID Course Up to Date\n"; + } +} + +my $adminCe = WeBWorK::CourseEnvironment->new({ + webwork_dir => $ENV{WEBWORK_ROOT}, + courseName => 'admin', +}); +my @courseIDs = listCourses($adminCe); +use Data::Dumper; + +for my $courseID (@courseIDs) { + print "-----------------------------------------\n"; + print "Upgrading $courseID\n"; + upgradeCourse($courseID); + print "=========================================\n"; +} diff --git a/bin/update-OPL-statistics.pl b/bin/update-OPL-statistics.pl index dc768d9a83..b772b155b5 100755 --- a/bin/update-OPL-statistics.pl +++ b/bin/update-OPL-statistics.pl @@ -1,25 +1,10 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - use strict; BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } @@ -30,7 +15,6 @@ BEGIN use String::ShellQuote; use DBI; -use WeBWorK::Utils::CourseIntegrityCheck; use WeBWorK::Utils::CourseManagement qw/listCourses/; my $time = time(); @@ -111,7 +95,7 @@ BEGIN print "\n"; } - next if $courseID eq 'admin' || $courseID eq 'modelCourse'; + next if $courseID eq $ce->{admin_course_id} || $courseID eq 'modelCourse'; # we extract the identifying information of the problem, # the status, attempted flag, number of attempts. diff --git a/bin/updateOPLextras.pl b/bin/updateOPLextras.pl index 93d74cc976..31151eb904 100755 --- a/bin/updateOPLextras.pl +++ b/bin/updateOPLextras.pl @@ -71,7 +71,7 @@ =head1 DESCRIPTION BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } diff --git a/bin/upgrade-database-to-utf8mb4.pl b/bin/upgrade-database-to-utf8mb4.pl index a79eb98a06..2d99c84ea6 100755 --- a/bin/upgrade-database-to-utf8mb4.pl +++ b/bin/upgrade-database-to-utf8mb4.pl @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME @@ -126,7 +112,7 @@ =head1 OPTIONS BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } @@ -165,7 +151,7 @@ BEGIN my $dbuser = shell_quote($ce->{database_username}); my $dbpass = $ce->{database_password}; -$ENV{'MYSQL_PWD'} = $dbpass; +local $ENV{MYSQL_PWD} = $dbpass; if (!$no_backup) { # Backup the database @@ -212,7 +198,7 @@ BEGIN }, ); -my $db = new WeBWorK::DB($ce->{dbLayouts}{ $ce->{dbLayoutName} }); +my $db = WeBWorK::DB->new($ce); my @table_types = sort(grep { !$db->{$_}{params}{non_native} } keys %$db); sub checkAndUpdateTableColumnTypes { @@ -222,29 +208,30 @@ sub checkAndUpdateTableColumnTypes { print "\tChecking '$table' (pass $pass)\n" if $verbose; my $schema_field_data = $db->{$table_type}{record}->FIELD_DATA; - for my $field (keys %$schema_field_data) { - my $field_name = $db->{$table_type}{params}{fieldOverride}{$field} || $field; - my @name_type = @{ + for my $field_name (keys %$schema_field_data) { + my @name_type = @{ $dbh->selectall_arrayref( "SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS " . "WHERE TABLE_SCHEMA='$dbname' AND TABLE_NAME='$table' AND COLUMN_NAME='$field_name';" ) }; - print("\t\tThe '$field_name' column is missing from '$table'.\n" - . "\t\tYou should upgrade the course via course administration to fix this.\n" - . "\t\tYou may need to run this script again after doing that.\n"), next - if !exists($name_type[0][0]); + if (!exists($name_type[0][0])) { + print("\t\tThe '$field_name' column is missing from '$table'.\n" + . "\t\tYou should upgrade the course via course administration to fix this.\n" + . "\t\tYou may need to run this script again after doing that.\n"); + next; + } my $data_type = $name_type[0][0]; next if !$data_type; $data_type =~ s/\(\d*\)$// if $data_type =~ /^(big|small)?int\(\d*\)$/; $data_type = lc($data_type); - my $schema_data_type = lc($schema_field_data->{$field}{type} =~ s/ .*$//r); + my $schema_data_type = lc($schema_field_data->{$field_name}{type} =~ s/ .*$//r); if ($data_type ne $schema_data_type) { print "\t\tUpdating data type for column '$field_name' in table '$table'\n" if $verbose; print "\t\t\t$data_type -> $schema_data_type\n" if $verbose; - eval { $dbh->do("ALTER TABLE `$table` MODIFY $field_name $schema_field_data->{$field}{type};"); }; + eval { $dbh->do("ALTER TABLE `$table` MODIFY $field_name $schema_field_data->{$field_name}{type};"); }; my $indent = $verbose ? "\t\t" : ""; die("${indent}Failed to modify '$field_name' in '$table' from '$data_type' to '$schema_data_type.\n" . "${indent}It is recommended that you restore a database backup. Make note of the\n" @@ -286,8 +273,10 @@ sub checkAndChangeTableCharacterSet { my $error = 0; for my $course (@courses) { - print("The course '$course' does not exist on the server\n"), next - if !grep($course eq $_, @server_courses); + if (!grep { $course eq $_ } @server_courses) { + print("The course '$course' does not exist on the server\n"); + next; + } print "Checking tables for '$course'\n" if $verbose; for my $table_type (@table_types) { diff --git a/bin/upgrade_admin_db.pl b/bin/upgrade_admin_db.pl index 57a3ebfe24..505a24e2f4 100755 --- a/bin/upgrade_admin_db.pl +++ b/bin/upgrade_admin_db.pl @@ -1,22 +1,8 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } @@ -24,40 +10,31 @@ BEGIN use lib "$ENV{WEBWORK_ROOT}/lib"; use WeBWorK::CourseEnvironment; - use WeBWorK::DB; -use WeBWorK::Utils::CourseIntegrityCheck; - -########################## -# update admin course -########################## -my $upgrade_courseID = 'admin'; +use WeBWorK::Utils::CourseDBIntegrityCheck; -my $ce = WeBWorK::CourseEnvironment->new({ +# Update admin course +my $ce = WeBWorK::CourseEnvironment->new({ webwork_dir => $ENV{WEBWORK_ROOT} }); +my $upgrade_courseID = $ce->{admin_course_id}; +$ce = WeBWorK::CourseEnvironment->new({ webwork_dir => $ENV{WEBWORK_ROOT}, courseName => $upgrade_courseID, }); -#warn "do_upgrade_course: updating |$upgrade_courseID| from" , join("|",@upgrade_courseIDs); -############################################################################# -# Create integrity checker -############################################################################# +# Create integrity checker my @update_report; -my $CIchecker = new WeBWorK::Utils::CourseIntegrityCheck(ce => $ce); +my $CIchecker = new WeBWorK::Utils::CourseDBIntegrityCheck($ce); -############################################################################# # Add missing tables and missing fields to existing tables -############################################################################# - my ($tables_ok, $dbStatus) = $CIchecker->checkCourseTables($upgrade_courseID); my @schema_table_names = keys %$dbStatus; # update tables missing from database; my @tables_to_create = - grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_A() } @schema_table_names; + grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_A() } @schema_table_names; my @tables_to_alter = - grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseIntegrityCheck::DIFFER_IN_A_AND_B() } @schema_table_names; + grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseDBIntegrityCheck::DIFFER_IN_A_AND_B() } @schema_table_names; push(@update_report, $CIchecker->updateCourseTables($upgrade_courseID, [@tables_to_create])); -foreach my $table_name (@tables_to_alter) -{ #warn "do_upgrade_course: adding new fields to table $table_name in course $upgrade_courseID"; + +for my $table_name (@tables_to_alter) { push(@update_report, $CIchecker->updateTableFields($upgrade_courseID, $table_name)); } diff --git a/bin/upload-OPL-statistics.pl b/bin/upload-OPL-statistics.pl index a90a807b09..928b040682 100755 --- a/bin/upload-OPL-statistics.pl +++ b/bin/upload-OPL-statistics.pl @@ -1,25 +1,10 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - # This script dumps the local OPL statistics table and uploads it. BEGIN { use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); + use Env qw(WEBWORK_ROOT); $WEBWORK_ROOT = curfile->dirname->dirname; } diff --git a/bin/webwork2 b/bin/webwork2 index ed3d264ac4..48ba93bd16 100755 --- a/bin/webwork2 +++ b/bin/webwork2 @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ use strict; use warnings; diff --git a/bin/ww_purge_old_nonces b/bin/ww_purge_old_nonces index 88e5ceb345..d8ee043bd7 100755 --- a/bin/ww_purge_old_nonces +++ b/bin/ww_purge_old_nonces @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME @@ -72,7 +58,7 @@ my $ce = WeBWorK::CourseEnvironment->new({ courseName => $course, }); -my $db = WeBWorK::DB->new($ce->{dbLayout}); +my $db = WeBWorK::DB->new($ce); my @errors; @@ -94,6 +80,6 @@ foreach my $user_id (@listKeys) { if (@errors) { - warn "The following errors occured:\n", map { "* $_\n" } @errors; + warn "The following errors occurred:\n", map { "* $_\n" } @errors; exit 1; } diff --git a/bin/wwdb b/bin/wwdb deleted file mode 100755 index 7857464502..0000000000 --- a/bin/wwdb +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - -=head1 NAME - -wwdb - export and import webwork databases. - -=head1 SYNOPSIS - - wwdb [-f] course { import | export } file [table ...] - -=head1 DESCRIPTION - -Exports data from a course database to an XML file, or imports data from an XML -file to a course database. Optionally restrict which tables are imported or -exported and specify a duplicate policy. - -=head1 OPTIONS - -=over - -=item -f - -Overwite duplicate records. - -=item course - -Course to use for import or export. - -=item { import | export } - -Specify action -- export or import data. - -=item file - -XML file to write to (in the case of export) or read from (in the case of -import). - -=item [table ...] - -If specified, only the listed tables will be imported or exported. - -=back - -=cut - -use strict; -use warnings; -use Getopt::Std; - -BEGIN { - use Mojo::File qw(curfile); - use Env qw(WEBWORK_ROOT); - - $WEBWORK_ROOT = curfile->dirname->dirname; -} - -use lib "$ENV{WEBWORK_ROOT}/lib"; - -use WeBWorK::CourseEnvironment; -use WeBWorK::DB; -use WeBWorK::Utils::DBImportExport qw/listTables dbExport dbImport/; - -sub usage { - print STDERR "usage: $0 [-f] course { import | export } file [table ...]\n"; - print STDERR "tables: ", join(" ", listTables()), "\n"; - exit 1; -} - -our $opt_f; -getopts("f"); - -my ($course, $command, $file, @tables) = @ARGV; - -usage() unless $course and $command and $file; - -my $ce = WeBWorK::CourseEnvironment->new({ - webwork_dir => $ENV{WEBWORK_ROOT}, - courseName => $course, -}); - -my $db = WeBWorK::DB->new($ce->{dbLayout}); - -my @errors; - -if ($command eq "export") { - my $fh; - if ($file eq "-") { - $fh = *STDOUT; - } else { - open $fh, ">", $file or die "failed to open file '$file' for writing: $!\n"; - } - @errors = dbExport( - db => $db, - xml => $fh, - tables => \@tables, - ); - close $fh; -} elsif ($command eq "import") { - my $conflict = ($opt_f ? "replace" : "skip"); - open my $fh, "<", $file or die "failed to open file '$file' for writing: $!\n"; - @errors = dbImport( - db => $db, - xml => $fh, - tables => \@tables, - conflict => $conflict, - ); - close $fh; -} else { - die "$command: unrecognized command.\n"; -} - -if (@errors) { - warn "The following errors occured:\n", map { "* $_\n" } @errors; - exit 1; -} diff --git a/bin/wwsh b/bin/wwsh index 1fc5477fa9..e55e969305 100755 --- a/bin/wwsh +++ b/bin/wwsh @@ -1,18 +1,4 @@ #!/usr/bin/env perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ =head1 NAME @@ -54,7 +40,7 @@ $ce = WeBWorK::CourseEnvironment->new({ }); -$db = WeBWorK::DB->new($ce->{dbLayout}); +$db = WeBWorK::DB->new($ce); print <<'EOF'; wwsh - The WeBWorK Shell diff --git a/conf/LTIConfigValues.config b/conf/LTIConfigValues.config deleted file mode 100644 index 7edc62163f..0000000000 --- a/conf/LTIConfigValues.config +++ /dev/null @@ -1,106 +0,0 @@ -#!perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - -# These are the LTI authentication variables that may be added to the 'LTI' tab -# on the Course Configuration page. These are added by setting the variables -# near the end of authen_LTI.conf. - -# YOU SHOULD NOT NEED TO EDIT THIS FILE!! - -$LTIConfigValues = { - 'LTI{v1p1}{LMS_name}' => { - var => 'LTI{v1p1}{LMS_name}', - doc => x('The name of the LMS'), - doc2 => x( - 'The name of the LMS. This is used in messages to users that direct them to go back to ' - . 'the LMS to access something in the WeBWorK course.' - ), - type => 'text' - }, - 'LTI{v1p1}{LMS_url}' => { - var => 'LTI{v1p1}{LMS_url}', - doc => x('A URL for the LMS'), - doc2 => x( - 'An address that can be used to log in to the LMS. This is used in messages to users ' - . 'that direct them to go back to the LMS to access something in the WeBWorK course.' - ), - type => 'text', - width => 30, - }, - external_auth => { - var => 'external_auth', - doc => x('Require users to log in through the LMS'), - doc2 => x( - 'If this is set, all users (including the instructor) must enter the WeBWorK course through the LMS. If ' - . 'a user reaches the regular WeBWorK login screen, they receive a message directing them back to ' - . 'the LMS.' - ), - type => 'boolean' - }, - LTIGradeMode => { - var => 'LTIGradeMode', - doc => x('Grade passback mode'), - doc2 => x( - 'Sets how grades will be passed back from WeBWorK to the LMS.
course
Sends a single ' - . 'grade back to the LMS. This grade is calculated out of the total question set that has been ' - . 'assigned to a user and made open. Therefore it can appear low, since it counts problem sets with ' - . 'future due dates as zero.
homework
Sends back a score for each problem set ' - . '(including for each quiz). To use this, the external links from the LMS must be problem set ' - . 'specific. For example, webwork.myschool.edu/webwork2/course-name/problem_set_name. ' - . 'If the problem set name has space characters, they should be underscores in these addresses. ' - . 'Also, to initialize the communication between WeBWorK and the LMS, the user must follow each of ' - . 'these external learning tools at least one time. Since there must be a separate external tool link ' - . 'for each problem set, this option requires more maintenance of the LMS course.
' - ), - values => [ '', qw(course homework) ], - labels => { '' => 'None', 'course' => 'Course', 'homework' => 'Homework' }, - type => 'popuplist' - }, - LMSManageUserData => { - var => 'LMSManageUserData', - doc => x('Allow the LMS to update user account data'), - doc2 => x( - 'WeBWorK will automatically create users when logging in via the LMS for the first time. If this flag is ' - . 'enabled then it will also keep the user account data (first name, last name, section, recitation) ' - . 'up to date with the LMS. If a user\'s information changes in the LMS then it will change in ' - . 'WeBWorK. However, any changes to the user data via WeBWorK will be overwritten the next time the ' - . 'user logs in.' - ), - type => 'boolean' - }, - debug_lti_parameters => { - var => 'debug_lti_parameters', - doc => x('Show LTI parameters (for debugging)'), - doc2 => x( - 'When this is true, then when a user enters WeBWorK from an external tool link in the LMS, the bottom of ' - . 'the screen will display the data that the LMS passed to WeBWorK. This may be useful to debug LTI, ' - . 'especially because different LMS systems have different parameters.' - ), - type => 'boolean' - }, -}; - -$LTIConfigValues->{'LTI{v1p3}{LMS_name}'} = - { %{ $LTIConfigValues->{'LTI{v1p1}{LMS_name}'} }, var => 'LTI{v1p3}{LMS_name}' }; -$LTIConfigValues->{'LTI{v1p3}{LMS_url}'} = - { %{ $LTIConfigValues->{'LTI{v1p1}{LMS_url}'} }, var => 'LTI{v1p3}{LMS_url}' }; - -if (@LTIConfigVariables && !(grep { $_->[0] eq 'LTI' } @$ConfigValues)) { - push(@$ConfigValues, - [ x('LTI'), map { $LTIConfigValues->{$_} } grep { defined $LTIConfigValues->{$_} } @LTIConfigVariables ]); -} - -1; # final line of the file to reassure perl that it was read properly. diff --git a/conf/README.md b/conf/README.md index 4ef53a321d..45ebac0def 100644 --- a/conf/README.md +++ b/conf/README.md @@ -16,8 +16,6 @@ Basic webwork2 configuration files. - `localOverrides.conf.dist` should be copied to `localOverrides.conf`. `localOverrides.conf` will be read after the `defaults.config` file is processed and will overwrite configurations in `defaults.config`. Use this file to make changes to the settings in `defaults.config`. -- `database.conf.dist` contains database configuration parameters. It is included by `defaults.config`. This file - should not be copied or modified unless you really know what you are doing. Configuration extension files. diff --git a/conf/authen_CAS.conf.dist b/conf/authen_CAS.conf.dist index 67f482b21e..4095283e96 100644 --- a/conf/authen_CAS.conf.dist +++ b/conf/authen_CAS.conf.dist @@ -8,9 +8,16 @@ ######################################################################################## # Set CAS as the authentication module to use. -$authen{user_module} = { - "*" => "WeBWorK::Authen::CAS", -}; +$authen{user_module} = 'WeBWorK::Authen::CAS'; + +# List of authentication modules that may be used to enter the admin course. +# This is used instead of $authen{user_module} when logging into the admin course. +# Since the admin course provides overall power to add/delete courses, access +# to this course should be protected by the best possible authentication you +# have available to you. +$authen{admin_module} = [ + 'WeBWorK::Authen::CAS' +]; $authen{cas_options} = { # Options to pass to the AuthCAS module. diff --git a/conf/authen_LTI.conf.dist b/conf/authen_LTI.conf.dist index 091bdc4e89..20196f4f49 100644 --- a/conf/authen_LTI.conf.dist +++ b/conf/authen_LTI.conf.dist @@ -4,33 +4,33 @@ # Configuration for using LTI authentication. # To enable this file, uncomment the appropriate lines in localOverrides.conf # The settings in this file apply to both LTI 1.1 and LTI 1.3 authentication. -# The settings specific to the LTI 1.1 authenticatio are in authen_LTI_1_1.conf. -# The settings specific to the LTI 1.3 authenticatio are in authen_LTI_1_3.conf. +# The settings specific to the LTI 1.1 authentication are in authen_LTI_1_1.conf. +# The settings specific to the LTI 1.3 authentication are in authen_LTI_1_3.conf. ################################################################################################ -# Set debug_lti_parameters to 1 to have LTI calling parameters printed to HTML page for -# debugging. This is useful when setting things up for the first time because different LMS -# systems have different parameters +# Set debug_lti_parameters to 1 to enable LTI debugging. This is useful when setting things up +# for the first time because different LMS systems have different parameters. Note that for LTI +# 1.1 these debug messages will be displayed in the HTML page. However, for LTI 1.3 none of the +# debug messages will be displayed in the HTML page due to the nature of how LTI 1.3 +# authentication works with automatic form submissions and redirects. These messages can be +# found in the webwork2 app log in that case. $debug_lti_parameters = 0; -# To get more information on passing grades back to the LMS enmass set debug_lti_grade_passback +# To get more information on passing grades back to the LMS en mass set debug_lti_grade_passback # to one. And set the LTIMassUpdateInterval to 60 (seconds). $debug_lti_grade_passback = 0; # This will print into the webwork2 app log the success or failure of updating each user/set. -# Setting both debug_lti_parameters and debug_lti_grade_passback will cause the full request and -# response between the LMS and WW to be printed into webwork2 app log file for each user/set -# update of the grade. +# Setting both debug_lti_parameters and debug_lti_grade_passback will cause the full requests +# and responses between the LMS and WW to be printed into webwork2 app log file for each +# user/set update of the grade. # The switches above can be set in course.conf to enable debugging for just one course. # If you want even more information enable the debug facility in the webwork2.mojolicious.yml # file. This will print extensive debugging messages for all courses. -# Note that for LTI 1.3 not all debug message will make it back to the HTML page due to the -# nature of how LTI 1.3 authentication works with automatic form submissions and redirects. - ################################################################################################ # Authentication settings ################################################################################################ @@ -40,9 +40,20 @@ $debug_lti_grade_passback = 0; # the LTIAdvantage will be used. If you know a site will not use one or the other, it can be # commented out. Failover to Basic_TheLastOption is necessary to authenticate with cookie keys. $authen{user_module} = [ - { '*' => 'WeBWorK::Authen::LTIAdvantage' }, # first try LTI 1.3 - { '*' => 'WeBWorK::Authen::LTIAdvanced' }, # next try LTI 1.1 - { '*' => 'WeBWorK::Authen::Basic_TheLastOption' } # fallback authorization method + 'WeBWorK::Authen::LTIAdvantage', # first try LTI 1.3 + 'WeBWorK::Authen::LTIAdvanced', # next try LTI 1.1 + 'WeBWorK::Authen::Basic_TheLastOption' # fallback authorization method +]; + +# List of authentication modules that may be used to enter the admin course. +# This is used instead of $authen{user_module} when logging into the admin course. +# Since the admin course provides overall power to add/delete courses, access +# to this course should be protected by the best possible authentication you +# have available to you. +$authen{admin_module} = [ + #'WeBWorK::Authen::LTIAdvantage', + #'WeBWorK::Authen::LTIAdvanced', + 'WeBWorK::Authen::Basic_TheLastOption' ]; # Include configurations. You must uncomment at least one of the following. You may uncomment @@ -104,7 +115,7 @@ $external_auth = 0; # address should be the address of that set in the Course. Students will receive a grade for # each Link/Assignment which is determined by their percentage homework grade on the Set which # the Link/Assignment points to. Students need to use the Link/Assignment in the LMS at least -# once to enable grade passback. In particular when working in this mode it is recommended that +# once to enable grade pass back. In particular when working in this mode it is recommended that # you only allow students to log in via the LMS. # Note: For both of these modes only the grades are passed back. In particular nothing else @@ -115,7 +126,7 @@ $external_auth = 0; # Site Administrator Note for LTI 1.3: This uses OAuth2 RSA private/public keys. These keys # are automatically generated the first time that they are needed. It is recommended that new # keys are generated on a regular basis. At this point, key rotation is not automatic for -# webwork2. Howevever, it is simple. Delete the files $webwork2_dir/DATA/lti_private_key.json +# webwork2. However, it is simple. Delete the files $webwork2_dir/DATA/lti_private_key.json # and $webwork2_dir/DATA/lti_public_key.json. New keys will then be automatically generated the # next time they are needed. Probably a good rule of thumb (for now) is to do this at the # beginning of every term. @@ -124,19 +135,84 @@ $LTIGradeMode = ''; #$LTIGradeMode = 'course'; #$LTIGradeMode = 'homework'; -# When set this variable sends grades back to the LMS every time a user submits an answer. This -# keeps students grades up to date but can be a drain on the server. -$LTIGradeOnSubmit = 1; +# There are several controls for when to report scores to the LMS. Sometimes these controls +# interact with each other, and the details of how they work may depend on whether $LTIGradeMode +# is set to 'course' or 'homework'. So it is recommended to understand all of them and then +# decide how to set them. -# If CheckPrior is set to 1 then the current LMS grade will be checked first, and if the grade -# has not changed then the grade will not be updated. This is intended to reduce changes to LMS -# records when no real grade change occurred. It requires a 2 round process, first querying the -# current grade from the LMS and then when needed making the grade submission. +# If $LTICheckPrior is 1, then any time WeBWorK is about to send a score to the LMS, it will +# first request from the LMS what that score currently is. Then if there is no significant +# difference between the LMS score and the WeBWorK score, WeBWorK will not follow through with +# updating the LMS score. This is to avoid frequent insignificant updates to a student's scores +# in the LMS. With some LMSs, students may receive notifications each time a score is updated, +# and setting this variable will prevent too many notifications for them. This does create a +# two-phase process, first querying the current score from the LMS and then actually updating +# the score (if there is a significant difference). + +# Additional details: +# - If the LMS score is not 100%, but the WeBWorK score is, then even if the LMS score is only +# insignificantly less than 100%, it will be updated anyway. +# - If the LMS score is null and the WeBWorK score is 0, this is considered an insignificant +# difference and the LMS score will not be updated to 0. However if it is after the +# $LTISendScoresAfterDate (described below), then the null score will be updated to 0 anyway. +# - "Significant" means an absolute difference of 0.001, or 0.1%. At this time this is not +# configurable. $LTICheckPrior = 0; -# The system periodically updates student grades on the LMS. This variable controls how often -# that happens. Set to -1 to disable. -$LTIMassUpdateInterval = 86400; #in seconds +# If $LTIGradeOnSubmit is set to 1, then each time a user submits an answer or scores a test, +# that will trigger WeBWorK possibly reporting a score to the LMS. See $LTICheckPrior for one +# reason that WeBWorK might not ultimately send a score. But there are other reasons too. +# WeBWorK will send the score (the assignment's score if $LTIGradeMode is 'homework' or the +# overall course score if $LTIGradeMode is 'course') to the LMS only if either the assignment's +# $LTISendGradesEarlyThreshold (described below) has been met or if it is past that assignment's +# $LTISendScoresAfterDate (also described below). +$LTIGradeOnSubmit = 1; + +# In addition to scores possibly being sent to the LMS upon submission, they can be sent by an +# instructor or admin user using the LTI Grades Update Tool. And thirdly, the system can +# periodically update student scores on the LMS on its own. For all three possible triggers for +# scores to be passed to the LMS, $LTISendScoresAfterDate and $LTISendGradesEarlyThreshold can +# affect what is sent. $LTISendScoresAfterDate can be 'open_date', 'reduced_scoring_date', +# 'due_date', 'answer_date', or 'never'. For a given assignment, if it is after the +# $LTISendScoresAfterDate, then WeBWorK will send scores. If $LTISendScoresAfterDate is 'never', +# then there is no date after which WeBWorK is guaranteed to send scores. In that case, scores +# are only sent when a set's $LTISendGradesEarlyThreshold is met (see below). +# - For 'course' grade passback mode, the assignment will be included in the overall course +# grade calculation. +# - For 'homework' grade passback mode, the assignment's score will be sent. + +# If $LTISendScoresAfterDate is 'reduced_scoring_date' and an assignment has no reduced scoring +# date or reduced scoring is disabled for that assignment, the fallback is to use the due date. + +# For a given assignment, if $LTISendScoresAfterDate is 'never' or if it is before the date +# specified by $LTISendScoresAfterDate, WeBWorK may send a score to the LMS depending on the +# value of $LTISendGradesEarlyThreshold. This variable can either be the string 'attempted' or a +# number from 0 to 1. If this variable is 'attempted', a given set must have been attempted for +# the threshold to have been met, and then the score can be used even if it is before the +# $LTISendScoresAfterDate. For a non-test set, 'attempted' just means that some exercise in the +# set was attempted using the Submit button. For a test, 'attempted' means that either there is +# one version with a graded submission, or there are at least two versions. + +# If $LTISendGradesEarlyThreshold is a number from 0 to 1, the score for an assignment needs to +# have reached that number for the threshold to be met, and then the score can be used even if +# it is before the $LTISendScoresAfterDate. + +#$LTISendScoresAfterDate = 'open_date'; +$LTISendScoresAfterDate = 'reduced_scoring_date'; +#$LTISendScoresAfterDate = 'due_date'; +#$LTISendScoresAfterDate = 'answer_date'; +#$LTISendScoresAfterDate = 'never'; + +$LTISendGradesEarlyThreshold = 'attempted'; +#$LTISendGradesEarlyThreshold = 0; +#$LTISendGradesEarlyThreshold = 0.7; +#$LTISendGradesEarlyThreshold = 1; + +# The system periodically updates student scores on the LMS. If it has been at least this many +# seconds since the last mass passback event and someone in the course does anything to load a +# page, then a new mass passback job will begin. Set this to -1 to disable mass passback. +$LTIMassUpdateInterval = 86400; + ################################################################################################ # Add an 'LTI' tab to the Course Configuration page @@ -147,9 +223,11 @@ $LTIMassUpdateInterval = 86400; #in seconds # to some of the LTI settings. You may leave some of the variables commented out if you would # like to omit them from the options in this tab. If all variables are left commented out, then # the tab will not be shown. Note that the default values for the variables that will be shown -# in the LTI tab are the values that are set above. Further note that only the variables listed -# in LTIConfigValues.config may be added to the LTI config tab. In addition, only the variables -# that pertain to the active LTI version will be shown in the tab. +# in the LTI tab are the values that are set above. Further note that only the commented out +# variables listed below may be added to the LTI config tab. In addition, only the variables that +# pertain to the active LTI version will be shown in the tab. Warning: Allowing users to modify +# the BasicConsumerSecret for LTI 1.1 or the IDs, URLs, etc for LTI 1.3 can expose the values +# of the variables and allow users to lock themselves out of logging in via an LMS. @LTIConfigVariables = ( #'LTI{v1p1}{LMS_name}', #'LTI{v1p3}{LMS_name}', @@ -157,8 +235,38 @@ $LTIMassUpdateInterval = 86400; #in seconds #'LTI{v1p3}{LMS_url}', #'external_auth', #'LTIGradeMode', + #'LTICheckPrior', + #'LTIGradeOnSubmit', + #'LTISendScoresAfterDate', + #'LTISendGradesEarlyThreshold', + #'LTIMassUpdateInterval', #'LMSManageUserData', - #'debug_lti_parameters' + #'LTI{v1p1}{BasicConsumerSecret}', + #'LTI{v1p3}{PlatformID}', + #'LTI{v1p3}{ClientID}', + #'LTI{v1p3}{DeploymentID}', + #'LTI{v1p3}{PublicKeysetURL}', + #'LTI{v1p3}{AccessTokenURL}', + #'LTI{v1p3}{AccessTokenAUD}', + #'LTI{v1p3}{AuthReqURL}', + #'debug_lti_parameters', + #'lms_context_id' ); +# By default only admin users can modify the LTI secrets and lms_context_id. The following +# permissions need to be modified to allow other users the permission to modify the values. +#$permissionLevels{'change_config_LTI{v1p1}{BasicConsumerSecret}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{PlatformID}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{ClientID}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{DeploymentID}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{PublicKeysetURL}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{AccessTokenURL}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{AccessTokenAUD}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{AuthReqURL}'} = "admin", +#$permissionLevels{'change_config_lms_context_id'} = "admin", + +# Note that the lms_context_id is actually a database setting. It must be set for a course in +# order for the instructor to utilize LTI content selection. This can also be set in the admin +# course. + 1; # final line of the file to reassure perl that it was read properly. diff --git a/conf/authen_LTI_1_1.conf.dist b/conf/authen_LTI_1_1.conf.dist index 2353b66a28..c2c8bb7f62 100644 --- a/conf/authen_LTI_1_1.conf.dist +++ b/conf/authen_LTI_1_1.conf.dist @@ -7,8 +7,11 @@ # This is a string that is used to name the LMS for end users, for example in a message telling # users to sign in through their LMS. -$LTI{v1p1}{LMS_name} = 'e.g., Blackboard, Canvas, Moodle, etc.'; -#$LTI{v1p1}{LMS_name} = 'Desire2Learn'; +$LTI{v1p1}{LMS_name} = 'the LMS'; +#$LTI{v1p1}{LMS_name} = 'Blackboard'; +#$LTI{v1p1}{LMS_name} = 'Canvas'; +#$LTI{v1p1}{LMS_name} = 'D2L Brightspace'; +#$LTI{v1p1}{LMS_name} = 'Moodle'; # This is a URL that should take users to a place they can log in to their LMS. It will use the # text from LMS_name, but use LMS_url as the href. If LMS_url is empty or undefined, the @@ -123,6 +126,18 @@ $LTI{v1p1}{preferred_source_of_student_id} = ''; # You should choose your own secret word for security and should treat it like a password. $LTI{v1p1}{BasicConsumerSecret} = ''; +# The consumer key is entered in the LMS request form, and needs to match the entry here if this +# is set. This is only used for content item selection requests from an LMS, and this does not +# even need to be set for that unless there are multiple courses from different LMS's that have +# the same LMS context id and both use a course on this webwork2 server. In that case each LMS +# must use a different consumer key, and the correct consumer keys should be set in the +# course.conf file for each course. If this server is a tool provider for multiple LMS's, then +# it is recommended that this be set. Usually it is not useful to set this here. However, if +# most courses use one LMS and only a few use another, then the consumer key for the first LMS +# could be set here, and then this would only need to be set in the course.conf files for the +# few courses use a different LMS. +#$LTI{v1p1}{ConsumerKey} = 'webwork'; + # The purpose of the LTI nonces is to prevent man-in-the-middle attacks. The NonceLifeTime (in # seconds) must be short enough to prevent at least casual man-in-the-middle attacks but long # enough to accommodate normal server and networking delays (and perhaps non-synchronization of @@ -212,7 +227,4 @@ $LTI{v1p1}{LMSrolesToWeBWorKroles} = { # $userSet->answer_date($niceAnswerTime); #}; -# Do not change this. -$LTI{v1p1}{grader} = 'WeBWorK::Authen::LTIAdvanced::SubmitGrade'; - 1; # final line of the file to reassure perl that it was read properly. diff --git a/conf/authen_LTI_1_3.conf.dist b/conf/authen_LTI_1_3.conf.dist index 7d5a668b9c..823155e1cc 100644 --- a/conf/authen_LTI_1_3.conf.dist +++ b/conf/authen_LTI_1_3.conf.dist @@ -7,8 +7,11 @@ # This is a string that is used to name the LMS for end users, for example in a message telling # users to sign in through their LMS. -$LTI{v1p3}{LMS_name} = 'e.g., Blackboard, Canvas, Moodle, etc.'; -#$LTI{v1p3}{LMS_name} = 'Desire2Learn'; +$LTI{v1p3}{LMS_name} = 'the LMS'; +#$LTI{v1p3}{LMS_name} = 'Blackboard'; +#$LTI{v1p3}{LMS_name} = 'Canvas'; +#$LTI{v1p3}{LMS_name} = 'D2L Brightspace'; +#$LTI{v1p3}{LMS_name} = 'Moodle'; # This is a URL that should take users to a place they can log in to their LMS. It will use the # text from LMS_name, but use LMS_url as the href. If LMS_url is empty or undefined, the @@ -100,6 +103,7 @@ $LTI{v1p3}{ClientID} = ''; $LTI{v1p3}{DeploymentID} = ''; $LTI{v1p3}{PublicKeysetURL} = ''; $LTI{v1p3}{AccessTokenURL} = ''; +$LTI{v1p3}{AccessTokenAUD} = ''; $LTI{v1p3}{AuthReqURL} = ''; # In the process of LTI 1.3 authentication a request is sent to the LMS in response to its @@ -140,6 +144,12 @@ $LTI{v1p3}{LMSrolesToWeBWorKroles} = { 'Grader' => 'ta', }; +# The LMS reports roles context (or membership), instititution, and system +# roles. WeBWorK always ignores system roles, and also ignores institution +# roles by default. In some cases you may also want to consider institution +# roles. In that case set the following to 1. +$LTI{v1p3}{AllowInstitutionRoles} = 0; + ################################################################################################ # Local routine to modify users ################################################################################################ @@ -185,7 +195,18 @@ $LTI{v1p3}{LMSrolesToWeBWorKroles} = { # $userSet->answer_date($niceAnswerTime); #}; -# Do not change this. -$LTI{v1p3}{grader} = 'WeBWorK::Authen::LTIAdvantage::SubmitGrade'; +################################################################################################ +# Miscellaneous +################################################################################################ + +# When grade passback mode is 'homework', someone must use a set-specific link from the LMS in +# order for grade passback to begin happening for that set. Use of the set-specific link lets +# WeBWorK store the set's "sourced_ID". So if there is no sourced_ID, the default behavior is +# that a user in WeBWorK sees the sets as disabled and there is a message about needing to +# access the set from the LMS. The following option can be set to allow users to work on the set +# anyway. There will be no grade passback until some later time when an LMS user clicks the +# set-specific link. In some LMSs, it is possible for the instructor to activate the link. + +$LTI{v1p3}{ignoreMissingSourcedID} = 0; 1; # final line of the file to reassure perl that it was read properly. diff --git a/conf/authen_ldap.conf.dist b/conf/authen_ldap.conf.dist index e895334729..45cc46179a 100644 --- a/conf/authen_ldap.conf.dist +++ b/conf/authen_ldap.conf.dist @@ -8,9 +8,16 @@ ######################################################################################## # Set LDAP as the authentication module to use. -$authen{user_module} = { - "*" => "WeBWorK::Authen::LDAP", -}; +$authen{user_module} = 'WeBWorK::Authen::LDAP'; + +# List of authentication modules that may be used to enter the admin course. +# This is used instead of $authen{user_module} when logging into the admin course. +# Since the admin course provides overall power to add/delete courses, access +# to this course should be protected by the best possible authentication you +# have available to you. +$authen{admin_module} = [ + 'WeBWorK::Authen::LDAP' +]; $authen{ldap_options} = { # hosts to attempt to connect to, in order. For example: @@ -32,23 +39,23 @@ $authen{ldap_options} = { # Edit the data below: net_ldap_base => "ou=people,dc=myschool,dc=edu", - # Use a Bind account if set to 1 - bindAccount => 0, + # Use a Bind account if set to 1 + bindAccount => 0, - searchDN => "cn=search,DC=youredu,DC=edu", - bindPassword => "password", + searchDN => "cn=search,DC=youredu,DC=edu", + bindPassword => "password", # The LDAP module searches for a DN whose RDN matches the username # entered by the user. The net_ldap_rdn setting tells the LDAP # backend what part of your LDAP schema you want to use as the RDN. # The correct value for net_ldap_rdn will depend on your LDAP setup. - # + # Uncomment this line if you use Active Directory. #net_ldap_rdn => "sAMAccountName", - # + # Uncomment this line if your schema uses uid as an RDN. #net_ldap_rdn => "uid", - # + # By default, net_ldap_rdn is set to "sAMAccountName". # If failover = "all", then all LDAP failures will be checked @@ -60,4 +67,4 @@ $authen{ldap_options} = { failover => "all", }; -1; #final line of the file to reassure perl that it was read properly. +1; #final line of the file to reassure perl that it was read properly. diff --git a/conf/authen_saml2.conf.dist b/conf/authen_saml2.conf.dist new file mode 100644 index 0000000000..a4403134a1 --- /dev/null +++ b/conf/authen_saml2.conf.dist @@ -0,0 +1,137 @@ +#!perl +################################################################################ +# Configuration for using Saml2 authentication. +# To enable Saml2 authentication, copy this file to conf/authen_saml2.conf +# and uncomment the appropriate lines in localOverrides.conf. The Saml2 +# authentication module uses the Net::SAML2 library. The library claims to be +# compatible with a wide range of SAML2 implementations, including Shibboleth. +################################################################################ + +# Set Saml2 as the authentication module to use. +# Comment out 'WeBWorK::Authen::Basic_TheLastOption' if bypassing Saml2 +# authentication is not allowed (see $saml2{bypass_query} below). +$authen{user_module} = [ + 'WeBWorK::Authen::Saml2', + 'WeBWorK::Authen::Basic_TheLastOption' +]; + +# List of authentication modules that may be used to enter the admin course. +# This is used instead of $authen{user_module} when logging into the admin +# course. Since the admin course provides overall power to add/delete courses, +# access to this course should be protected by the best possible authentication +# you have available to you. +$authen{admin_module} = [ + 'WeBWorK::Authen::Saml2' +]; + +# This URL query parameter can be added to the end of a course url to skip the +# saml2 authentication module and go to the next one, for example, +# http://your.school.edu/webwork2/courseID?bypassSaml2=1. Comment out the next +# line to disable this feature. +$saml2{bypass_query} = 'bypassSaml2'; + +# Note that Saml2 authentication can be used in conjunction with webwork's two +# factor authentication. If the identity provider does not provide two factor +# authentication, then it is recommended that you DO use webwork's two factor +# authentication. If the identity provider does provide two factor +# authentication, then you would not want your users need to perform two factor +# authentication twice, so you should disable webwork's two factor +# authentication. The two factor authentication settings are set in +# localOverrides.conf. + +# As noted above, if the identity provider offers two factor authentication, +# then you would not want webwork2's two factor authentication to be used at the +# same time. However, if the bypass parameter is allowed, you should still +# enable two factor authentication in that case. If this is the case, then set +# $saml2{twoFAOnlyWithBypass} to 1. This will skip webwork2's two factor +# authentication for users signing in via the identity provider, but still +# require it for users signing in with a username/password. If this is set to 0, +# then webwork2's two factor authentication will always be required. +$saml2{twoFAOnlyWithBypass} = 0; + +# If $external_auth is 1, and the authentication sequence reaches +# Basic_TheLastOption, then the webwork login screen will show a message +# directing the user to use the external authentication system to login. This +# prevents users from attempting to login in to WeBWorK directly. +$external_auth = 0; + +# The $saml2{idps} hash contains names of identity proviers and their SAML2 +# metadata URLs that are used by this server. Webwork will request the identity +# provider's metadata from the URL of the $saml2{active_idp} during the +# authentication process. Additional identity providers can also be added for a +# particular course by adding, for example, $saml2{idps}{other_idp} = '...' to +# the course.conf file of the course. Note that the names of the identity +# providers in this hash are used for a directory name in which the metadata and +# certificate for the identity provider are saved. So the names should only +# contain alpha numeric characters and underscores. +$saml2{idps} = { + default => 'http://idp/simplesaml/module.php/saml/idp/metadata', + # Add additional identity providers used by this server below. + #other_idp => 'http://other.idp.server/metadata', +}; + +# The $saml2{active_idp} is the identity provider in the $saml2{idps} hash that +# will be used. If different identity providers are used for different courses, +# then set $saml2{active_idp} = 'other_idp' in the course.conf file of each +# course. +$saml2{active_idp} = 'default'; + +# This the id for the webwork2 service provider. This is usually the application +# root URL plus the base path to the service provider. +$saml2{sp}{entity_id} = 'http://localhost:8080/webwork2/saml2'; + +# This is the organization metadata information for the webwork2 service +# provider. The Saml2 authentication module will generate xml metadata that can +# be obtained by the identity provider for configuration from the URL +# https://webwork.yourschool.edu/webwork2/saml2/metadata if Saml2 authentication +# is enabled site wide. The URL needs to have the courseID URL parameter added +# if Saml2 authentication is not enabled site wide, but is enabled for some +# courses in those course's course.conf files. So for example if one course is +# myTestCourse, then the metadata URL would be +# https://webwork.yourschool.edu/webwork2/saml2/metadata?courseID=myTestCourse +# Further note that if multiple courses use that same identity provider then +# just pick any one of the courses to use in the metadata URL. All of the other +# courses share the same metedata. +$saml2{sp}{org} = { + contact => 'webwork@example.edu', + name => 'webwork', + url => 'https://localhost:8080/', + display_name => 'WeBWorK' +}; + +# The following list of attributes will be checked in the given order for a +# matching user in the webwork2 course. If no attributes are given, then +# webwork2 will default to the NameID. It is recommended that you use the +# attribute's OID. +$saml2{sp}{attributes} = [ + 'urn:oid:0.9.2342.19200300.100.1.1' +]; + +# The following settings are the locations of the files that contain the +# certificate and private key for the webwork2 service provider. A certificate +# and private key can be generated using openssl. For example, +# openssl req -newkey rsa:3072 -new -x509 -days 3652 -nodes -out saml.crt -keyout saml.pem +# The files saml.crt and saml.pem that are generated contain the public +# "certificate" and the "private_key", respectively. +# Note that if the files are placed within the root webwork2 app directory, then +# the paths may be given relative to the root webwork2 app directory. Otherwise +# the absolute path must be given. Make sure that the webwork2 app has read +# permissions for those files. +$saml2{sp}{certificate_file} = 'docker-config/idp/certs/saml.crt'; +$saml2{sp}{private_key_file} = 'docker-config/idp/certs/saml.pem'; + +############################################################################## +# SECURITY WARNING +# For production, you MUST provide your own unique 'certificate' and +# 'private_key' files. The files referred to in the default settings above are +# only intended to be used in development, and are publicly exposed. Hence, they +# provide NO SECURITY. +############################################################################## + +# If this is set to 1, then service provider initiated logout from the identity +# provider is enabled. This means that when the user clicks the webwork2 "Log +# Out" button, a request is sent to the identity provider that also ends the +# session for the user with the identity provider. +$saml2{sp}{enable_sp_initiated_logout} = 0; + +1; diff --git a/conf/authen_saml2.dist.yml b/conf/authen_saml2.dist.yml deleted file mode 100644 index 700c26a255..0000000000 --- a/conf/authen_saml2.dist.yml +++ /dev/null @@ -1,119 +0,0 @@ ---- -################################################################################ -# Configuration for the Saml2 plugin -# -# To enable the Saml2 plugin, copy authen_saml2.dist.yml to authen_saml2.yml -# -# The Saml2 plugin uses the Net::SAML2 library, the library claims to be -# compatible with a wide range of SAML2 implementations, including Shibboleth. -################################################################################ - -# add this query to the end of a course url to skip the saml2 authen module -# and go to the next one, comment out to disable this feature -bypass_query: bypassSaml2 -idp: # this is the central SAML2 server - # url where we can get the SAML2 metadata xml for the IdP - metadata_url: http://idp.example.edu/saml2/idp/metadata -sp: # this is the Webwork side - # also known as iss (issuer) - entity_id: https://webwork.example.edu/saml2 - # endoints created by the plugin, relative to the webwork root url - route: - base: '/saml2' # prefix path for all URLs handled by the plugin - metadata: '/metadata' # actual path would be /saml2/metadata - # 'Assertion Consumer Service', basically handles the SAML response, plugin - # only supports a POST response (HTTP POST Binding) - acs: - post: '/acs/post' - # Ideally, there would be a way to have separate app info and org info but - # Net::SAML2's metadata generation doesn't seem to have that separation. So - # I've filled out the org info with app info instead. - org: - contact: 'webwork@example.edu' - name: 'webwork' - url: 'https://webwork.docker:8080/' - display_name: 'WeBWorK' - # list of attributes that can be used as the username, each of them will be - # tried in turn to see if there's a matching user in the classlist. If no - # attributes are given, then we'll default to the NameID - attributes: - - 'studentNumber' - ############################################################################## - # SECURITY WARNING - # For production, you MUST generate your own unique 'cert' and 'signing_key'. - # The examples below are publicly exposed and thus provides NO SECURITY. - ############################################################################## - # Cert and key pairs can be generated using an openssl command such as: - # openssl req -newkey rsa:3072 -new -x509 -days 3652 -nodes -out webwork.crt -keyout webwork.pem - # Where webwork.crt contains the cert and webwork.pem contains the signing_key - cert: | - -----BEGIN CERTIFICATE----- - MIIE7zCCA1egAwIBAgIUIteyNYLSAiB0FcNl0GLJNYRppk8wDQYJKoZIhvcNAQEL - BQAwgYYxCzAJBgNVBAYTAkFBMQswCQYDVQQIDAJBQTEQMA4GA1UEBwwHRXhhbXBs - ZTEQMA4GA1UECgwHRXhhbXBsZTEQMA4GA1UECwwHRXhhbXBsZTEQMA4GA1UEAwwH - RXhhbXBsZTEiMCAGCSqGSIb3DQEJARYTZXhhbXBsZUBleGFtcGxlLmVkdTAeFw0y - NDA1MDMwMTA2MzNaFw0zNDA1MDMwMTA2MzNaMIGGMQswCQYDVQQGEwJBQTELMAkG - A1UECAwCQUExEDAOBgNVBAcMB0V4YW1wbGUxEDAOBgNVBAoMB0V4YW1wbGUxEDAO - BgNVBAsMB0V4YW1wbGUxEDAOBgNVBAMMB0V4YW1wbGUxIjAgBgkqhkiG9w0BCQEW - E2V4YW1wbGVAZXhhbXBsZS5lZHUwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGK - AoIBgQC45DCHUejzAeq+eVwEX5zSQWC+kqydEmoxpydT4YiSXnNeoNAkilKfHGOY - Uc4djwx148N14A+S0GCys2j3Ey2wuL7DSep5y1Z9Uxj6Ayg23XGIFFFJJLMy1Qfe - pjCcr1djPH9PpwglG1nTsiWqvHGGc3WWn1u6RfyrCf+jxbhygNRTA+LVPpqNvko6 - MWKsbVLKrYMV2kPcQ0PQByNHJnjBy3KH2k99lS20h32sgHbgbVpJWdAWjeyJrOh9 - aDt4/AfK90BvhkjF4BuQ+Jw5oIwMhbx7YmzIfiJmBLLaGjVRppuoAQtLX9uLst9l - aLZzaeutg+G3RUYcvDMlnP7cU8Sq4BD7uK0ChKxxCMFcihAhQ8wqCKncaaE9WqPs - CM16SB/6xptOxoLcg/5q3PJyUi2g4VDKXuQc6AURKIJxSM9nlrcv/R7fCFgk3Nj/ - piWykDk6/BDWFpEHaj+NnFE9ZIxKr9CjTxdmqiDTyqSv50rNCjleyL/iASBTSCCF - OPVOYQECAwEAAaNTMFEwHQYDVR0OBBYEFGs8F3VIGSEk+DE2MBqqNKX6UuZTMB8G - A1UdIwQYMBaAFGs8F3VIGSEk+DE2MBqqNKX6UuZTMA8GA1UdEwEB/wQFMAMBAf8w - DQYJKoZIhvcNAQELBQADggGBAIpDktpfGH7ZqgdWvxbJrjekb1IyCGrWsHOYSjwM - +MxnhAA6oY63wC04a2i31zIMNOkY9F0tAdd4uDchxA9IWHqpb7t7zBlZdDabPPC3 - WoDYnKhtZBULVVo7AvWO0UJGfZNJE393aKer3ePvfoG0OpCyrw4eFI/GCd4UjJBF - DnD7hvUxE7RRwOhbuYrtDRuB3Z7CeeP8o81eDVexyuBpM/9UQjYPqBBAfoeYKQzu - ZIhpGRWXw0ntH+EEOWagRXA5pRru61hteParZe4LBjPqisqN4Ek6ZR7MD9gB5xnt - Pn1BKRY08quFOZyaogzwfkYk5SCF8F8jBA8ZNAYwJWe1gtO3iw5vpUaQc2iCabvI - Y+Pc6qsSNwbkl7+sFrVHzI9QZVyz1cARUXxvrgGNLBkYtprkG91k6mCjX90cQspb - ZwHixcQyCNv+4H738e99h/Wf0YzjxFjDKrbGoosYBzWAsYYtzrtsBvw3SJMTXIh7 - OvFMA+rbIL8XWs8oNmZDDh8g0A== - -----END CERTIFICATE----- - signing_key: | - -----BEGIN PRIVATE KEY----- - MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQC45DCHUejzAeq+ - eVwEX5zSQWC+kqydEmoxpydT4YiSXnNeoNAkilKfHGOYUc4djwx148N14A+S0GCy - s2j3Ey2wuL7DSep5y1Z9Uxj6Ayg23XGIFFFJJLMy1QfepjCcr1djPH9PpwglG1nT - siWqvHGGc3WWn1u6RfyrCf+jxbhygNRTA+LVPpqNvko6MWKsbVLKrYMV2kPcQ0PQ - ByNHJnjBy3KH2k99lS20h32sgHbgbVpJWdAWjeyJrOh9aDt4/AfK90BvhkjF4BuQ - +Jw5oIwMhbx7YmzIfiJmBLLaGjVRppuoAQtLX9uLst9laLZzaeutg+G3RUYcvDMl - nP7cU8Sq4BD7uK0ChKxxCMFcihAhQ8wqCKncaaE9WqPsCM16SB/6xptOxoLcg/5q - 3PJyUi2g4VDKXuQc6AURKIJxSM9nlrcv/R7fCFgk3Nj/piWykDk6/BDWFpEHaj+N - nFE9ZIxKr9CjTxdmqiDTyqSv50rNCjleyL/iASBTSCCFOPVOYQECAwEAAQKCAYAR - p6iCo22tFrfFrGz+9epRoXCNgg/9h66gQyfcOKMD5wT5Oj3l31d4XgucleMqq2gz - MaaOcPDLwh4ZskwJm8k3IM0GdN5w9tuxZ+fwp7CFXKvkpJwGcfyyk+kGd7QYoh2k - GjjF8Fs0v+HZ9x7lqMzmW8wUr+7gYKJ56qCAkPbF6EteCfb1Cd9UPaF04RZdBKtt - MxhbU9Y7CClHigbyWlgZmUW8dzoz8bTFklKL0FCJqad/bZYTMUYu91XT88oKCXbD - AUxpF2Ikbkfj820XOqq8iV3xGpYszt1aMRpsdXbDAhCqfKoNet2X7jnRWlNXZutC - RIUGm4VUNDNeD4nXW8aLgDa8bNQnvsSmM9DUVuPjbejUs0VN7uwxo8rYqvkAKiBQ - 1ZqxoBK4ShZVcqgWE6CUj9FRZ3CVzSzydxZSQzex/ZRYPuYLUhQJFHLVIdJSYhf3 - XTEki0+ndwAB7yP/tBNlcxLftCzAaS7mPLLn1tf0A27QPCSjwOsTLxuJ4WYVkmkC - gcEAuuh8EImBfE9WOg3ITmJpr95WlVi8WE6BHWowV8dQwODQLj+38itDDL1xLn9+ - Vuz4o9AaIBiH5fCr6otun28lVp/sNVdWnBVeioSpu3tGV18OiDNaXtXOo7qkUnBI - Z+V7cD69gJLS6byD3OXlGi42h3XxK4mVlhwQtkQ69qI/zhl6rc0O2/iXXUAFa5T5 - MJ84Cw1B9kHFB/NC27sraee+cwAK0Pogj5WnqaBOIPeIO/f+br65xMUvEYvDD1m4 - TwIzAoHBAP082l0IQ5KHBY4WuFIDOoevO5SxHN5EUp2sPRDZwZxwOrjHxFRXPc/h - pDrVEHEn/4HQ706AHYpED0diumr4gee7gusNIDcGpXwjGVdFmFvxKoDbhz1C5vL3 - xC7qgyS/ZtAopxpCPH3+7IrQyBk8e6He+8F97bA0e9sYSBQSuPLcdKQXGNbLYb6s - yLbP02cB2CNeI1GJMQIOXe9bi9Cz5w+hCGMvEKt5oAz5SLWlPBvv1YATpG5Ux8Wy - RbGPD4zj+wKBwBGVDx6rIMAl4nGhnEcrYM/HdZOk/kq8T88JjzSirkkGnO7M1av1 - P+Bx7bS3D5Zzwkv+poaAaEBMLI/qv+RFm1iTwK+f4KjcJcGYCzN0vEA50+8iDY1A - RakHRK/wmg8T+lGrxT3UEf0k266q/atBz6VchexXi/fL+hJ7RqSuzJvBr9WrpYsx - zmNaQ2hEYlCdmbMIcz0MINHHo3FyIPpcb4D37wyLiwaWyGffiZn2Tx19DbUzQdxt - xCi9YgMOqJTeGwKBwQD4rJ0x5j+U0ApgcWcnAgyj2SwE47eZfDY0p0KAHZXGbV78 - vQ7KU7FbRhTjwP6YX9LEQ8v7pktbz2HBk+3DxayrRrNU5lrQLjKrKDxmOu1WvAgk - 6W5wdhYcWbnI6HlHyLzJhGIzov+MKp1V45fbUE2Hs1Q9uc+CzMcja0C8lXYQ5vOT - fyrhIm8lsr6W5paN/H2mnXbJRpNdlYYg2iD+HOu1qUh3PWx9Nr44f0MrPMs+E9Hw - J1m9DnvuYxWVOwrmK6kCgcADfcatftIJWMqeYJsDnB9jJaANmjln2G3bppo9WcIC - lvfXFE+Rf3FleaijVrUFbgxDU2MHh/2VPjJgIQT3QtfqS5+OnF1Z5+uOTGwbDNmT - 3Th0IcSt6TjvLJwkanNeSkvc+2lMnuNtH6TQLXB0qEs3D7xND0kFWHfyies+RYNC - eualoZJ/6UL9X2gkPG5jmzXjInEBguAL0ll5yETXgx6v0hXR058TcvPl58j73cCQ - dzDq+xUD8nHpKM33A2EaUFY= - -----END PRIVATE KEY----- diff --git a/conf/authen_shibboleth.conf.dist b/conf/authen_shibboleth.conf.dist new file mode 100644 index 0000000000..7453144c89 --- /dev/null +++ b/conf/authen_shibboleth.conf.dist @@ -0,0 +1,143 @@ +#!perl +################################################################################ +# Configuration for using Shibboleth authentication. +# +# To enable Shibboleth authentication, copy this file to +# conf/authen_shibboleth.conf and uncomment the appropriate lines in +# localOverrides.conf. +# +################################################################################ + +# Note that Shibboleth authentication will only work if webwork2 is proxied via +# apache2 and a Shibboleth service provider (mod_shib) is installed and +# configured. Instructions on how to configure the Shibboleth service provider +# are below. These instructions are specifically for Ubuntu, and setup will be +# slightly different on other systems. +# +# Install the Shibboleth service provider for apache2 by installing the Ubuntu +# apache2-mod-shib package. +# +# Modify the /etc/shibboleth/shibboleth2.xml file as follows. +# +# Change the "entityID" attribute of the "ApplicationDefaults" tag to +# https://your.server.edu`. Note that the Shibboleth identity provider that you +# will use will also need to be configured to allow this "entityID" to work with +# it. +# +# Change the "SSO" tag in the "Sessions" section to +# SAML2 +# +# Near the end of the file where example "MetadataProvider" sections are +# located add the following "MetadataProvider" tag. +# +# +# Note that further adjustments to that file may be needed depending on how your +# Shibboleth identity provider is set up. +# +# Next modify the /etc/shibboleth/attribute-map.xml file by adding the attribute +# that will be used for $shibboleth{mapping}{user_id} below. For example, if +# you are using the "uid" as in the default value of that variable, then add +# +# to the Attributes section of the file. +# Note the file already has some attributes configured, and so you may not need +# to modify that file at all. For example, if you use "eppn" for +# $shibboleth{mapping}{user_id}, then you don't need to change that file, since +# "eppn" is already listed. +# +# Finally, configure apache2 to protect route webwork2 course URLs to the +# Shibboleth service provider by adding one of the following to your apache2 +# site configuration file. +# +# +# AuthType shibboleth +# ShibRequestSetting requireSession 1 +# Require valid-user +# RequestHeader unset uid +# RequestHeader set uid %{uid}e env=uid +# +# +# or +# +# +# AuthType shibboleth +# ShibRequestSetting requireSession 0 +# Require shibboleth +# RequestHeader unset uid +# RequestHeader set uid %{uid}e env=uid +# +# +# Use the first if you want strict Shibboleth authentication. With this set up +# the webwork2 app will never see course URL requests if the user is not first +# authenticated with the Shibboleth identity provider. The apache2 Shibboleth +# service provider module will redirect the user first. +# +# Use the second if you want lazy Shibboleth authentication. With this set up +# the course URL requests will continue to the webwork2 app even if the user has +# not authenticated with the Shibboleth identity provider. The webwork2 app will +# redirect the user if authentication is needed. This allows for the usage of +# the $shibboleth{bypass_query} parameter or the $shiboff option described +# below. +# +# In both cases change all instances of "uid" to whatever you are using for the +# value of $shibboleth{mapping}{user_id} below. +# +# Execute "sudo shibd -t" to test the Shibboleth service provider configuration. +# Make sure to execute "sudo systemctl restart apache2" and +# "sudo systemctl restart shibd" so that settings take effect. + +################################################################################ + +# Set Shibboleth as the authentication module to use. +# Comment out 'WeBWorK::Authen::Basic_TheLastOption' if bypassing Saml2 +# authentication via the bypass query option (see $shibboleth{bypass_query} +# below) or the $shiboff option are both not allowed . +$authen{user_module} = [ + 'WeBWorK::Authen::Shibboleth', + 'WeBWorK::Authen::Basic_TheLastOption' +]; + +# List of authentication modules that may be used to enter the admin course. +# This is used instead of $authen{user_module} when logging into the admin +# course. Since the admin course provides overall power to add/delete courses, +# access to this course should be protected by the best possible authentication +# you have available to you. +$authen{admin_module} = [ + 'WeBWorK::Authen::Shibboleth' +]; + +# Set $shiboff to 1 to disable Shibboleth authentication. Usually this is not +# set here, but in the course.conf file for a course for which Shibboleth +# authentication is to be disabled. +#$shiboff = 0; + +# This URL query parameter can be added to the end of a course URL to skip the +# Shibboleth authentication module and go to the next one, for example, +# http://your.school.edu/webwork2/courseID?bypassShib=1. Comment out the next +# line to disable this feature. +$shibboleth{bypass_query} = 'bypassShib'; + +# The Shibboleth service provider login path. +$shibboleth{login_script} = '/Shibboleth.sso/Login'; + +# The Shibboleth service provider logout path. The default setting below +# demonstrates how to have the user redirected back to the course login page +# after the logout is complete. +$shibboleth{logout_script} = '/Shibboleth.sso/Logout?return=' . $server_root_url . $webwork_url; + +# Set to 1 to allow Shibboleth to manage session time instead of webwork. +$shibboleth{manage_session_timeout} = 1; + +# The user id hash method. The possible values are 'none' or 'MD5'. Use it when +# you want to hide real user_ids from showing in the URL. +$shibboleth{hash_user_id_method} = 'none'; + +# The salt to use for the hash method. +$shibboleth{hash_user_id_salt} = ''; + +# Set to the Shibboleth attribute that will be used for the webwork user id. +$shibboleth{mapping}{user_id} = 'uid'; diff --git a/conf/database.conf.dist b/conf/database.conf.dist deleted file mode 100644 index 5a5a81211b..0000000000 --- a/conf/database.conf.dist +++ /dev/null @@ -1,510 +0,0 @@ -#!perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - -=head1 NAME - -database.conf - define standard database layouts - -=head1 SYNOPSIS - -In defaults.config: - - include "conf/database.conf"; - *dbLayout = $dbLayouts{layoutName}; - -=head1 DESCRIPTION - -This file contains definitions for the commonly-used database layouts. Database -layouts consist of all the information necessary to describe how to access data -used by WeBWorK. For more information on the format of a database layout, -consult the documentation for the WeBWorK::DB module. - -A database layout is selected from the list of possible layouts by adding a -line like the one below to the F or F file. - - $dbLayoutName = "layoutName"; - *dbLayout = $dbLayouts{$dbLayoutName}; - -=cut - -%dbLayouts = (); # layouts are added to this hash below - -=head2 THE SQL_SINGLE DATABASE LAYOUT - -The C layout is similar to the C layout, except that it uses a -single database for all courses. This is accomplished by prefixing each table -name with the name of the course. The names and passwords of these accounts are -given as parameters to each table in the layout. - - username the username to use when connecting to the database - password the password to use when connecting to the database - -Be default, username is "webworkRead" and password is "". It is not recommended -that you use only a non-empty password to secure database access. Most RDBMSs -allow IP-based authorization as well. As the system administrator, IT IS YOUR -RESPONSIBILITY TO SECURE DATABASE ACCESS. - -Don't confuse the account information above with the accounts of the users of a -course. This is a system-wide account which allow WeBWorK to talk to the -database server. - -Other parameters that can be given are as follows: - - tableOverride an alternate name to use when referring to the table (used - when a table name is a reserved word) - fieldOverride a hash mapping WeBWorK field names to alternate names to use - when referring to those fields (used when one or more field - names are reserved words) - debug if true, SQL statements are printed before being executed - -=cut - - -# params common to all tables - -my %sqlParams = ( - username => $database_username, - password => $database_password, - debug => $database_debug, - # kinda hacky, but needed for table dumping - mysql_path => $externalPrograms{mysql}, - mysqldump_path => $externalPrograms{mysqldump}, -); - -if ( $ce->{database_driver} =~ /^mysql$/i ) { - # The extra UTF8 connection setting is ONLY needed for older DBD:mysql driver - # and forbidden by the newer DBD::MariaDB driver - if ( $ENABLE_UTF8MB4 ) { - $sqlParams{mysql_enable_utf8mb4} = 1; # Full 4-bit UTF-8 - } else { - $sqlParams{mysql_enable_utf8} = 1; # Only the partial 3-bit mySQL UTF-8 - } -} - -$dbLayouts{sql_single} = { - locations => { - record => "WeBWorK::DB::Record::Locations", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - non_native => 1, - }, - }, - location_addresses => { - record => "WeBWorK::DB::Record::LocationAddresses", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - non_native => 1, - }, - }, - depths => { - record => "WeBWorK::DB::Record::Depths", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - }, - password => { - record => "WeBWorK::DB::Record::Password", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_password", - }, - }, - permission => { - record => "WeBWorK::DB::Record::PermissionLevel", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_permission", - }, - }, - key => { - record => "WeBWorK::DB::Record::Key", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_key", - fieldOverride => { key => "key_not_a_keyword" }, - }, - }, - user => { - record => "WeBWorK::DB::Record::User", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_user", - }, - }, - set => { - record => "WeBWorK::DB::Record::Set", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_set", - #fieldOverride => { visible => "published" }, # for compatibility -- visible was originally called published - }, - }, - set_user => { - record => "WeBWorK::DB::Record::UserSet", - schema => "WeBWorK::DB::Schema::NewSQL::NonVersioned", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_set_user", - #fieldOverride => { visible => "published" }, # for compatibility -- visible was originally called published - }, - }, - set_merged => { - record => "WeBWorK::DB::Record::UserSet", - schema => "WeBWorK::DB::Schema::NewSQL::Merge", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - depend => [qw/set_user set/], - params => { %sqlParams, - non_native => 1, - merge => [qw/set_user set/], - }, - }, - set_version => { - record => "WeBWorK::DB::Record::SetVersion", - schema => "WeBWorK::DB::Schema::NewSQL::Versioned", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - tableOverride => "${courseName}_set_user", - #fieldOverride => { visible => "published" }, # for compatibility -- visible was originally called published - - }, - }, - set_version_merged => { - record => "WeBWorK::DB::Record::SetVersion", - schema => "WeBWorK::DB::Schema::NewSQL::Merge", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - depend => [qw/set_version set_user set/], - params => { %sqlParams, - non_native => 1, - merge => [qw/set_version set_user set/], - }, - }, - set_locations => { - record => "WeBWorK::DB::Record::SetLocations", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_set_locations" - }, - }, - set_locations_user => { - record => "WeBWorK::DB::Record::UserSetLocations", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_set_locations_user" - }, - }, - problem => { - record => "WeBWorK::DB::Record::Problem", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_problem" - }, - }, - problem_user => { - record => "WeBWorK::DB::Record::UserProblem", - schema => "WeBWorK::DB::Schema::NewSQL::NonVersioned", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_problem_user" - }, - }, - problem_merged => { - record => "WeBWorK::DB::Record::UserProblem", - schema => "WeBWorK::DB::Schema::NewSQL::Merge", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - depend => [qw/problem_user problem/], - params => { %sqlParams, - non_native => 1, - merge => [qw/problem_user problem/], - }, - }, - problem_version => { - record => "WeBWorK::DB::Record::ProblemVersion", - schema => "WeBWorK::DB::Schema::NewSQL::Versioned", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - non_native => 1, - tableOverride => "${courseName}_problem_user", - }, - }, - problem_version_merged => { - record => "WeBWorK::DB::Record::ProblemVersion", - schema => "WeBWorK::DB::Schema::NewSQL::Merge", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - depend => [qw/problem_version problem_user problem/], - params => { %sqlParams, - non_native => 1, - merge => [qw/problem_version problem_user problem/], - }, - }, - setting => { - record => "WeBWorK::DB::Record::Setting", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_setting" - }, - }, - achievement => { - record => "WeBWorK::DB::Record::Achievement", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_achievement" - }, - }, - past_answer => { - record => "WeBWorK::DB::Record::PastAnswer", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_past_answer" - }, - }, - - achievement_user => { - record => "WeBWorK::DB::Record::UserAchievement", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_achievement_user" - }, - }, - global_user_achievement => { - record => "WeBWorK::DB::Record::GlobalUserAchievement", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - character_set => $database_character_set, - params => { %sqlParams, - tableOverride => "${courseName}_global_user_achievement" - }, - }, - # LTI Advantage - lti_resource_link => { - record => "WeBWorK::DB::Record::LTI1p3::ResourceLink", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - tableOverride => "${courseName}_lti_resource_link" - }, - }, - lti_user => { - record => "WeBWorK::DB::Record::LTI1p3::User", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - tableOverride => "${courseName}_lti_user" - }, - }, - lti_contexts => { - record => "WeBWorK::DB::Record::LTI1p3::Contexts", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - }, - lti_nonces => { - record => "WeBWorK::DB::Record::LTI1p3::Nonces", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - }, - lti_access_tokens => { - record => "WeBWorK::DB::Record::LTI1p3::AccessTokens", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - }, - # Delayed Job Tables - funcmap => { - record => "WeBWorK::DB::Record::DelayedJob::Funcmap", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - }, - job => { - record => "WeBWorK::DB::Record::DelayedJob::Job", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - }, - note => { - record => "WeBWorK::DB::Record::DelayedJob::Note", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - }, - error => { - record => "WeBWorK::DB::Record::DelayedJob::Error", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - }, - exitstatus => { - record => "WeBWorK::DB::Record::DelayedJob::Exitstatus", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - } -}; - -# include ("conf/database.conf"); # uncomment to provide local overrides - - -=head1 DATABASE LAYOUT METADATA - -=over - -=item @dbLayout_order - -Database layouts listed in this array will be displayed first, in the order -specified, wherever database layouts are listed. (For example, in the "Add -Course" tool.) Other layouts are listed after these. - -=cut - -@dbLayout_order = qw/sql_single sql_moodle/; - -=item %dbLayout_descr - -Hash mapping database layout names to textual descriptions. - -=cut - -%dbLayout_descr = ( - sql_single => "Uses a single SQL database to record WeBWorK data for all courses using this layout. This is the recommended layout for new courses.", -# sql_moodle => "Similar to sql_single, but uses a Moodle database for user, password, and permission information. This layout should be used for courses used with wwmoodle.", -); - -=back - -=cut diff --git a/conf/defaults.config b/conf/defaults.config index 12794700f7..541d0f3937 100644 --- a/conf/defaults.config +++ b/conf/defaults.config @@ -1,18 +1,4 @@ #!perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ # This file is used to set up the default WeBWorK course environment for all # requests. Values may be overwritten by the course.conf for a specific course. @@ -42,6 +28,16 @@ include("VERSION"); # get WW version # Mail Settings ################################################################################ +# $generic_sender_name will be used as the "From:" name on all feedback emails +# sent without a defined user. +$generic_sender_name = ''; + +# The following variables will override the "From:" address in messages sent by +# the named feature. +$feedback_sender_email = ''; # For student feedback +$instructor_sender_email = ''; # For instructors emailing students +$jitar_sender_email = ''; # For notifications of incomplete JiTaR sets + # By default, feedback is sent to all users who have permission to # receive_feedback in a course. If this list is non-empty, feedback is also sent # to the addresses specified here. @@ -130,7 +126,6 @@ $courseURLs{feedbackFormURL} = ""; ################################################################################ $defaultTheme = "math4"; -$defaultThemeTemplate = "system"; # The institution logo should be an image file in the theme's images folder $institutionLogo = 'maa_logo.svg'; @@ -143,11 +138,10 @@ $institutionName = 'MAA (Mathematical Association of America)'; $achievementsEnabled = 0; $achievementItemsEnabled = 0; $achievementPointsPerProblem = 5; +$achievementPointsPerProblemReduced = 3; $achievementPreambleFile = "preamble.at"; - -################################################################################ -# Achievements -################################################################################ +$achievementExcludeSet = []; +$mail{achievementEmailFrom} = ''; $showCourseHomeworkTotals = 1; ################################################################################ @@ -203,6 +197,20 @@ $pg{options}{periodicRandomizationPeriod} = 5; # and before a new version is requested. $pg{options}{showCorrectOnRandomize} = 0; +############################################################################### +# answer feedback options +################################################################################ + +# If set to 1, then answer feedback is automatically shown when returning to an +# answered problem or after answers are available for the assignment. This is +# present in problems when a student opens the problem without the student +# needing to click the "Check Answers" button. +$pg{options}{automaticAnswerFeedback} = 1; + +# If set to 1, then a "Reveal" button must be clicked to reveal correct answers +# even after the answer date. +$pg{options}{correctRevealBtnAlways} = 0; + ################################################################################ # Single Problem Grader ################################################################################ @@ -224,7 +232,11 @@ $pg{specialPGEnvironmentVars}{waiveExplanations} = 0; # Language ################################################################################ -$language = "en"; # tr = turkish, en=english +# This must be a language code for a language that is supported by webwork. +# Note that this is also used for the locale for date/time formats. +# Check the directory lib/WeBWorK/Localize to see which languages are +# currently supported (e.g. en, es, fr, he-IL, tr, zh-HK). +$language = "en"; # $perProblemLangAndDirSettingMode controls how and whether LANG and/or DIR # attributes are added to the DIV element enveloping a problem @@ -234,6 +246,17 @@ $language = "en"; # tr = turkish, en=english # to be translated to Hebrew. $perProblemLangAndDirSettingMode = "force::ltr"; +################################################################################ +# Student Date Format +################################################################################ + +# This is the format of the dates displayed for students. This can be created +# from the strftime patterns documented at https://metacpan.org/pod/DateTime#strftime-Patterns, +# or can be one of the localizable DateTime::Locale::FromData formats +# 'datetime_format_short', 'datetime_format_medium', 'datetime_format_long', or +# 'datetime_format_full'. See https://metacpan.org/pod/DateTime::Locale::FromData. +$studentDateDisplayFormat = 'datetime_format_long'; + ################################################################################ # System-wide locations (directories and URLs) ################################################################################ @@ -307,8 +330,9 @@ $webworkDirs{localize} = "$webworkDirs{root}/lib/WeBWorK/Localize"; # URL of general WeBWorK documentation. $webworkURLs{docs} = "https://webwork.maa.org"; -# URL of WeBWorK Bugzilla database. -$webworkURLs{bugReporter} = "https://bugs.webwork.maa.org/enter_bug.cgi"; +# URLs for new issues in Github. +$webworkURLs{webwork2BugReporter} = "https://github.com/openwebwork/webwork2/issues/new"; +$webworkURLs{OPLBugReporter} = "https://github.com/openwebwork/webwork-open-problem-library/issues/new"; # URL of WeBWorK on GitHub $webworkURLs{GitHub} = "https://github.com/openwebwork"; @@ -336,7 +360,43 @@ $webworkURLs{AuthorHelpURL} ='https://webwork.maa.org/wiki/Category:Au # /css/math.css"/> ################################################################################ -# Defaults for course-specific locations (directories and URLs) +# Problem library options +################################################################################ +# +# The problemLibrary configuration data should now be set in localOverrides.conf + +# For configuration instructions, see: +# https://webwork.maa.org/wiki/Open_Problem_Library +# The directory containing the open problem library files. +# Set the root to "" if no problem + +#RE-CONFIGURE problemLibrary values in localOverrides.conf +# if these defaults are not correct. +################################################# +$problemLibrary{root} = "/opt/webwork/libraries/webwork-open-problem-library/OpenProblemLibrary"; +$contribLibrary{root} = "/opt/webwork/libraries/webwork-open-problem-library/Contrib"; +$problemLibrary{version} = "2.5"; +########################################################### + +# Problem Library SQL database connection information +$problemLibrary_db = { + dbsource => $database_dsn, + user => $database_username, + passwd => $database_password, + storage_engine => 'MYISAM', +}; + +$problemLibrary{tree} = 'library-directory-tree.json'; + +# These flags control if statistics on opl problems are shown in the library +# browser. If you want to include local statistics you will need to +# run webwork2/bin/update-OPL-statistics on a regular basis. +$problemLibrary{showLibraryLocalStats} = 1; +# This flag controls whether global statistics will be displayed +$problemLibrary{showLibraryGlobalStats} = 1; + +################################################################################ +# Defaults for course-specific locations (directories, URLs, and links) ################################################################################ # The root directory of the current course. (The ID of the current course is @@ -371,9 +431,10 @@ $courseDirs{templates} = "$courseDirs{root}/templates"; $courseDirs{hardcopyThemes} = "$courseDirs{templates}/hardcopyThemes"; # Location of course achievement files. -$courseDirs{achievements} = "$courseDirs{templates}/achievements"; -$courseDirs{achievements_html} = "$courseDirs{html}/achievements"; #contains badge icons -$courseURLs{achievements} = "$courseURLs{html}/achievements"; +$courseDirs{achievements} = "$courseDirs{templates}/achievements"; +$courseDirs{achievement_notifications} = "$courseDirs{achievements}/notifications"; +$courseDirs{achievements_html} = "$courseDirs{html}/achievements"; #contains badge icons +$courseURLs{achievements} = "$courseURLs{html}/achievements"; # Location of course-specific macro files. $courseDirs{macros} = "$courseDirs{templates}/macros"; @@ -387,6 +448,12 @@ $courseDirs{tmpEditFileDir} = "$courseDirs{templates}/tmpEdit"; # mail merge status directory $courseDirs{mailmerge} = "$courseDirs{DATA}/mailmerge"; +# course links that are regulated by webwork2 +$courseLinks{Library} = [ $problemLibrary{root}, "$courseDirs{templates}/Library" ]; +$courseLinks{Contrib} = [ $contribLibrary{root}, "$courseDirs{templates}/Contrib" ]; +$courseLinks{capaLibrary} = [ "$contribLibrary{root}/CAPA", "$courseDirs{templates}/capaLibrary" ]; +$courseLinks{Student_Orientation} = [ "$webworkDirs{assets}/pg/Student_Orientation", "$courseDirs{templates}/Student_Orientation" ]; + ################################################################################ # System-wide files ################################################################################ @@ -556,69 +623,13 @@ $default_status = "Enrolled"; # Database options ################################################################################ -# Database schemas are defined in the file conf/database.conf and stored in the -# hash %dbLayouts. The standard schema is called "sql_single"; - -include( "./conf/database.conf.dist"); # always include database.conf.dist - - # in the rare case where you want local overrides - # you can place include("conf/database.conf") in - # the database.conf.dist file -# this change is meant to help alleviate the common mistake of forgetting to update the -# database.conf file when changing WW versions. - -# Select the default database layout. This can be overridden in the course.conf -# file of a particular course. The only database layout supported in WW 2.1.4 -# and up is "sql_single". -$dbLayoutName = "sql_single"; - -# This sets the symbol "dbLayout" as an alias for the selected database layout. -*dbLayout = $dbLayouts{$dbLayoutName}; - # This sets the max course id length. It might need to be changed depending # on what database tables are present. Mysql allows a max table length of 64 # characters. With the ${course_id}_global_user_achievement table that means # the max ${course_id} is exactly 40 characters. - -$maxCourseIdLength = 40; - # Reference: https://dev.mysql.com/doc/refman/8.0/en/identifier-length.html -################################################################################ -# Problem library options -################################################################################ -# -# The problemLibrary configuration data should now be set in localOverrides.conf - -# For configuration instructions, see: -# https://webwork.maa.org/wiki/Open_Problem_Library -# The directory containing the open problem library files. -# Set the root to "" if no problem - -#RE-CONFIGURE problemLibrary values in localOverrides.conf -# if these defaults are not correct. -################################################# -$problemLibrary{root} = "/opt/webwork/libraries/webwork-open-problem-library/OpenProblemLibrary"; -$contribLibrary{root} = "/opt/webwork/libraries/webwork-open-problem-library/Contrib"; -$problemLibrary{version} = "2.5"; -########################################################### - -# Problem Library SQL database connection information -$problemLibrary_db = { - dbsource => $database_dsn, - user => $database_username, - passwd => $database_password, - storage_engine => 'MYISAM', -}; - -$problemLibrary{tree} = 'library-directory-tree.json'; - -# These flags control if statistics on opl problems are shown in the library -# browser. If you want to include local statistics you will need to -# run webwork2/bin/update-OPL-statistics on a regular basis. -$problemLibrary{showLibraryLocalStats} = 1; -# This flag controls whether global statistics will be displayed -$problemLibrary{showLibraryGlobalStats} = 1; +$maxCourseIdLength = 40; ################################################################################ # Logs @@ -663,47 +674,40 @@ $courseFiles{logs}{activity_log} = ''; # Site defaults (Usually overridden in localOverrides.conf) ################################################################################ -# The default_templates_course is used by default to create a new course. -# The contents of the templates directory are copied from this course -# to the new course being created. -$siteDefaults{default_templates_course} ="modelCourse"; +# The default_copy_from_course is used by default when creating a new course. +# Its templates folder, html folder, course.conf file, and simple.conf file +# might be copied into a new course. This course might not be a true course; +# it might only have a directory structure and no presense in the database. +# If it is a real course, then also its title, institution, non-student users, +# achievements, and sets can be copied. +$siteDefaults{default_copy_from_course} ="modelCourse"; # Provide a list of model courses which are not real courses, but from which -# the templates for a new course can be copied. +# the templates for a new course can be copied. This list helps exclude such +# non-real courses when that is appopriate. $modelCoursesForCopy = [ "modelCourse" ]; ################################################################################ # Authentication system ################################################################################ -# FIXME This mechanism is a little awkward and probably should be merged with -# the dblayout selection system somehow. - # Select the authentication module to use for normal logins. -# -# If this value is a string, the given authentication module will be used -# regardless of the database layout. If it is a hash, the database layout name -# will be looked up in the hash and the resulting value will be used as the -# authentication module. The special hash key "*" is used if no entry for the -# current database layout is found. -# If this value is a sequence of strings or hashes, then each -# string or hash in the sequence will be successively tested to see if it -# provides a module that can handle -# the authentication request (by calling the module's -# sub request_has_data_for_this_verification_module ). -# The first module that responds affirmatively will be used. -# -# -$authen{user_module} = { - # sql_moodle => "WeBWorK::Authen::Moodle", - # sql_ldap => "WeBWorK::Authen::LDAP", - "*" => "WeBWorK::Authen::Basic_TheLastOption", -}; +# If this value is a string, then that authentication module will be used. If +# this value is a reference to an array of strings, then each string in the +# array will be successively tested to see if it provides a module that can +# handle the authentication request (by calling that module's +# request_has_data_for_this_verification_module method). The first module that +# responds affirmatively will be used. +$authen{user_module} = 'WeBWorK::Authen::Basic_TheLastOption'; # Select the authentication module to use for proctor logins. # A string or a hash is accepted, as above. $authen{proctor_module} = "WeBWorK::Authen::Proctor"; +# List of authentication modules that may be used to enter the admin course. +# This is used instead of $authen{user_module} when logging into the admin course. +$authen{admin_module} = ['WeBWorK::Authen::Basic_TheLastOption']; + ################################################################################ # Authorization system (Make local overrides in localOverrides.conf ) ################################################################################ @@ -737,8 +741,10 @@ $authen{proctor_module} = "WeBWorK::Authen::Proctor"; %permissionLevels = ( login => "guest", navigation_allowed => "guest", + use_two_factor_auth => "student", report_bugs => "ta", submit_feedback => "student", + change_name => "student", change_password => "student", change_email_address => "student", change_pg_display_settings => "student", @@ -753,10 +759,13 @@ $authen{proctor_module} = "WeBWorK::Authen::Proctor"; view_hidden_sets => "ta", view_answers => "ta", view_ip_restricted_sets => "ta", + view_leaderboard => "professor", + view_leaderboard_usernames => "professor", become_student => "professor", access_instructor_tools => "ta", score_sets => "professor", + problem_grader => "professor", send_mail => "professor", receive_feedback => ['ta', 'professor', 'admin'], @@ -828,18 +837,57 @@ $authen{proctor_module} = "WeBWorK::Authen::Proctor"; download_hardcopy_format_pdf => "guest", download_hardcopy_format_tex => "ta", download_hardcopy_change_theme=> "ta", + + ##### Permission to edit a specific setting on the course configuration page ##### + # Each permission of this type must have the form change_config_[var]. Note + # that the user must also have the permission modify_problem_sets to change + # configuration settings. If a configuration option is not specifically set + # for a setting, then the modify_problem_sets permission alone is + # sufficient to change a configuration setting. + + #change_config_courseTitle => "admin", + change_config_lms_context_id => "admin", + 'change_config_LTI{v1p1}{BasicConsumerSecret}' => "admin", + 'change_config_LTI{v1p3}{PlatformID}' => "admin", + 'change_config_LTI{v1p3}{ClientID}' => "admin", + 'change_config_LTI{v1p3}{DeploymentID}' => "admin", + 'change_config_LTI{v1p3}{PublicKeysetURL}' => "admin", + 'change_config_LTI{v1p3}{AccessTokenURL}' => "admin", + 'change_config_LTI{v1p3}{AccessTokenAUD}' => "admin", + 'change_config_LTI{v1p3}{AuthReqURL}' => "admin", + + # Do not confuse the permission to change a configuration permission with + # the actual permission as in the following example. If this us uncommented, + # then only admin users will be able to change the permission level for the + # permission record_answers_when_acting_as_student. (Note the quoting + # needed for this configuration value also.) + #'change_config_permissionLevels{record_answers_when_acting_as_student}' => "admin", ); # This is the default permission level given to new students and students with # invalid or missing permission levels. $default_permission_level = $userRoles{student}; +# This sets the default fallback source to use for a user's password when a user +# account is created in a course. That is the source that will be shown by +# default in a select menu in the user interface, and the source that will be +# used by the importClassList.pl and addcourse scripts. It can be one of +# 'user_id', 'first_name', 'last_name', or 'student_id', or can be set to '' (or +# anything not listed before). If a user is created and no password is +# explicitly provided, then this source will used for the password (assuming +# that source value is also set). If this is not one of the allowed sources, +# and if a password is not explicitly provided, then the user will be created +# without a password and will not be able to sign in with username and password. +# Note that this is only used at the time that a user is initially created in a +# course, and not when editing passwords at a later time. +$fallback_password_source = ''; + ################################################################################ # Session options ################################################################################ -# $sessionKeyTimeout defines seconds of inactivity before a key expires -$sessionKeyTimeout = 60*60; +# $sessionTimeout defines seconds of inactivity before a user's session expires. +$sessionTimeout = 60*60; # $sessionKeyLength defines the length (in characters) of the session key $sessionKeyLength = 32; @@ -851,12 +899,6 @@ $sessionKeyLength = 32; # (you can comment this out to remove practice user support) $practiceUserPrefix = "practice"; -# There is a practice user who can be logged in multiple times. He's -# commented out by default, though, so you don't hurt yourself. It is -# kindof a backdoor to the practice user system, since he doesn't have a -# password. Come to think of it, why do we even have this?! -#$debugPracticeUser = "practice666"; - # Option for gateway tests; $gatewayGracePeriod is the time in seconds # after the official due date during which we'll still grade the test $gatewayGracePeriod = 120; @@ -865,89 +907,120 @@ $gatewayGracePeriod = 120; # Session Management ################################################################################ -## Session management, i.e., checking whether the session has timed out, -## can be handled either using the key database, the traditional method, -## or by using session cookies. If one is using the key database -## for session_management and is using WeBWorK's password authentication, -## then one has the option of keeping a Login cookie with a duration -## of up to 30 days. However, if one uses cookies for session management, -## then one cannot also use Login cookies. So session management using -## cookies is more appropriate when external authentication systems -## are used, e.g., LDAP, LTIAdvanced, etc. -## - -## The following ability to update the cookie timestamp instead of the timestamp in the database -## has been disabled for now. It opens a potential security hole. -## Setting $session_management_via="session_cookies" sets a session cookie automatically -## even if you don't ask for it explicitly and this login cookie contains a session_key which -## allows you to reenter your course (within 20 minutes or so) even if you move away from the -## webwork HTML page and loose the session_key embedded in the HTML page. -## However the time stamp in the cookie is not used currently used for anything. -## -#### NOT CURRENT: The reason one might want to use cookies for session management -#### is to avoid having to obtain a write lock on the Key database -#### every time a request is received in order to update the timestamp -#### in the Key database. When cookies are used for session management, -#### one obtains a write lock on the Key database for the original -#### login request in order to write the new session key and its initial -#### timestamp to the Key database, but on subsequent requests, -#### one merely obtains a read lock on the Key database in order -#### to verify that the session key in the session cookie is the -#### same as the session key in the Key database. The session timestamp -#### is maintained in the cookie, not in the Key database, which will -#### only show the timestamp of the original login. - - - -## For session management using session_cookies, uncomment the first of the -## following lines. -## For session management using keys stored -## in the key database and, possibly, enduring cookies if any have been set, -## uncomment the second line. - -## These choices can be overridden locally in the localOverrides.conf file. -## The default is to use "session_cookie". +# Session management can be handled either using the key database, the +# traditional method, or by using signed session cookies. -$session_management_via = "session_cookie"; -#$session_management_via = "key"; +# Setting $session_management_via="key" uses the key database for session +# management. If password authentication is used, then a user can opt to use a +# session cookie with a duration determined by the $sessionTimeout setting +# above by checking the "Remember Me" checkbox on the login page. -################################################################################ -# WeBWorK Caliper -################################################################################ +# Setting $session_management_via="session_cookies" uses a session cookie to +# manage the session. Note that even in this case a key is stored in the +# database which is compared to the key stored in the session cookie. The +# lifetime of the cookie is determined by the $sessionTimeout setting above. -# Caliper is disabled by default. See localOverrides.conf.dist for configuration -# options when enabling Caliper. -$caliper{enabled} = 0; +# Note that the key database method is less secure as the key must be embeded in +# the page and added as a url parameter in order to maintain the session. These +# things can be accessed by malicious javascript. The session cookies are http +# only cookies which can not be accessed via javascript. + +$session_management_via = "session_cookie"; ################################################################################ # Cookie control settings ################################################################################ -# The following variables can be set to control cookie behavior. - -# Set the value of the samesite attribute of the WeBWorK cookie: -# See: https://blog.chromium.org/2019/10/developers-get-ready-for-new.html -# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite -# https://tools.ietf.org/html/draft-west-cookie-incrementalism-00 - +# Set the value of the samesite attribute of the session cookie: +# See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite # Notes about the $CookieSameSite options: -# The "None" setting should only be used with HTTPS and when $CookieSecure = 1; is set below. The "None" setting is -# also less secure and can allow certain types of cross-site attacks. -# The "Strict" setting can break the links in the system generated feedback emails when read in a web mail client. -# Due to those factors, the "Lax" setting is probably the optimal choice for typical WeBWorK servers. - +# The "None" setting should only be used with HTTPS and when $CookieSecure is +# set to 1 below. The "None" setting is also less secure and can allow certain +# types of cross-site attacks. The "Strict" setting can break the links in the +# system generated feedback emails when read in a web mail client. Due to those +# factors, the "Lax" setting is probably the optimal choice for typical webwork2 +# servers. $CookieSameSite = "Lax"; -# Set the value of the secure cookie attribute: -# Default is 0 here, as 1 will not work without https -$CookieSecure = 0; +# Set the value of the secure cookie attribute. +$CookieSecure = 1; + +# If $useSessionCookie is set to 1, then a "session" cookie will be used. This +# means that the cookie will be deleted when the browser session ends. +# Typically, this is when the browser is closed. Note that the browser defines +# when this is, and some browser's also allow sessions to be restored when the +# browser is reopened. In any case, a user's session will end when the session +# is idle for more than the number of seconds the $sessionTimeout value is set +# to. +$useSessionCookie = 0; + +################################################################################ +# Two Factor Authentication +################################################################################ + +# The following variables enable two factor authentication and control how it +# works. Two factor authentication only applies to courses that use password +# authentication, i.e., the Basic_TheLastOption user authentication module +# without an external authentication approach (like LTI, CAS, Shibboleth, etc.). +# It is recommended that two factor authentication be enabled for all courses +# that use password authentication. It is extremely highly recommended that this +# be enabled for the admin course. Two factor authentication works with an +# authenticator app on a mobile device (such as Google Authenticator, +# Microsoft authenticator, Twilio Authy, etc.). + +# $twoFA{enabled} determines if two factor authentication is enabled for a +# course. If this is set to 0, then two factor authentication is disabled for +# all courses. If this is 1 (the default), then two factor authentication is +# enabled for all courses that use password authentication. If this is a string +# course name like 'admin', then two factor authentication is enabled only for +# that course. If this is an array of string course names, then two factor +# authentication is enabled only for those courses listed. This can also be set +# in a course's course.conf file. Note that only the values of 0 and 1 make +# sense there. +$twoFA{enabled} = 1; + +# There are two methods that can be used to setup two factor authentication when +# a user signs in for the first time. The setup information can be emailed to +# the user, or can be directly displayed in the browser on the next page that is +# shown after password verification succeeds. +# +# If $twoFA{email_sender} is set, then the email approach will be used. In this +# case, after a user signs in and the password is verified, the user will be +# sent an email containing a QR code and instructions on how to set up a OTP +# generator app. This is probably a more secure way to set up two factor +# authentication, as it ensures the user setting it up is the correct user. Note +# that if a user does not have an email address, then the browser method below +# will be used as a fallback. +# +# If $twoFA{email_sender} is not set, then after a user signs in and the +# password is verified, the QR code, OTP link, and instructions will be +# displayed directly on the page in the browser. This is potentially less secure +# because a hacker could guess a username and password before a user has setup +# two factor authentication (particularly if the username and password are +# initially the same), and then the hacker would gain access to that user's +# account, and the actual user would be locked out. Note that you will need to +# use this option if your server can not send emails. Also note that no-reply +# addresses may be blocked by the email server or marked as spam. So it may be +# better to find a valid email address to use for this. +$twoFA{email_sender} = ''; + +# When a user signs in and enters the two factor authentication code, the user +# has the option to skip two factor verification on a given device for +# subsequent logins. That will only last for the amount of time set as the +# skip_verification_code_interval. By default this is set to one year. However, +# good security practices most likely recommend a shorter time interval for +# this. So change this value if you want to require a shorter and thus more +# secure time interval before users will need to enter the two factor +# authentication code again. +$twoFA{skip_verification_code_interval} = 3600 * 24 * 365; -# The CookieLifeTime setting determines how long the browser should retain the cookie. -# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie -# The CookieLifeTime value should be numeric and in seconds, or should be set to "session", in which case -# the Cookie will expire when the browser session ends (a "session cookie"). -# The default value is 7 days. -$CookieLifeTime = 604800; +################################################################################ +# WeBWorK Caliper +################################################################################ + +# Caliper is disabled by default. See localOverrides.conf.dist for configuration +# options when enabling Caliper. +$caliper{enabled} = 0; ################################################################################ # PG subsystem options @@ -978,20 +1051,14 @@ $pg{additionalPGEditorDisplayModes} = [ # Whether the homework editor pages should show options for conditional release $options{enableConditionalRelease} = 0; -# In the hmwk sets editor, how deep to search within templates for .def files. -# Note that this does not apply to the Library and Contrib directories. -# Those directories are not searched in any case (see below). -# 0 means only within templates. +########################################################################################## +# Searching for set.def files to import +########################################################################################## +# In the set manager and the library browser, the directory depth to search +# within templates for .def files. Note that this does not apply to the Library +# and Contrib directories. Those directories are not searched in any case. $options{setDefSearchDepth} = 4; -# In the hmwk sets editor, also list OPL or Contrib set defintion files. Note -# that the directories are not searched, but these lists are loaded from the -# files htdocs/DATA/library-set-defs.json and htdocs/DATA/contrib-set-defs.json -# which are generated by running bin/generate-OPL-set-def-lists.pl (which is -# also run if you run bin/OPL-update). -$options{useOPLdefFiles} = 1; -$options{useContribDefFiles} = 1; - ########################################################################################## #### Default settings for the problem editor pages ########################################################################################## @@ -1048,12 +1115,6 @@ $pg{options}{showEvaluatedAnswers} = 1; # propogate to the main process. So this really should never be set to 0. $pg{options}{catchWarnings} = 1; -# decorations for correct input blanks -- apparently you can't define and name attribute collections in a .css file -$pg{options}{correct_answer} = "{border-width:2;border-style:solid;border-color:#8F8}"; #matches resultsWithOutError class in math2.css - -# decorations for incorrect input blanks -$pg{options}{incorrect_answer} = "{border-width:2;border-style:solid;border-color:#F55}"; #matches resultsWithError class in math2.css - ##### Settings for various display modes # "images" mode has several settings: @@ -1131,7 +1192,7 @@ $pg{specialPGEnvironmentVars}{use_javascript_for_live3d} = 1; # Binary that the PGtikz.pl and PGlateximage.pl macros will use to create svg images. # This should be either 'pdf2svg' or 'dvisvgm'. -$pg{specialPGEnvironmentVars}{latexImageSVGMethod} = "pdf2svg"; +$pg{specialPGEnvironmentVars}{latexImageSVGMethod} = "dvisvgm"; # When ImageMagick is used for image conversions, this sets the default options. # See https://imagemagick.org/script/convert.php for a full list of options. @@ -1250,6 +1311,7 @@ ${pg}{modules} = [ [qw(Matrix)], [qw(Multiple)], [qw(PGrandom)], + [qw(Plots::Plot Plots::Axes Plots::Data Plots::Tikz Plots::JSXGraph Plots::GD)], [qw(Regression)], [qw(Select)], [qw(Units)], @@ -1261,28 +1323,29 @@ ${pg}{modules} = [ [qw(Applet MIME::Base64)], [qw(PGcore PGalias PGresource PGloadfiles PGanswergroup PGresponsegroup Tie::IxHash)], [qw(Locale::Maketext)], - [qw(WeBWorK::Localize)], - [qw(JSON)], + [qw(WeBWorK::PG::Localize)], + [qw(Mojo::JSON)], [qw(Rserve Class::Tiny IO::Handle)], [qw(DragNDrop)], [qw(Types::Serialiser)], + [qw(strict)], ]; ##### Problem creation defaults # The default weight (also called value) of a problem to use when using the -# Library Browser, Problem Editor or Hmwk Sets Editor to add problems to a set +# Library Browser, Problem Editor, or Sets Manager to add problems to a set # or when this value is left blank in an imported set definition file. $problemDefaults{value} = 1; # The default max_attempts for a problem to use when using the -# Library Browser, Problem Editor or Hmwk Sets Editor to add problems to a set +# Library Browser, Problem Editor, or Sets Manager to add problems to a set # or when this value is left blank in an imported set definition file. Note that # setting this to -1 gives students unlimited attempts. $problemDefaults{max_attempts} = -1; # The default showMeAnother for a problem to use when using the -# Library Browser, Problem Editor or Hmwk Sets Editor to add problems to a set +# Library Browser, Problem Editor, or Sets Manager to add problems to a set # or when this value is left blank in an imported set definition file. Note that # setting this to -1 disables the showMeAnother button $problemDefaults{showMeAnother} = -2; @@ -1331,7 +1394,6 @@ $pg{ansEvalDefaults} = { enableReducedScoring => 0, reducedScoringPeriod => 0, # Default length of Reduced Scoring Period in minutes reducedScoringValue => .75, # Percent of score students receive in Reduced Scoring Period - timeAssignDue => "11:59pm", assignOpenPriorToDue => 14400, # a number of minutes (default is 10 days) answersOpenAfterDueDate => 2880 # number of minutes (default is 2 days) }; @@ -1369,18 +1431,29 @@ $webservices = { $pg{specialPGEnvironmentVars}{entryAssist} = 'MathQuill'; ############################################################################### -# default Homework Config settings +# Default Set settings ############################################################################### -# time of day the assignment is due. +# Time of day that newly created sets are set to be due. This should not be a +# localized string, and is never used for display purposes. $pg{timeAssignDue} = '11:59pm'; -# number of minutes prior to due date that the assignment is open. +# Number of minutes prior to due date that newly created sets +# are set to open. $pg{assignOpenPriorToDue} = 10080; -#number of minutes after due date are the answers open; +# Number of minutes after due date that newly created sets are +# set for answers to be made available; $pg{answersOpenAfterDueDate} = 2880; +############################################################################### +# Default Test settings +############################################################################### + +# Cap for the number of problems that can be used on a page. +# If 0, there is no cap. Otherwise, should be a positive integer. +$test{maxProblemsPerPage} = 0; + ############################################################################### # Progress Bar switch ############################################################################### @@ -1389,747 +1462,6 @@ $pg{options}{enableProgressBar} = 1; ################################################################################ # Site wide overrides are entered into the file localOverrides.conf ################################################################################ -# This is loaded before the $ConfigValues definition, so that the if values of -# $hardcopyThemes are overridden in localOverrides.conf, then those values are -# used and shown in the course configuration. include("conf/localOverrides.conf"); -################################################################################ -# WeBWorK::ContentGenerator::Instructor::Config -################################################################################ - -# Configuration data -# It is organized by section. The allowable types are -# 'text' for a text string (no quotes allowed), -# 'number' for a number, -# 'list' for a list of text strings, -# 'permission' for a permission value, -# 'boolean' for variables which really hold 0/1 values as flags, -# 'timezone' for a time zone, -# 'time' for a time, -# 'checkboxlist' for variables that hold a list of values which can be independently picked yes/no as checkboxes, -# 'popuplist' for variables that hold a list of values to be selected from. - -# Localization Info: The doc strings in this portion are reproduced in -# lib/WeBWorK/Localize.pm solely so that xgettext.pl will -# include them when creating .pot files. -# If you change these strings you should make the corresponding changes in -# Localize.pm - -# This is a dummy function used to mark strings in the config values for localization. -# The method in lib/WeBWorK/Utils.pm cannot be used here. -sub x { return @_; } - -$ConfigValues = [ - [ - x('General'), - { - var => 'courseFiles{course_info}', - doc => x('Name of course information file'), - doc2 => x( - 'The name of course information file (located in the templates directory). ' - . 'Its contents are displayed in the right panel next to the list of homework sets.' - ), - type => 'text' - }, - { - var => 'defaultTheme', - doc => x('Theme (refresh page after saving changes to reveal new theme.)'), - doc2 => x( - 'There is one main theme to choose from: math4. It has three variants: math4-green, math4-red, and ' - . 'math4-yellow. The theme specifies a unified look and feel for the WeBWorK course web pages.' - ), - values => [qw(math4 math4-green math4-red)], - type => 'popuplist', - hashVar => '{defaultTheme}' - }, - { - var => 'language', - doc => x('Language (refresh page after saving changes to reveal new language.)'), - doc2 => x('WeBWorK currently has translations for the languages listed in the course configuration.'), - values => [qw(en tr es fr zh-HK he)], - type => 'popuplist' - }, - { - var => 'perProblemLangAndDirSettingMode', - doc => x('Mode in which the LANG and DIR settings for a single problem are determined.'), - doc2 => x( - '

Mode in which the LANG and DIR settings for a single problem are determined.

The system will ' - . 'set the LANGuage attribute to either a value determined from the problem, a course-wide ' - . 'default, or the system default, depending on the mode selected. The tag will only be added to ' - . 'the DIV enclosing the problem if it is different than the value which should be set in the main ' - . 'HTML tag set for the entire course based on the course language.

There are two options ' - . 'for the DIRection attribute: "ltr" for left-to-write scripts, and "rtl" for right-to-left ' - . 'scripts like Arabic and Hebrew.

The DIRection attribute is needed to trigger proper ' - . 'display of the question text when the problem text-direction is different than that used by the ' - . 'current language of the course. For example, English problems from the library browser would ' - . 'display improperly in RTL mode for a Hebrew course, unless the problen Direction is set to LTR.' - . '

The feature to set a problem language and direction was only added in 2018 to the PG ' - . 'language, so most problems will not declare their language, and the system needs to fall back ' - . 'to determining the language and direction in a different manner. The OPL itself is all English, ' - . 'so the system wide fallback is to en-US in LTR mode.

Since the defaults fall back ' - . 'to the LTR direction, most sites should be fine with the "auto::" mode, but may want to select ' - . 'the one which matches their course language. The mode "force::ltr" would also be an option for ' - . 'a course which runs into trouble with the "auto" modes.

Modes:

  • "none" prevents ' - . 'any additional LANG and/or DIR tag being added. The browser will use the main setting which was ' - . 'applied to the entire HTML page. This is likely to cause trouble when a problem of the other ' - . 'direction is displayed.
  • "auto::" allows the system to make the settings based on the ' - . 'language and direction reported by the problem (a new feature, so not set in almost all ' - . 'existing problems) and falling back to the expected default of en-US in LTR mode.
  • ' - . '
  • "auto:LangCode:Dir" allows the system to make the settings based on the language and ' - . 'direction reported by the problem (a new feature, so not set in almost all existing problems) ' - . 'but falling back to the language with the given LangCode and the direction Dir when problem ' - . 'settings are not available from PG.
  • "auto::Dir" for problems without PG settings, this ' - . 'will use the default en=english language, but force the direction to Dir. Problems with PG ' - . 'settings will get those settings.
  • "auto:LangCode:" for problems without PG settings, ' - . 'this will use the default LTR direction, but will set the language to LangCode.Problems with PG ' - . 'settings will get those settings.
  • "force:LangCode:Dir" will ignore any setting ' - . 'made by the PG code of the problem, and will force the system to set the language with the ' - . 'given LangCode and the direction to Dir for all problems.
  • "force::Dir" will ' - . 'ignore any setting made by the PG code of the problem, and will force the system to set ' - . 'the direction to Dir for all problems, but will avoid setting any language attribute for ' - . 'individual problem.
' - ), - values => [ - qw(none auto:: force::ltr force::rtl force:en:ltr auto:en:ltr force:tr:ltr auto:tr:ltr force:es:ltr - auto:es:ltr force:fr:ltr auto:fr:ltr force:zh_hk:ltr auto:zh_hk:ltr force:he:rtl auto:he:rtl) - ], - type => 'popuplist' - }, - { - var => 'sessionKeyTimeout', - doc => x('Inactivity time before a user is required to login again'), - doc2 => x( - 'Length of time, in seconds, a user has to be inactive before he is required to login again. ' - . 'This value should be entered as a number, so as 3600 instead of 60*60 for one hour.' - ), - type => 'number' - }, - { - var => 'siteDefaults{timezone}', - doc => x('Timezone for the course'), - doc2 => x( - '

Some servers handle courses taking place in different timezones. If this course is not showing ' - . 'the correct timezone, enter the correct value here. The format consists of unix times, such ' - . 'as "America/New_York", "America/Chicago", "America/Denver", "America/Phoenix" or ' - . '"America/Los_Angeles".

Complete list: ' - . 'TimeZoneFiles' - ), - type => 'timezone', - hashVar => '{siteDefaults}->{timezone}' - }, - { - var => 'hardcopyThemes', - doc => x('Enabled Site Hardcopy Themes'), - doc2 => x( - 'Choose which of the site PDF hardcopy themes are available. In addition to the themes selected here, ' - . 'all themes in the course hardcopyThemes folder will be available. Your selection must be saved ' - . 'and then the page must be reloaded before the new list of enabled themes will be reflected ' - . 'in the selections that follows.' - ), - values => [qw(empty.xml)], - type => 'checkboxlist', - min => 1, - hashVar => '{hardcopyThemes}' - }, - { - var => 'hardcopyTheme', - doc => x('Hardcopy Theme'), - doc2 => x('Choose a layout/styling theme for PDF hardcopy production.'), - values => [qw(empty.xml)], - type => 'popuplist', - hashVar => '{hardcopyTheme}' - }, - { - var => 'hardcopyThemePGEditor', - doc => x('Hardcopy Theme for Problem Editor'), - doc2 => x('Choose a layout/styling theme for PDF hardcopy production from the Prooblem Editor.'), - values => [qw(empty.xml)], - type => 'popuplist', - hashVar => '{hardcopyThemePGEditor}' - }, - { - var => 'showCourseHomeworkTotals', - doc => x('Show Total Homework Grade on Grades Page'), - doc2 => x( - 'When this is on students will see a line on the Grades page which has their total cumulative ' - . 'homework score. This score includes all sets assigned to the student.' - ), - type => 'boolean' - }, - { - var => 'pg{options}{enableProgressBar}', - doc => x('Enable Progress Bar and current problem highlighting'), - doc2 => x( - 'A switch to govern the use of a Progress Bar for the student; this also enables/disables the ' - . 'highlighting of the current problem in the side bar, and whether it is correct (✓), ' - . 'in progress (…), incorrect (✗), or unattempted (no symbol).' - ), - type => 'boolean' - }, - { - var => 'pg{timeAssignDue}', - doc => x('Default Time that the Assignment is Due'), - doc2 => x( - 'The time of the day that the assignment is due. This can be changed on an individual basis, ' - . 'but WeBWorK will use this value for default when a set is created.' - ), - type => 'time', - hashVar => '{pg}->{timeAssignDue}' - }, - { - var => 'pg{assignOpenPriorToDue}', - doc => x('Default Amount of Time (in minutes) before Due Date that the Assignment is Open'), - doc2 => x( - 'The amount of time (in minutes) before the due date when the assignment is opened. You can change ' - . 'this for individual homework, but WeBWorK will use this value when a set is created.' - ), - type => 'number', - hashVar => '{pg}->{assignOpenPriorToDue}' - }, - { - var => 'pg{answersOpenAfterDueDate}', - doc => x('Default Amount of Time (in minutes) after Due Date that Answers are Open'), - doc2 => x( - 'The amount of time (in minutes) after the due date that the Answers are available to student to ' - . 'view. You can change this for individual homework, but WeBWorK will use this value when a set ' - . 'is created.' - ), - type => 'number', - hashVar => '{pg}->{answersOpenAfterDueDate}' - }, - ], - [ - x('Optional Modules'), - { - var => 'achievementsEnabled', - doc => x('Enable Course Achievements'), - doc2 => x( - 'Activiating this will enable Mathchievements for webwork. Mathchievements can be managed ' - . 'by using the Achievement Editor link.' - ), - type => 'boolean' - }, - { - var => 'achievementPointsPerProblem', - doc => x('Achievement Points Per Problem'), - doc2 => x('This is the number of achievement points given to each user for completing a problem.'), - type => 'number' - }, - { - var => 'achievementItemsEnabled', - doc => x('Enable Achievement Rewards'), - doc2 => x( - 'Activating this will enable achievement rewards. This feature allows students to earn rewards by ' - . 'completing achievements that allow them to affect their homework in a limited way.' - ), - type => 'boolean' - }, - { - var => 'achievementExcludeSet', - doc => x('List of sets excluded from achievements'), - doc2 => x( - 'Comma separated list of set names that are excluded from all achievements. ' - . 'No achievement points and badges can be earned for submitting problems in these sets. ' - . 'Note that underscores (_) must be used for spaces in set names.' - ), - type => 'list' - }, - { - var => 'options{enableConditionalRelease}', - doc => x('Enable Conditional Release'), - doc2 => x( - 'Enables the use of the conditional release system. To use conditional release you need to specify a ' - . 'list of set names on the Problem Set Detail Page, along with a minimum score. Students will ' - . 'not be able to access that homework set until they have achieved the minimum score on all of ' - . 'the listed sets.' - ), - type => 'boolean' - }, - { - var => 'pg{ansEvalDefaults}{enableReducedScoring}', - doc => x('Enable Reduced Scoring'), - doc2 => x( - '

This sets whether the Reduced Scoring system will be enabled. If enabled you will need to set the ' - . 'default length of the reduced scoring period and the value of work done in the reduced scoring ' - . 'period below.

To use this, you also have to enable Reduced Scoring for individual ' - . 'assignments and set their Reduced Scoring Dates by editing the set data.

This works with ' - . 'the avg_problem_grader (which is the the default grader) and the std_problem_grader (the all ' - . 'or nothing grader). It will work with custom graders if they are written appropriately.

' - ), - type => 'boolean' - }, - { - var => 'pg{ansEvalDefaults}{reducedScoringValue}', - doc => x('Value of work done in Reduced Scoring Period'), - doc2 => x( - '

After the Reduced Scoring Date all additional work done by the student counts at a reduced rate. ' - . 'Here is where you set the reduced rate which must be a percentage. For example if this value ' - . 'is 50% and a student views a problem during the Reduced Scoring Period, they will see the ' - . 'message "You are in the Reduced Scoring Period: All additional work done counts 50% of the ' - . 'original."

To use this, you also have to enable Reduced Scoring and set the Reduced ' - . 'Scoring Date for individual assignments by editing the set data using the Hmwk Sets Editor.

' - . '

This works with the avg_problem_grader (which is the the default grader) and the ' - . 'std_problem_grader (the all or nothing grader). It will work with custom graders if they ' - . 'are written appropriately.

' - ), - labels => { - '0.1' => '10%', - '0.15' => '15%', - '0.2' => '20%', - '0.25' => '25%', - '0.3' => '30%', - '0.35' => '35%', - '0.4' => '40%', - '0.45' => '45%', - '0.5' => '50%', - '0.55' => '55%', - '0.6' => '60%', - '0.65' => '65%', - '0.7' => '70%', - '0.75' => '75%', - '0.8' => '80%', - '0.85' => '85%', - '0.9' => '90%', - '0.95' => '95%', - '1' => '100%' - }, - values => [qw(1 0.95 0.9 0.85 0.8 0.75 0.7 0.65 0.6 0.55 0.5 0.45 0.4 0.35 0.3 0.25 0.2 0.15 0.1)], - type => 'popuplist' - }, - { - var => 'pg{ansEvalDefaults}{reducedScoringPeriod}', - doc => x('Default Length of Reduced Scoring Period in minutes'), - doc2 => x( - 'The Reduced Scoring Period is the default period before the due date during which all additional work ' - . 'done by the student counts at a reduced rate. When enabling reduced scoring for a set the ' - . 'reduced scoring date will be set to the due date minus this number. The reduced scoring date ' - . 'can then be changed. If the Reduced Scoring is enabled and if it is after the reduced scoring ' - . 'date, but before the due date, a message like "This assignment has a Reduced Scoring Period ' - . 'that begins 11/08/2009 at 06:17pm EST and ends on the due date, 11/10/2009 at 06:17pm EST. ' - . 'During this period all additional work done counts 50% of the original." will be displayed.' - ), - type => 'number' - }, - { - var => 'pg{options}{enableShowMeAnother}', - doc => x('Enable Show Me Another button'), - doc2 => x( - 'Enables use of the Show Me Another button, which offers the student a newly-seeded version ' - . 'of the current problem, complete with solution (if it exists for that problem).' - ), - type => 'boolean' - }, - { - var => 'pg{options}{showMeAnotherDefault}', - doc => x('Default number of attempts before Show Me Another can be used (-1 => Never)'), - doc2 => x( - 'This is the default number of attempts before show me another becomes available to students. ' - . 'It can be set to -1 to disable show me another by default.' - ), - type => 'number' - }, - { - var => 'pg{options}{showMeAnotherMaxReps}', - doc => x('Maximum times Show me Another can be used per problem (-1 => unlimited)'), - doc2 => x( - 'The Maximum number of times Show me Another can be used per problem by a student. ' - . 'If set to -1 then there is no limit to the number of times that Show Me Another can be used.' - ), - type => 'number' - }, - { - var => 'pg{options}{showMeAnother}', - doc => x('List of options for Show Me Another button'), - doc2 => x( - '
  • SMAcheckAnswers: enables the Check Answers button for the new problem when ' - . 'Show Me Another is clicked
  • SMAshowSolutions: shows walk-through solution ' - . 'for the new problem when Show Me Another is clicked; a check is done first to make ' - . 'sure that a solution exists
  • SMAshowCorrect: correct answers for the new ' - . 'problem can be viewed when Show Me Another is clicked; note that SMAcheckAnswers' - . 'needs to be enabled at the same time
  • SMAshowHints: show hints for the new ' - . 'problem (assuming they exist)
Note: there is very little point enabling the ' - . 'button unless you check at least one of these options - the students would simply see a new ' - . 'version that they can not attempt or learn from.' - ), - min => 0, - values => [ "SMAcheckAnswers", "SMAshowSolutions", "SMAshowCorrect", "SMAshowHints" ], - type => 'checkboxlist' - }, - { - var => 'pg{options}{enablePeriodicRandomization}', - doc => x('Enable periodic re-randomization of problems'), - doc2 => x( - 'Enables periodic re-randomization of problems after a given number of attempts. Student would have ' - . 'to click Request New Version to obtain new version of the problem and to continue working on ' - . 'the problem' - ), - type => 'boolean' - }, - { - var => 'pg{options}{periodicRandomizationPeriod}', - doc => x('The default number of attempts between re-randomization of the problems ( 0 => never)'), - doc2 => x('The default number of attempts before the problem is re-randomized. ( 0 => never )'), - type => 'number' - }, - { - var => 'pg{options}{showCorrectOnRandomize}', - doc => x('Show the correct answer to the current problem before re-randomization.'), - doc2 => x( - 'Show the correct answer to the current problem on the last attempt before a new version is ' - . 'requested.' - ), - type => 'boolean' - }, - ], - [ - x('Permissions'), - { - var => 'permissionLevels{login}', - doc => x('Allowed to login to the course'), - type => 'permission' - }, - { - var => 'permissionLevels{change_password}', - doc => x('Allowed to change their password'), - doc2 => x( - 'Users at this level and higher are allowed to change their password. ' - . 'Normally guest users are not allowed to change their password.' - ), - type => 'permission' - }, - { - var => 'permissionLevels{become_student}', - doc => x('Allowed to act as another user'), - type => 'permission' - }, - { - var => 'permissionLevels{submit_feedback}', - doc => x('Can e-mail instructor'), - doc2 => x('Only this permission level and higher get buttons for sending e-mail to the instructor.'), - type => 'permission' - }, - { - var => 'permissionLevels{record_answers_when_acting_as_student}', - doc => x('Can submit answers for a student'), - doc2 => - x('When acting as a student, this permission level and higher can submit answers for that student.'), - type => 'permission' - }, - { - var => 'permissionLevels{report_bugs}', - doc => x('Can report bugs'), - doc2 => x( - 'Users with at least this permission level get a link in the left panel for reporting bugs to the ' - . 'bug tracking system at bugs.webwork.maa.org.' - ), - type => 'permission' - }, - { - var => 'permissionLevels{change_email_address}', - doc => x('Allowed to change their e-mail address'), - doc2 => x( - 'Users at this level and higher are allowed to change their e-mail address. Normally guest users are ' - . 'not allowed to change the e-mail address since it does not make sense to send e-mail to ' - . 'anonymous accounts.' - ), - type => 'permission' - }, - { - var => 'permissionLevels{change_pg_display_settings}', - doc => x('Allowed to change display settings used in pg problems'), - doc2 => x( - 'Users at this level and higher are allowed to change display settings used in pg problems.' - . 'Note that if it is expected that there will be students that have vision impairments and ' - . 'MathQuill is enabled to assist with answer entry, then you should not set this permission to a ' - . 'level above student as those students may need to disable MathQuill.' - ), - type => 'permission' - }, - { - var => 'permissionLevels{view_answers}', - doc => x('Allowed to view past answers'), - doc2 => x('These users and higher get the "Show Past Answers" button on the problem page.'), - type => 'permission' - }, - { - var => 'permissionLevels{view_unopened_sets}', - doc => x('Allowed to view problems in sets which are not open yet'), - type => 'permission' - }, - { - var => 'permissionLevels{show_correct_answers_before_answer_date}', - doc => x('Allowed to see the correct answers before the answer date'), - type => 'permission' - }, - { - var => 'permissionLevels{show_solutions_before_answer_date}', - doc => x('Allowed to see solutions before the answer date'), - type => 'permission' - }, - { - var => 'permissionLevels{can_show_old_answers}', - doc => x('Can show old answers'), - doc2 => x( - 'When viewing a problem, WeBWorK usually puts the previously submitted answer in the answer blank. ' - . 'Below this level, old answers are never shown. Typically, that is the desired behaviour for ' - . 'guest accounts.' - ), - type => 'permission' - }, - { var => 'permissionLevels{navigation_allowed}', - doc => 'Allowed to view course home page', - doc2 => 'If a user does not have this permission, then the user will not be allowed to navigate to the ' - . 'course home page, i.e., the Homework Sets page. This should only be used for a course when LTI ' - . 'authentication is used, and is most useful when LTIGradeMode is set to homework. In this case the ' - . 'Homework Sets page is not useful and can even be confusing to students. To use this feature set ' - . 'this permission to "login_proctor".', - type => 'permission' - }, - ], - [ - x('Problem Display/Answer Checking'), - { - var => 'pg{displayModes}', - doc => x('List of display modes made available to students'), - doc2 => x( - '

When viewing a problem, users may choose different methods of rendering formulas via an options ' - . 'box in the left panel. Here, you can adjust what display modes are listed.

Some display ' - . 'modes require other software to be installed on the server. Be sure to check that all display ' - . 'modes selected here work from your server.

The display modes are

  • plainText: ' - . 'shows the raw LaTeX strings for formulas.
  • images: produces images using the external ' - . 'programs LaTeX and dvipng.
  • MathJax: a successor to jsMath, uses javascript to place ' - . 'render mathematics.

You must use at least one display mode. If you select only ' - . 'one, then the options box will not give a choice of modes (since there will only be one active).' - . '

' - ), - min => 1, - values => [ "MathJax", "images", "plainText" ], - type => 'checkboxlist' - }, - { - var => 'pg{options}{displayMode}', - doc => x('The default display mode'), - doc2 => - x('Enter one of the allowed display mode types above. See \'display modes entry\' for descriptions.'), - min => 1, - values => [qw(MathJax images plainText)], - type => 'popuplist' - }, - { - var => 'pg{specialPGEnvironmentVars}{entryAssist}', - doc => x('Assist with the student answer entry process.'), - #doc2 => x( - # '

MathQuill renders students answers in real-time as they type on the keyboard.

MathView ' - # . 'allows students to choose from a variety of common math structures (such as fractions and ' - # . 'square roots) as they attempt to input their answers.

WIRIS provides a separate workspace ' - # . 'for students to construct their response in a WYSIWYG environment.

' - #), - doc2 => x( - '

MathQuill renders students answers in real-time as they type on the keyboard.

MathView ' - . 'allows students to choose from a variety of common math structures (such as fractions and ' - . 'square roots) as they attempt to input their answers.

' - ), - min => 1, - values => [qw(None MathQuill MathView)], - type => 'popuplist' - }, - { - var => 'pg{options}{showEvaluatedAnswers}', - doc => x('Display the evaluated student answer'), - doc2 => x( - 'Set to true to display the "Entered" column which automatically shows the evaluated student answer, ' - . 'e.g., 1 if student input is sin(pi/2). If this is set to false, e.g., to save space in the ' - . 'response area, the student can still see their evaluated answer by clicking on the typeset ' - . 'version of their answer.' - ), - type => 'boolean' - }, - { - var => 'pg{ansEvalDefaults}{useBaseTenLog}', - doc => x('Use log base 10 instead of base e'), - doc2 => x('Set to true for log to mean base 10 log and false for log to mean natural logarithm.'), - type => 'boolean' - }, - { - var => 'pg{specialPGEnvironmentVars}{useOldAnswerMacros}', - doc => x('Use older answer checkers'), - doc2 => x( - '

During summer 2005, a newer version of the answer checkers was implemented for answers which are ' - . 'functions and numbers. The newer checkers allow more functions in student answers, and behave ' - . 'better in certain cases. Some problems are specifically coded to use new (or old) answer ' - . 'checkers. However, for the bulk of the problems, you can choose what the default will be here.' - . '

Choosing false here means that the newer answer checkers will be used by default, ' - . 'and choosing true means that the old answer checkers will be used by default.

' - ), - type => 'boolean' - }, - { - var => 'pg{specialPGEnvironmentVars}{parseAlternatives}', - doc => x('Allow Unicode alternatives in student answers'), - doc2 => x( - 'Set to true to allow students to enter Unicode versions of some characters (like U+2212 for the ' - . 'minus sign) in their answers. One reason to allow this is that copying and pasting output ' - . 'from MathJax can introduce these characters, but it is also getting easier to enter these ' - . 'characters directory from the keyboard.' - ), - type => 'boolean' - }, - { - var => 'pg{specialPGEnvironmentVars}{convertFullWidthCharacters}', - doc => x('Automatically convert Full Width Unicode characters to their ASCII equivalents'), - doc2 => x( - 'Set to true to have Full Width Unicode character (U+FF01 to U+FF5E) converted to their ASCII ' - . 'equivalents (U+0021 to U+007E) automatically in MathObjects. This may be valuable for Chinese ' - . 'keyboards, for example, that automatically use Full Width characters for parentheses and ' - . 'commas.' - ), - type => 'boolean' - }, - { - var => 'pg{ansEvalDefaults}{numRelPercentTolDefault}', - doc => x('Allowed error, as a percentage, for numerical comparisons'), - doc2 => x( - 'When numerical answers are checked, most test if the student\'s answer is close enough to the ' - . 'programmed answer be computing the error as a percentage of the correct answer. This value ' - . 'controls the default for how close the student answer has to be in order to be marked correct.' - . '

A value such as 0.1 means 0.1 percent error is allowed.

' - ), - type => 'number' - }, - { - var => 'pg{specialPGEnvironmentVars}{waiveExplanations}', - doc => x('Skip explanation essay answer fields'), - doc2 => x( - 'Some problems have an explanation essay answer field, typically following a simpler answer field. ' - . 'For example, find a certain derivative using the definition. An answer blank would be present ' - . 'for the derivative to be automatically checked, and then there would be a separate essay answer ' - . 'field to show the steps of actually using the definition of the derivative, to be scored ' - . 'manually. With this setting, the essay explanation fields are supperessed. Instructors may ' - . 'use the exercise without incurring the manual grading.' - ), - type => 'boolean' - }, - { - var => 'pg{options}{showHintsAfter}', - doc => x('Default number of attempts before hints are shown in a problem (-1 => hide hints)'), - doc2 => x( - 'This is the default number of attempts a student must make before hints will be shown to the student. ' - . 'Set this to -1 to hide hints. Note that this can be overridden with a per problem setting.' - ), - type => 'number' - }, - { - var => 'problemGraderScore', - doc => x('Method to enter problem scores in the single problem manual grader'), - doc2 => x( - 'This configures if the single problem manual grader has inputs to enter problem scores as a percent, ' - . 'a point value, or both. Note, the problem score is always saved as a percent, so when ' - . 'using a point value, the problem score will be rounded to the nearest whole percent.' - ), - values => [qw(Percent Point Both)], - type => 'popuplist' - }, - { - var => 'pg{options}{enterKey}', - doc => x('Enter Key Behavior'), - doc2 => x( - 'If this is set to "preview", hitting the enter key on a homework problem page activates the "Preview ' - . 'My Answers" button. If this is set to "submit", then the enter key activates the "Submit ' - . 'Answers" button instead. Or if that button is not present, it will activate the "Check ' - . 'Answers" button. Or if that button is also not present, it will activate the "Preview My ' - . 'Answers" button. A third option is "conservative". In this case, the enter key behaves like ' - . '"preview" when the "Submit" button is available and there are only finitely many attempts ' - . 'allowed. Otherise the enter key behaves like "submit". Note that this is only affects ' - . 'homework problem pages, not test/quiz pages, and not instructor pages like the PG Editor ' - . 'and the Library Browser.' - ), - type => 'popuplist', - values => ['preview', 'submit', 'conservative'] - } - ], - [ - x('E-Mail'), - { - var => 'mail{feedbackSubjectFormat}', - doc => x('Format for the subject line in feedback e-mails'), - doc2 => x( - 'When students click the Email Instructor button to send feedback, WeBWorK fills in the ' - . 'subject line. Here you can set the subject line. In it, you can have various bits of ' - . 'information filled in with the following escape sequences.

  • %c = course ID
  • ' - . '
  • %u = user ID
  • %s = set ID
  • %p = problem ID
  • %x = section
  • ' - . '
  • %r = recitation
  • %% = literal percent sign
' - ), - width => 45, - type => 'text' - }, - { - var => 'mail{feedbackVerbosity}', - doc => x('E-mail verbosity level'), - doc2 => x( - 'The e-mail verbosity level controls how much information is automatically added to feedback e-mails. ' - . 'Levels are
  1. Simple: send only the feedback comment and context link
  2. ' - . '
  3. Standard: as in Simple, plus user, set, problem, and PG data
  4. ' - . '
  5. Debug: as in Standard, plus the problem environment (debugging data)
  6. ' - . '
' - ), - labels => { - '0' => 'Simple', - '1' => 'Standard', - '2' => 'Debug' - }, - values => [qw(0 1 2)], - type => 'popuplist' - - }, - { - var => 'permissionLevels{receive_feedback}', - doc => x('Permission levels for receiving feedback email'), - doc2 => x( - 'Users with these permission levels will be sent feedback emails from students when they use the ' - . 'feedback button.' - ), - type => 'permission_checkboxlist', - }, - { - var => 'mail{feedbackRecipients}', - doc => x('Additional addresses for receiving feedback email'), - doc2 => x( - 'By default, feedback is sent to all users above who have permission to receive feedback. Feedback ' - . 'is also sent to any addresses specified here. Separate email address entries with commas.' - ), - type => 'list' - }, - { - var => 'feedback_by_section', - doc => x('Feedback by Section.'), - doc2 => x( - 'By default, feedback is always sent to all users specified to recieve feedback. This variable sets ' - . 'the system to only email feedback to users who have the same section as the user initiating the ' - . 'feedback. I.e., feedback will only be sent to section leaders.' - ), - type => 'boolean' - }, - ], - ['LTI', - { var => 'lti_advantage{auto_assign_users_to_sets}', - doc => 'Automatically assign users to sets', - doc2 => 'By default, users are automatically assigned to all sets when they launch into WeBWorK from the LMS or when the class roster is synced.', - type => 'boolean' - }, - { var => 'lti_advantage{cron_grade_sync}', - doc => 'Automatically send student grades to LMS via cron job', - doc2 => 'By default, student grades are automatically sent to the LRS via a cron job.', - type => 'boolean' - }, - { var => 'lti_advantage{cron_roster_sync}', - doc => 'Automatically get roster from LMS via cron job', - doc2 => 'By default, class roster is automatically fetched from the LMS via a cron job.', - type => 'boolean' - }, - ], -]; - -include('conf/LTIConfigValues.config'); - 1; #final line of the file to reassure perl that it was read properly. diff --git a/conf/localOverrides.conf.dist b/conf/localOverrides.conf.dist index a49a0de6dd..60922c91e9 100644 --- a/conf/localOverrides.conf.dist +++ b/conf/localOverrides.conf.dist @@ -1,18 +1,4 @@ #!perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ # This file should be used to override any of the default settings in defaults.config. # The most commonly changed settings are provided as examples below, but any directive @@ -26,18 +12,34 @@ # localOverrides.conf contains the local modifications commonly made # when installing WeBWorK on a new site. The configurations in defaults.config -# and in database.conf can usually remain untouched. +# should not be changed. # # localOverride.conf is the appropriate place to override permission settings, # paths to macros and other customizations that are specific to your # WeBWorK site - - ################################################################################ # Additional mail settings in defaults.config can be overridden here ################################################################################ +# $generic_sender_name will be used as the "From:" name on all feedback emails +# sent without a defined user. + +$generic_sender_name = ''; + +# The following variables will override the "From:" address in messages sent by +# the named feature. +# When one of these is set, all messages sent by that feature will use the +# supplied email address as the "From:" address, and the address of the relevant +# user will be set as the "Reply-to:" address for the message. +# These may be required if you have provided an SMTP username and password in +# site.conf. They can also be used to improve email verification and avoid +# messages getting filtered as spam. + +$feedback_sender_email = ''; # For student feedback +$instructor_sender_email = ''; # For instructors emailing students +$jitar_sender_email = ''; # For notifications of incomplete JiTaR sets + # By default, feedback is sent to all users who have permission to # receive_feedback in a course. If this list is non-empty, feedback is also sent # to the addresses specified here. @@ -79,7 +81,6 @@ $webwork_server_admin_email = $ENV{'WEBWORK_SUPPORT_EMAIL'} // 'lt.hub@ubc.ca'; ################################################################################ #$defaultTheme = "math4"; -#$defaultThemeTemplate = "system"; # The institution logo should be an image file in the theme's images folder #$institutionLogo = 'my_school_logo.png'; @@ -208,7 +209,7 @@ $courseFiles{problibs} = { # $permissionLevels{login} = "guest"; # The above code would give the permission to login to any user with permission -# level guest or higher. +# level guest or higher (which is the default). # By default answers for all users are logged to the past_answers table in the database # and the myCourse/logs/answer_log file. If you only want answers logged for users below @@ -244,7 +245,7 @@ $permissionLevels{create_and_delete_courses} = "designer"; # designers sometimes need to assign problem sets (e.g. troubleshooting) $permissionLevels{assign_problem_sets} = "designer"; -# TAs are asked to do a lot of instructor tasks +# TAs are asked to do a lot of instructor tasks $permissionLevels{modify_student_data} = "ta"; $permissionLevels{become_student} = "ta"; $permissionLevels{send_email} = "ta"; @@ -261,6 +262,24 @@ $permissionLevels{change_password} = "admin"; $permissionLevels{change_email_address} = "designer"; +################################################################################ +# Initial password fallback +################################################################################ + +# This sets the default fallback source to use for a user's password when a user +# account is created in a course. That is the source that will be shown by +# default in a select menu in the user interface, and the source that will be +# used by the importClassList.pl and addcourse scripts. It can be one of +# 'user_id', 'first_name', 'last_name', or 'student_id', or can be set to '' (or +# anything not listed before). If a user is created and no password is +# explicitly provided, then this source will used for the password (assuming +# that source value is also set). If this is not one of the allowed sources, +# and if a password is not explicitly provided, then the user will be created +# without a password and will not be able to sign in with username and password. +# Note that this is only used at the time that a user is initially created in a +# course, and not when editing passwords at a later time. +#$fallback_password_source = 'student_id'; + ################################################################################ # Default settings for the problem editor pages ################################################################################ @@ -281,7 +300,7 @@ $options{PGMathQuill}= 1; # List of enabled display modes. Comment out any modes you don't wish to make # available for use. # The first uncommented option is the default for instructors rendering problems -# in the homework sets editor. +# in the Library Browser and Set Detail page. #$pg{displayModes} = [ #"MathJax", # render TeX math expressions on the client side using MathJax # we strongly recommend people install and use MathJax, and it is required if you want to use mathview @@ -315,20 +334,20 @@ $options{PGMathQuill}= 1; ################################################################################ # The default weight (also called value) of a problem to use when using the -# Library Browser, Problem Editor or Hmwk Sets Editor to add problems to a set +# Library Browser, Problem Editor, or Sets Manager to add problems to a set # or when this value is left blank in an imported set definition file. $problemDefaults{value} = 1; # The default max_attempts for a problem to use when using the -# Library Browser, Problem Editor or Hmwk Sets Editor to add problems to a set +# Library Browser, Problem Editor, or Sets Manager to add problems to a set # or when this value is left blank in an imported set definition file. Note that # setting this to -1 gives students unlimited attempts. $problemDefaults{max_attempts} = -1; # The default showMeAnother for a problem to use when using the -# Library Browser, Problem Editor or Hmwk Sets Editor to add problems to a set +# Library Browser, Problem Editor, or Sets Manager to add problems to a set # or when this value is left blank in an imported set definition file. Note that # setting this to -1 disables the showMeAnother button $problemDefaults{showMeAnother} = -1; @@ -393,11 +412,9 @@ $problemDefaults{showMeAnother} = -1; # $pg{specialPGEnvironmentVars}{convertFullWidthCharacters} = 1; # Application that the PGtikz.pl and PGlateximage.pl macros will use to create svg images. -# This should be either 'pdf2svg' or 'dvisvgm'. The default is 'pdf2svg'. -# If the system version of latex is 3.14159265-2.6-1.40.20 (TeX Live 2019) or newer -# and the system version of dvisvgm is 2.8.1 or newer, then change this to 'dvisvgm' -# by uncommenting the line below. 'dvisvgm' will generally create better 'svg' -# images. +# This should be either 'pdf2svg' or 'dvisvgm'. The default is 'dvisvgm' as +# 'dvisvgm' will generally create a better 'svg'. However, if you see issues +# with that, then you may want to switch this to 'pdf2svg'. #$pg{specialPGEnvironmentVars}{latexImageSVGMethod} = "dvisvgm"; # When ImageMagick is used for image conversions, this sets the default options. @@ -442,16 +459,6 @@ $pg{specialPGEnvironmentVars}{entryAssist} = 'MathView'; ##################### #push (@{${pg}{modules}}, [qw(LaTeXImage)]); -################################################################################ -# Student Date Format -################################################################################ - -# Uncomment the following line to customize the format of the dates displayed to -# students. As it is written, the line below will display open, due and answer -# dates in the following format: Wed Jun 27 at 10:30am -# For all available options, consult the documentation for perl DateTime under -# "strftime patterns". -#$studentDateDisplayFormat="%a %b %d at %l:%M%P"; ################################################################################ # Using R with WeBWorK @@ -486,7 +493,7 @@ $pg{specialPGEnvironmentVars}{entryAssist} = 'MathView'; # END_PREAMBLE ################################################################################ -# Authentication Methods +# Authentication ################################################################################ # Extra modules have been created to allow WeBWorK to use certain external @@ -496,6 +503,44 @@ $authen{user_module} = [ 'WeBWorK::Authen::PreferExternalUnlessBypassed' ]; +# Select the authentication module to use for normal logins. +# If this value is a string, then that authentication module will be used. If +# this value is a reference to an array of strings, then each string in the +# array will be successively tested to see if it provides a module that can +# handle the authentication request (by calling that module's +# request_has_data_for_this_verification_module method). The first module that +# responds affirmatively will be used. +#$authen{user_module} = [ +# "WeBWorK::Authen::LDAP", +# "WeBWorK::Authen::Basic_TheLastOption" +#]; + +# Select the authentication module to use for proctor logins. +# A string or a hash is accepted, as above. +#$authen{proctor_module} = "WeBWorK::Authen::Proctor"; + +# List of authentication modules that may be used to enter the admin course. +# This is used instead of $authen{user_module} when logging into the admin course. +# Since the admin course provides overall power to add/delete courses, access +# to this course should be protected by the best possible authentication you +# have available to you. The current default is +# WeBWorK::Authen::Basic_TheLastOption which is simple password based +# authentication for a password locally stored in your WeBWorK server's +# database. On one hand, this is necessary as the initial setting, as it is the +# only option available when a new server is being installed, on the other hand, +# this option does not provide any capabilities to prevent dictionary attacks, etc. +# At the very least you should use a very strong password with two factor authentication. +# If you have the option to use a more secure authentication approach to the admin course +# (one which you are confident cannot be spoofed) that is preferable. +# +# Note that if you include authentication module config files further down, +# those may override the setting of $authen{admin_module} here. + +#$authen{admin_module} = [ +# 'WeBWorK::Authen::LDAP', +# 'WeBWorK::Authen::Basic_TheLastOption' +#]; + ################################################################################ # IMS LTI Authentication ################################################################################ @@ -524,67 +569,164 @@ $LTI{v1p3}{LMS_url} = 'http://canvas.docker:9100/'; #include("conf/authen_ldap.conf"); +################################################################################ +# Shibboleth Authentication +################################################################################ + +# Uncomment the following line to enable Shibboleth authentication. You will +# also need to copy the file authen_shibboleth.conf.dist to authen_shibboleth.conf, +# and then edit that file to fill in the settings for your installation. + +#include("conf/authen_shibboleth.conf"); + +################################################################################ +# Saml2 Authentication +################################################################################ +# Uncomment the following line to enable authentication via a Saml2 identity +# provider. You will also need to copy the file authen_saml2.conf.dist to +# authen_saml2.conf, and then edit that file to fill in the settings for your +# installation. + +#include("conf/authen_saml2.conf"); + ################################################################################ # Session Management ################################################################################ -## For a discussion of session_management_via session_cookies or the -## Key database, see the Session Management section -## of defaults.config.dist +# Session management can be handled either using the key database, the +# traditional method, or by using signed session cookies. + +# Setting $session_management_via="key" uses the key database for session +# management. If password authentication is used, then a user can opt to use a +# session cookie with a duration determined by the $sessionTimeout setting +# below by checking the "Remember Me" checkbox on the login page. + +# Setting $session_management_via="session_cookies" uses a session cookie to +# manage the session. Note that even in this case a key is stored in the +# database which is compared to the key stored in the session cookie. The +# lifetime of the cookie is determined by the $sessionTimeout setting below. -## For session management using the key database table, uncomment the following line, -## which will override the setting $session_management_via = "session_cookie" -## set in defaults.config. +# Note that the key database method is less secure as the key must be embeded in +# the page and added as a url parameter in order to maintain the session. These +# things can be accessed by malicious javascript. The session cookies are http +# only cookies which can not be accessed via javascript. + +# The default value for $session_management_via is "session_cookie". #$session_management_via = "key"; -## This is the length of time (in seconds) after which a user's session becomes -## invalid if they have no activity. The default is 30 minutes (60*30 seconds). +# This is the length of time (in seconds) after which a user's session becomes +# invalid if they have no activity. The default is 30 minutes (60*30 seconds). -#$sessionKeyTimeout = 60*60*2; +#$sessionTimeout = 60*60*2; ################################################################################ # Cookie control settings ################################################################################ -# The following variables can be set to control cookie behavior. - -# Set the value of the samesite attribute of the WeBWorK cookie: -# See: https://blog.chromium.org/2019/10/developers-get-ready-for-new.html -# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite -# https://tools.ietf.org/html/draft-west-cookie-incrementalism-00 - +# Set the value of the samesite attribute of the session cookie: +# See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite # Notes about the $CookieSameSite options: -# The "None" setting should only be used with HTTPS and when $CookieSecure = 1; is set below. The "None" setting is -# also less secure and can allow certain types of cross-site attacks. -# The "Strict" setting can break the links in the system generated feedback emails when read in a web mail client. -# Due to those factors, the "Lax" setting is probably the optimal choice for typical WeBWorK servers. - +# The "None" setting should only be used with HTTPS and when $CookieSecure is +# set to 1 below. The "None" setting is also less secure and can allow certain +# types of cross-site attacks. The "Strict" setting can break the links in the +# system generated feedback emails when read in a web mail client. Due to those +# factors, the "Lax" setting is probably the optimal choice for typical webwork2 +# servers. #$CookieSameSite = "None"; #$CookieSameSite = "Strict"; #$CookieSameSite = "Lax"; -# Set the value of the secure cookie attribute: -# Default is 0 here, as 1 will not work without https -#$CookieSecure = 1; - -# The CookieLifeTime setting determines how long the browser should retain the cookie. -# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie -# The CookieLifeTime value should be numeric and in seconds, or should be set to "session", in which case -# the Cookie will expire when the browser session ends (a "session cookie"). -# The default value is 7 days. -#$CookieLifeTime = 604800; -#$CookieLifeTime = "session"; +# Set the value of the secure cookie attribute. +# The default is 1, so if you are serving without https then set this to 0. +#$CookieSecure = 0; + +# If $useSessionCookie is set to 1, then a "session" cookie will be used. This +# means that the cookie will be deleted when the browser session ends. +# Typically, this is when the browser is closed. Note that the browser defines +# when this is, and some browser's also allow sessions to be restored when the +# browser is reopened. In any case, a user's session will end when the session +# is idle for more than the number of seconds the $sessionTimeout value is set +# to. +#$useSessionCookie = 1; + +################################################################################ +# Two Factor Authentication +################################################################################ + +# The following variables enable two factor authentication and control how it +# works. Two factor authentication only applies to courses that use password +# authentication, i.e., the Basic_TheLastOption user authentication module +# without an external authentication approach (like LTI, CAS, Shibboleth, etc.). +# It is recommended that two factor authentication be enabled for all courses +# that use password authentication. It is extremely highly recommended that this +# be enabled for the admin course. Two factor authentication works with an +# authenticator app on a mobile device (such as Google Authenticator, +# Microsoft authenticator, Twilio Authy, etc.). + +# $twoFA{enabled} determines if two factor authentication is enabled for a +# course. If this is set to 0, then two factor authentication is disabled for +# all courses. If this is 1 (the default), then two factor authentication is +# enabled for all courses that use password authentication. If this is a string +# course name like 'admin', then two factor authentication is enabled only for +# that course. If this is an array of string course names, then two factor +# authentication is enabled only for those courses listed. This can also be set +# in a course's course.conf file. Note that only the values of 0 and 1 make +# sense there. +#$twoFA{enabled} = $admin_course_id; # Use this at the very least. +#$twoFA{enabled} = [$admin_course_id, 'another_courseID', 'another_courseID_3']; + +# There are two methods that can be used to setup two factor authentication when +# a user signs in for the first time. The setup information can be emailed to +# the user, or can be directly displayed in the browser on the next page that is +# shown after password verification succeeds. +# +# If $twoFA{email_sender} is set, then the email approach will be used. In this +# case, after a user signs in and the password is verified, the user will be +# sent an email containing a QR code and instructions on how to set up a OTP +# generator app. This is probably a more secure way to set up two factor +# authentication, as it ensures the user setting it up is the correct user. Note +# that if a user does not have an email address, then the browser method below +# will be used as a fallback. +# +# If $twoFA{email_sender} is not set, then after a user signs in and the +# password is verified, the QR code, OTP link, and instructions will be +# displayed directly on the page in the browser. This is potentially less secure +# because a hacker could guess a username and password before a user has setup +# two factor authentication (particularly if the username and password are +# initially the same), and then the hacker would gain access to that user's +# account, and the actual user would be locked out. Note that you will need to +# use this option if your server can not send emails. Also note that no-reply +# addresses may be blocked by the email server or marked as spam. So it may be +# better to find a valid email address to use for this. +#$twoFA{email_sender} = 'noreply@your.school.edu'; + +# When a user signs in and enters the two factor authentication code, the user +# has the option to skip two factor verification on a given device for +# subsequent logins. That will only last for the amount of time set as the +# skip_verification_code_interval. By default this is set to one year. However, +# good security practices most likely recommend a shorter time interval for +# this. So change this value if you want to require a shorter and thus more +# secure time interval before users will need to enter the two factor +# authentication code again. +#$twoFA{skip_verification_code_interval} = 3600 * 24 * 7; + +# By default all users with the role of "student" or higher are required to use +# two factor authentication when signing in with a username and password. If +# you want to disable two factor authentication for students, but require it for +# instructors then set the permission level below to "login_proctor" (or +# higher). + +#$permissionLevels{use_two_factor_auth} = "login_proctor"; ################################################################################ # Searching for set.def files to import ################################################################################ -## Uncomment below so that when the homework sets editor searches for set def -## files, it searches beyond templates; it can search deeper subfolders of -## templates, and optionally also descend into Library +# In the set manager and the library browser, the directory depth to search +# within templates for .def files. Note that this does not apply to the Library +# and Contrib directories. Those directories are not searched in any case. -#$options{setDefSearchDepth}=4; #search down 4 levels -#$options{useOPLdefFiles}=1; +#$options{setDefSearchDepth} = 4; ################################################################################ # Permission overrides (e.g. "admin", "professor", "ta", "student", "guest" @@ -618,8 +760,14 @@ $LTI{v1p3}{LMS_url} = 'http://canvas.docker:9100/'; # 'hebrewTwoCol.xml', #]; -# The Hebrew themes need to use xelatex. Uncomment the following for xelatex -#$externalPrograms{pdflatex} ="/usr/bin/xelatex --no-shell-escape"; +# xelatex is the default external program used to generate pdf from LaTeX. It +# supports unicode characters (needed for multilingual use among other things). +# Newer versions of DateTime::Locale use a narrow no-break space in US dates. +# So xelatex is needed even for English. Note that --no-shell-escape is +# important for security reasons. You may be able to use pdflatex instead of +# xelatex if you have an older version of DateTime::Locale on your system. +# However, pdflatex does not support unicode characters. +#$externalPrograms{latex2pdf} = "/usr/bin/pdflatex --no-shell-escape"; # A course may have additional themes in $courseDirs{hardcopyThemes}. All such # "course" hardcopy themes are effectively enabled and offered for use when @@ -725,7 +873,11 @@ $delayed_job{enabled} = 1; ################################################################################ $lti_advantage{studentlog} = $webworkDirs{logs} . "/studentupdates.log"; # Set password for the admin user created for all imported courses. -$lti_advantage{adminuserpw} = "admin"; +$lti_advantage{adminuserpw} = $ENV{"LTI_ADMIN_PASSWORD"}; +# Note, not the raw webwork TOTP secret string, but the base32 encoded version +# of that string. Easy way to obtain this base32 encoded string is to scan the +# QR code with your MFA app and copy the secret string shown by the app. +$lti_advantage{adminusertotp} = $ENV{"LTI_ADMIN_TOTP"}; $lti_advantage{push_grades_on_submit} = 1; $lti_advantage{hide_new_courses} = 1; $lti_advantage{auto_assign_users_to_sets} = 1; @@ -824,4 +976,12 @@ push @{$pg{modules}}, $pg{specialPGEnvironmentVars}{Rserve} = {host => $ENV{"R_HOST"}}; +############################################################################### +# Test settings +############################################################################### + +# Cap for the number of problems that can be used on a page. +# If 0, there is no cap. Otherwise, should be a positive integer. +#$test{maxProblemsPerPage} = 1; + 1; #final line of the file to reassure perl that it was read properly. diff --git a/conf/site.conf.dist b/conf/site.conf.dist index 4a00f93e23..19ba71333c 100644 --- a/conf/site.conf.dist +++ b/conf/site.conf.dist @@ -1,18 +1,4 @@ #!perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# 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. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ # This file is used to set up the default WeBWorK course environment for all # requests. Values may be overwritten by the course.conf for a specific course. @@ -68,6 +54,26 @@ $server_root_url = ''; # Be sure to use single quotes for the address or the @ sign will be interpreted as an array. $webwork_server_admin_email = ''; +# The following is the name of the admin course where admin level users can create +# courses, delete courses, and more. It is named 'admin' by default but for security, +# you may want to change to something that cannot be guessed. +$admin_course_id = 'admin'; + +# When new courses are created using the admin course, this setting controls +# whether or not they will be hidden. Setting this to anything other than +# "hidden" or "visible" (or leaving it unset) means that courses created using +# "Add Courses" are not hidden, and all unarchived courses have whatever hidden +# status they had when archived. +# Setting this to "hidden" means that courses created using "Add Courses" and +# courses created using "Unarchive Courses" with a new course ID will be hidden. +# Unarchived courses that keep their original name will keep their hidden +# status. +# Setting this to "visible" means that courses created using "Add Courses" and +# courses created using "Unarchive Courses" with a new course ID will be visible. +# Unarchived courses that keep their original name will keep their hidden +# status. +#$new_courses_hidden_status = 'hidden'; + # password strings (or any other string allowing special characters) should be specified inside single quotes # otherwise a string such as "someone@nowhere" will interpolate the contents of the array @nowhere -- which is probably # empty, but still not what you want. Similar things happen with % and $ @@ -79,27 +85,23 @@ $webwork_server_admin_email = ''; # or even in /opt/local/bin. # You can use "which tar" for example to find out where the "tar" program is located -#################################################### # system utilities -#################################################### -$externalPrograms{mv} = "/bin/mv"; -$externalPrograms{cp} = "/bin/cp"; -$externalPrograms{rm} = "/bin/rm"; -$externalPrograms{mkdir} = "/bin/mkdir"; -$externalPrograms{tar} = "/bin/tar"; -$externalPrograms{gzip} = "/bin/gzip"; +$externalPrograms{tar} = "/usr/bin/tar"; $externalPrograms{git} = "/usr/bin/git"; -#################################################### # equation rendering/hardcopy utiltiies -#################################################### $externalPrograms{latex} = "/usr/bin/latex --no-shell-escape"; -$externalPrograms{pdflatex} = "/usr/bin/pdflatex --no-shell-escape"; -# Note that --no-shell-escape is important for security reasons. -# Consider using xelatex instead of pdflatex for multilingual use, and -# use polyglossia and fontspec packages (which require xelatex or lualatex). -#$externalPrograms{pdflatex} = "/usr/bin/xelatex --no-shell-escape"; +# xelatex is the default external program used to generate pdf from LaTeX. It +# supports unicode characters (needed for multilingual use among other things). +# Newer versions of DateTime::Locale use a narrow no-break space in US dates. So +# xelatex is needed even for English. Note that --no-shell-escape is important +# for security reasons. +$externalPrograms{latex2pdf} = "/usr/bin/xelatex --no-shell-escape"; +# You may be able to use pdflatex instead of xelatex if you have an older +# version of DateTime::Locale on your system. However, pdflatex does not +# support unicode characters. +#$externalPrograms{latex2pdf} = "/usr/bin/pdflatex --no-shell-escape"; $externalPrograms{dvipng} = "/usr/bin/dvipng"; @@ -111,43 +113,13 @@ $externalPrograms{convert} = "/usr/bin/convert"; $externalPrograms{dvisvgm} = "/usr/bin/dvisvgm"; $externalPrograms{pdf2svg} = "/usr/bin/pdf2svg"; -#################################################### -# NetPBM - basic image manipulation utilities -# Most sites only need to configure $netpbm_prefix. -#################################################### -my $netpbm_prefix = "/usr/bin"; -$externalPrograms{giftopnm} = "$netpbm_prefix/giftopnm"; -$externalPrograms{ppmtopgm} = "$netpbm_prefix/ppmtopgm"; -$externalPrograms{pnmtops} = "$netpbm_prefix/pnmtops"; -$externalPrograms{pnmtopng} = "$netpbm_prefix/pnmtopng"; -$externalPrograms{pngtopnm} = "$netpbm_prefix/pngtopnm"; - -#################################################### # curl -#################################################### $externalPrograms{curl} = "/usr/bin/curl"; -#################################################### -# image conversions utiltiies -# the source file is given on stdin, and the output expected on stdout. -#################################################### - -$externalPrograms{gif2eps} = "$externalPrograms{giftopnm} | $externalPrograms{ppmtopgm} | $externalPrograms{pnmtops} -noturn 2>/dev/null"; -$externalPrograms{png2eps} = "$externalPrograms{pngtopnm} | $externalPrograms{ppmtopgm} | $externalPrograms{pnmtops} -noturn 2>/dev/null"; -$externalPrograms{gif2png} = "$externalPrograms{giftopnm} | $externalPrograms{pnmtopng}"; - -#################################################### # mysql clients -#################################################### - $externalPrograms{mysql} ="/usr/bin/mysql"; $externalPrograms{mysqldump} ="/usr/bin/mysqldump"; - -#################################################### -# End paths to external utilities. -#################################################### - ################################################################################ # Database options ################################################################################ @@ -159,8 +131,8 @@ $externalPrograms{mysqldump} ="/usr/bin/mysqldump"; # where webworkWrite and passwordRW must match the corresponding variables in the next section. ################################################################################ -# these variables are used by database.conf. we define them here so that editing -# database.conf isn't necessary. +# these variables are used by the database layout (lib/WeBWorK/DB/Layout.pm) and the +# database connection. we define them here so that editing those files isn't necessary. # You must initialize the database and set the password for webworkWrite. # Edit the $database_password line and replace 'passwordRW' by the actual password used in the GRANT command above @@ -256,26 +228,16 @@ $webwork_courses_dir = "/opt/webwork/courses"; # a typical place to put course d # The following directives need to be configured in order for your webwork # server to be able to send mail. -# Mail sent by the PG system and the mail merge and feedback modules will be -# sent via this SMTP server. localhost may work if your server is capable -# of sending email, otherwise type the name of your School's outgoing email -# server. +# Mail sent by the mail merge and feedback modules will be sent via this SMTP +# server. localhost may work if your server is capable of sending email, +# otherwise type the name of your School's outgoing email server. $mail{smtpServer} = ''; # e.g. 'mail.yourschool.edu' or 'localhost' -# When connecting to the above server, WeBWorK will send this address in the -# MAIL FROM command. This has nothing to do with the "From" address on the mail -# message. It can really be anything, but some mail servers require it contain -# a valid mail domain, or at least be well-formed. -$mail{smtpSender} = ''; # e.g. 'webwork@yourserver.yourschool.edu' -# Be sure to use single quotes for the address or the @ sign will be interpreted as an array. - $mail{set_return_path} = ''; #sets the return_path to the From: field (sender's email address) # The return path is used to send error messages about bounced emails # "noreply\@$mail{smtpServer}" discards error messages, -# using $mail{smtpSender} would deliver error messages to that address. # The default setting should be adjusted for local domain # Leaving the return path blank triggers the default which results in Return-Path being set to the email of the sender. -# # Seconds to wait before timing out when connecting to the SMTP server. # the default is 120 seconds. @@ -284,32 +246,47 @@ $mail{set_return_path} = ''; #sets the return_path to the From: field (sender's $mail{smtpTimeout} = 30; - # TLS is a method for providing secure connections to the smtp server. # https://en.wikipedia.org/wiki/Transport_Layer_Security -# At some sites coordinating the certificates properly is tricky -# Set this value to 0 to avoid checking certificates. -# Set it to 0 to trouble shoot an inability to verify certificates with the smtp server +# Allowed values: 'starttls', 'ssl', 'maybestarttls', 0 +# Values of 'maybestarttls' and 0 are insecure and are not recommended for +# production environments, except where the smtp server is localhost. $mail{tls_allowed} = 0; -#$tls_allowed=0; #old method -- this variable no longer works. +# Extra settings for SSL/TLS +# You may need to use this setting if your SMTP server uses a self-signed certificate. +# SSL_verify_mode => 0 is not recommended for production environments for security +# reasons. See https://metacpan.org/pod/IO::Socket::SSL#Common-Usage-Errors +#$mail{smtpSSLOptions} = {SSL_verify_mode => 0}; # errors of the form -# unable to establish SMTP connection to smtp-gw.rochester.edu port 465 -# indicate that there is a mismatch between the port number and the use of ssl -# use port 25 when ssl is off and use port 465 when ssl is on (tls_allowed=1) +# "unable to establish SMTP connection to smtp-gw.rochester.edu port 465" +# indicate that there may be a mismatch between the port number and the use of ssl. +# Many mail servers use port 25 when ssl is off, use port 465 when ssl is on (tls_allowed='ssl'), +# and use port 587 when starttls is used (tls_allowed='starttls'). -# Set the SMTP port manually. Typically this does not need to be done it will use -# port 25 if no SSL is on and 465 if ssl is on +# Set the SMTP port manually. Typically this does not need to be done. It will use +# port 25 if insecure, and 465 if ssl is on #$mail{smtpPort} = 25; # Debugging tutorial for sending email using ssl/tls # https://maulwuff.de/research/ssl-debugging.html +# SMTP Authentication +# If your SMTP server requires authentication you can provide the username and password +# for the account on the mail server. +# If you set these credentials, you may need to define the variables $feedback_sender_email, +# $instructor_sender_email and $jitar_sender_email in localOverrides.conf, as some SMTP +# servers require the "From:" address of outgoing emails to match this username. Setting +# those sender variables will then put the user's email address in the "Reply-to:" field. + +#$mail{smtpUsername} = ''; +#$mail{smtpPassword} = ''; + # Set maxAttachmentSize to the maximum number of megabytes to allow for the size of # files attached to feedback emails. Note that this should be set to match the # limitations of the email server chosen above, and should be set to a value greater @@ -334,8 +311,8 @@ $job_queue{backend} = 'SQLite'; # Database dsn for the Minion job queue. Some examples of settings for the # respective backends follow. The postgres and mysql examples will need to be # modified to work. The default sqlite setting will work as is. -#$job_queue{database_dsn} = "postgresql://dbuser@/webwork2_job_queue"; -#$job_queue{database_dsn} = "mysql://dbuser:dbpasswd@localhost/webwork2_job_queue"; +#$job_queue{database_dsn} = 'postgresql://dbuser@/webwork2_job_queue'; +#$job_queue{database_dsn} = 'mysql://dbuser:dbpasswd@localhost/webwork2_job_queue'; $job_queue{database_dsn} = "sqlite:$webwork_dir/DATA/webwork2_job_queue.db"; ################################################################################ @@ -371,23 +348,9 @@ $job_queue{database_dsn} = "sqlite:$webwork_dir/DATA/webwork2_job_queue.db"; # # perl -MDateTime::TimeZone -e 'print join "\n", DateTime::TimeZone::links' # -# If left blank, the system timezone will be used. This is usually what you -# want. You might want to set this if your server is NOT in the same timezone as -# your school. If just a few courses are in a different timezone, set this in -# course.conf for the affected courses instead. -# +# This can be set per course either in course.conf or via the course configuration. $siteDefaults{timezone} = "America/New_York"; -# Locale for time format localization -# Set the following variable to localize the format of things like days -# of the week and month names (i.e. translate them) -# This variable must match one of the locales available on your system -# To show the current locale in use on the system, type 'locale' at the -# command prompt. For a list of installed locales, type 'locale -a' and -# enter one of the listed values here. -# If you do not fill this in, the system will default to "en_US" -$siteDefaults{locale}=""; - ################################################################################ # Search Engine Indexing Enable/Disable ################################################################################ diff --git a/conf/webwork2.mojolicious.dist.yml b/conf/webwork2.mojolicious.dist.yml index 557d099e19..3e77a25f8a 100644 --- a/conf/webwork2.mojolicious.dist.yml +++ b/conf/webwork2.mojolicious.dist.yml @@ -1,4 +1,12 @@ --- +# Make sure to change this to your own secret. Any long random string of +# characters will work. Note that you can add new secrets to this list, and it +# is recommended that you do so once in a while. Only add to the beginning of +# the list (and move the old secrets down). The first secret is the only one +# that will be used for signing new cookies, but the old secrets will be used +# for validating signatures on existing cookies. Eventually the old secrets +# should be removed (roughly after the length of time set for $sessionTimeout +# in localOverrides.conf or defaults.config). secrets: - 607280d0b2c621220b554a1c6ed123aa1a96f2de @@ -78,6 +86,32 @@ server_group: www-data # used when serving the webwork2 app directly. redirect_http_to_https: 0 +# Change enable_certbot_webroot_routes to 1 to enable routes in the webwork2 app +# used by certbot for certificate renewal with the webroot option. Note that +# this should only be used when serving the webwork2 app directly. You will also +# need to add "- http://*:80" as well as "- http://*:443" to the hypnotoad +# listen values below for this to work. +# Then execute +# sudo certbot certonly --webroot -w /opt/webwork/webwork2/tmp \ +# -d your.domain.edu \ +# --post-hook "chown -R www-data:www-data /etc/letsencrypt && systemctl reload webwork2" +# to renew certificates without needing to stop the webwork2 app. That command +# will renew the certificate for the first time, and also set up autorenewal in +# the future. Obviously your.domain.edu needs to be changed to your actual +# domain name. Note that /opt/webwork/webwork2/tmp is the default value of +# $webworkDirs{tmp}. If you customize $webworkDirs{tmp} in localOverrides.conf, +# then you will need to use what you have that variable set to instead. Be +# careful since the default value of $webworkDirs{tmp} depends on the value of +# $webworkDirs{root} (which is /opt/webwork/webwork2 by default). So if you +# customize $webworkDirs{root}, then you will need to adjust the path +# accordingly. Also, change www-data:www-data in the command to be +# server_user:server_group where server_user and server_group are the values of +# those settings above. The post hook in the command will run every time that +# certificates are automatically renewed, and will fix permissions on the new +# certificates so that the webwork2 app can read them, and will hot reload the +# webwork2 app to load the new certificates (with zero downtime). +enable_certbot_webroot_routes: 0 + # hypnotoad server configuration # See https://docs.mojolicious.org/Mojo/Server/Daemon # Any of the attributes listed there can be set in this section. @@ -206,3 +240,10 @@ debug: hardcopy: # If 1, don't delete temporary files created when a hardcopy is generated. preserve_temp_files: 0 + +# Set this to 1 to allow the html2xml and render_rpc endpoints to disable +# cookies and thus skip two factor authentication. This should never be enabled +# for a typical webwork server. This should only be enabled if you want to +# allow serving content via these endpoints to links in external websites with +# usernames and passwords embedded in them such as for PreTeXt textbooks. +allow_unsecured_rpc: 0 diff --git a/courses.dist/modelCourse/course.conf b/courses.dist/modelCourse/course.conf index fb80c242a5..54391c557f 100644 --- a/courses.dist/modelCourse/course.conf +++ b/courses.dist/modelCourse/course.conf @@ -2,20 +2,11 @@ # This file is used to override the global WeBWorK course environment for this course. -# Database Layout (global value typically defined in global.conf) -# Several database are defined in the file conf/database.conf and stored in the -# hash %dbLayouts. -# The database layout is always set here, since one should be able to change the -# default value in global.conf without disrupting existing courses. -# global.conf values: -# $dbLayoutName = 'sql_single'; -# *dbLayout = $dbLayouts{$dbLayoutName}; -$dbLayoutName = 'sql_single'; -*dbLayout = $dbLayouts{$dbLayoutName}; - -# Users for whom to label problems with the PG file name +# Users for whom to label problems with the PG file name (global value typically "professor") # For users in this list, PG will display the source file name when rendering a problem. -#$pg{specialPGEnvironmentVars}{PRINT_FILE_NAMES_FOR} = ['user_id1']; +# defaults.config values: +# $pg{specialPGEnvironmentVars}{PRINT_FILE_NAMES_FOR} = ['professor']; +$pg{specialPGEnvironmentVars}{PRINT_FILE_NAMES_FOR} = ['admin']; # The following hashes control which users are allowed to see students from which # sections. This is typically used for large multi-section classes with many students, ta's and @@ -35,5 +26,3 @@ $dbLayoutName = 'sql_single'; # user_id1 => [1, 2, 3], # list of viewable recitations for user_id1 # user_id2 => [1], #}; - -1; diff --git a/courses.dist/modelCourse/html/achievements/Fractal-Cornucopia.svg b/courses.dist/modelCourse/html/achievements/Fractal-Cornucopia.svg new file mode 100644 index 0000000000..d517ecf916 --- /dev/null +++ b/courses.dist/modelCourse/html/achievements/Fractal-Cornucopia.svg @@ -0,0 +1,11551 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/courses.dist/modelCourse/html/achievements/chipping_away.png b/courses.dist/modelCourse/html/achievements/chipping_away.png index 6ffadc7b5c..cfdd040c6c 100644 Binary files a/courses.dist/modelCourse/html/achievements/chipping_away.png and b/courses.dist/modelCourse/html/achievements/chipping_away.png differ diff --git a/courses.dist/modelCourse/html/achievements/complete_100_problems.png b/courses.dist/modelCourse/html/achievements/complete_100_problems.png index 12d3ec390b..fc7926bca9 100644 Binary files a/courses.dist/modelCourse/html/achievements/complete_100_problems.png and b/courses.dist/modelCourse/html/achievements/complete_100_problems.png differ diff --git a/courses.dist/modelCourse/html/achievements/complete_10_problems.png b/courses.dist/modelCourse/html/achievements/complete_10_problems.png index 9ea2f4da3e..6bf73f552b 100644 Binary files a/courses.dist/modelCourse/html/achievements/complete_10_problems.png and b/courses.dist/modelCourse/html/achievements/complete_10_problems.png differ diff --git a/courses.dist/modelCourse/html/achievements/complete_150_problems.png b/courses.dist/modelCourse/html/achievements/complete_150_problems.png index eb29ce22fe..554addb60a 100644 Binary files a/courses.dist/modelCourse/html/achievements/complete_150_problems.png and b/courses.dist/modelCourse/html/achievements/complete_150_problems.png differ diff --git a/courses.dist/modelCourse/html/achievements/complete_25_problems.png b/courses.dist/modelCourse/html/achievements/complete_25_problems.png index 7500c99f70..6db76378bb 100644 Binary files a/courses.dist/modelCourse/html/achievements/complete_25_problems.png and b/courses.dist/modelCourse/html/achievements/complete_25_problems.png differ diff --git a/courses.dist/modelCourse/html/achievements/complete_50_problems.png b/courses.dist/modelCourse/html/achievements/complete_50_problems.png index 4457a77bfd..0937c5ef81 100644 Binary files a/courses.dist/modelCourse/html/achievements/complete_50_problems.png and b/courses.dist/modelCourse/html/achievements/complete_50_problems.png differ diff --git a/courses.dist/modelCourse/html/achievements/complete_one_problem.png b/courses.dist/modelCourse/html/achievements/complete_one_problem.png index d5adff139f..9ffab1b887 100644 Binary files a/courses.dist/modelCourse/html/achievements/complete_one_problem.png and b/courses.dist/modelCourse/html/achievements/complete_one_problem.png differ diff --git a/courses.dist/modelCourse/html/achievements/crack_o_dawn.png b/courses.dist/modelCourse/html/achievements/crack_o_dawn.png index fa72be7bb8..62557a8ecd 100644 Binary files a/courses.dist/modelCourse/html/achievements/crack_o_dawn.png and b/courses.dist/modelCourse/html/achievements/crack_o_dawn.png differ diff --git a/courses.dist/modelCourse/html/achievements/last_minute.png b/courses.dist/modelCourse/html/achievements/last_minute.png index 505604d5ba..6bdc01449d 100644 Binary files a/courses.dist/modelCourse/html/achievements/last_minute.png and b/courses.dist/modelCourse/html/achievements/last_minute.png differ diff --git a/courses.dist/modelCourse/html/achievements/level_eight.png b/courses.dist/modelCourse/html/achievements/level_eight.png index b42683d69d..c5e793ffca 100644 Binary files a/courses.dist/modelCourse/html/achievements/level_eight.png and b/courses.dist/modelCourse/html/achievements/level_eight.png differ diff --git a/courses.dist/modelCourse/html/achievements/level_five.png b/courses.dist/modelCourse/html/achievements/level_five.png index 6c18cc9713..598adc678d 100644 Binary files a/courses.dist/modelCourse/html/achievements/level_five.png and b/courses.dist/modelCourse/html/achievements/level_five.png differ diff --git a/courses.dist/modelCourse/html/achievements/level_four.png b/courses.dist/modelCourse/html/achievements/level_four.png index 6590f4a4dd..e027fabb23 100644 Binary files a/courses.dist/modelCourse/html/achievements/level_four.png and b/courses.dist/modelCourse/html/achievements/level_four.png differ diff --git a/courses.dist/modelCourse/html/achievements/level_one.png b/courses.dist/modelCourse/html/achievements/level_one.png index 5cc62e863d..552010f472 100644 Binary files a/courses.dist/modelCourse/html/achievements/level_one.png and b/courses.dist/modelCourse/html/achievements/level_one.png differ diff --git a/courses.dist/modelCourse/html/achievements/level_seven.png b/courses.dist/modelCourse/html/achievements/level_seven.png index 937337cd51..3f9b1d9cd2 100644 Binary files a/courses.dist/modelCourse/html/achievements/level_seven.png and b/courses.dist/modelCourse/html/achievements/level_seven.png differ diff --git a/courses.dist/modelCourse/html/achievements/level_two.png b/courses.dist/modelCourse/html/achievements/level_two.png index 8f38e89884..b86afcc186 100644 Binary files a/courses.dist/modelCourse/html/achievements/level_two.png and b/courses.dist/modelCourse/html/achievements/level_two.png differ diff --git a/courses.dist/modelCourse/html/achievements/on_fire.png b/courses.dist/modelCourse/html/achievements/on_fire.png index 05cc600513..e66b33af9c 100644 Binary files a/courses.dist/modelCourse/html/achievements/on_fire.png and b/courses.dist/modelCourse/html/achievements/on_fire.png differ diff --git a/courses.dist/modelCourse/html/achievements/on_one_hand.png b/courses.dist/modelCourse/html/achievements/on_one_hand.png index 22fa5e82ca..6b034c3846 100644 Binary files a/courses.dist/modelCourse/html/achievements/on_one_hand.png and b/courses.dist/modelCourse/html/achievements/on_one_hand.png differ diff --git a/courses.dist/modelCourse/html/achievements/one_click.png b/courses.dist/modelCourse/html/achievements/one_click.png index e239cc84fe..dc0e8e7626 100644 Binary files a/courses.dist/modelCourse/html/achievements/one_click.png and b/courses.dist/modelCourse/html/achievements/one_click.png differ diff --git a/courses.dist/modelCourse/html/achievements/pattern_recognition.png b/courses.dist/modelCourse/html/achievements/pattern_recognition.png index 89c6e3af64..fde237c45a 100644 Binary files a/courses.dist/modelCourse/html/achievements/pattern_recognition.png and b/courses.dist/modelCourse/html/achievements/pattern_recognition.png differ diff --git a/courses.dist/modelCourse/html/achievements/persistance.png b/courses.dist/modelCourse/html/achievements/persistance.png index 16f3ea2076..b6a2938bbb 100644 Binary files a/courses.dist/modelCourse/html/achievements/persistance.png and b/courses.dist/modelCourse/html/achievements/persistance.png differ diff --git a/courses.dist/modelCourse/html/achievements/reaching_a_limit.png b/courses.dist/modelCourse/html/achievements/reaching_a_limit.png index e5f74fddfb..152b817c9a 100644 Binary files a/courses.dist/modelCourse/html/achievements/reaching_a_limit.png and b/courses.dist/modelCourse/html/achievements/reaching_a_limit.png differ diff --git a/courses.dist/modelCourse/html/achievements/speed_mather.png b/courses.dist/modelCourse/html/achievements/speed_mather.png index cc2806f499..d882f45de9 100644 Binary files a/courses.dist/modelCourse/html/achievements/speed_mather.png and b/courses.dist/modelCourse/html/achievements/speed_mather.png differ diff --git a/courses.dist/modelCourse/html/achievements/super_persistance.png b/courses.dist/modelCourse/html/achievements/super_persistance.png index f37d80896e..c238015aa8 100644 Binary files a/courses.dist/modelCourse/html/achievements/super_persistance.png and b/courses.dist/modelCourse/html/achievements/super_persistance.png differ diff --git a/courses.dist/modelCourse/html/achievements/super_speed_math.png b/courses.dist/modelCourse/html/achievements/super_speed_math.png index 3c4942580b..02296da682 100644 Binary files a/courses.dist/modelCourse/html/achievements/super_speed_math.png and b/courses.dist/modelCourse/html/achievements/super_speed_math.png differ diff --git a/courses.dist/modelCourse/html/achievements/the_lhopital.png b/courses.dist/modelCourse/html/achievements/the_lhopital.png index ce67b858d6..d27873c276 100644 Binary files a/courses.dist/modelCourse/html/achievements/the_lhopital.png and b/courses.dist/modelCourse/html/achievements/the_lhopital.png differ diff --git a/courses.dist/modelCourse/html/achievements/three_in_a_row.png b/courses.dist/modelCourse/html/achievements/three_in_a_row.png index ef315760c6..8043f11727 100644 Binary files a/courses.dist/modelCourse/html/achievements/three_in_a_row.png and b/courses.dist/modelCourse/html/achievements/three_in_a_row.png differ diff --git a/courses.dist/modelCourse/html/achievements/to_infinity.png b/courses.dist/modelCourse/html/achievements/to_infinity.png index fb4b700567..5b036ae083 100644 Binary files a/courses.dist/modelCourse/html/achievements/to_infinity.png and b/courses.dist/modelCourse/html/achievements/to_infinity.png differ diff --git a/courses.dist/modelCourse/templates/PGMLLab/PGML-lab.pg b/courses.dist/modelCourse/templates/PGMLLab/PGML-lab.pg deleted file mode 100644 index 7e20ce46ed..0000000000 --- a/courses.dist/modelCourse/templates/PGMLLab/PGML-lab.pg +++ /dev/null @@ -1,716 +0,0 @@ -DOCUMENT(); - -loadMacros('PGstandard.pl', 'PGML.pl', 'parserMultiAnswer.pl', 'PGcourse.pl'); - -# Hide the score summary and show past answers and email instructor buttons. -HEADER_TEXT(MODES( - HTML => tag('style', '.problemFooter, #score_summary {display: none}'), - TeX => '' -)); - -sub EscapeHTML { - my $s = shift; - $s =~ s/&/~~&/g; - $s =~ s//~~>/g; - $s =~ s/"/~~"/g; - return $s; -} - -# Make a reference menu -sub Menu { - return tag( - 'select', - aria_labelledby => 'reference-label', - style => 'width:15em', - join('', map { tag('option', $_) } @_) - ); -} - -# Make an example menu -sub Examples { - my ($title, @examples) = @_; - - return tag( - 'select', - class => 'example-selector', - id => $title, - aria_labelledby => 'examples-label', - style => 'width:15em', - tag('option', value => '', $title) . join( - '', - map { - tag( - 'option', - value => $_->[0], - data_vars => EscapeHTML($_->[1][0]), - data_pgml => EscapeHTML($_->[1][1]), - $_->[0] - ) - } @examples - ) - ); -} - -TEXT(MODES( - HTML => tag('div', style => 'text-align:center', tag('b', 'Interactive PGML Lab:')), - TeX => $BCENTER . $BBOLD . 'Interactive PGML Lab:' . $EBOLD . $ECENTER -)); - -$vars = $inputs_ref->{vars} // ''; -$pgml = ($inputs_ref->{pgml} // '') =~ s/~~r?~~n/~~n/gr; -$result = ''; - -if ($vars ne '') { - ($vresult, $verror) = PG_restricted_eval($vars); - if ($verror) { - $verror =~ s/ at ~~(eval ~~d+~~) line ~~d+(, at EOF)?//; - $verror = EscapeHTML($verror); - $verror =~ s/~~n/
/g; - $verror = tag('span', style => 'color:#c00', 'Error processing variables: ' . tag('i', $verror)); - } -} else { - $vars = ''; -} - -if ($pgml ne '') { - $PGML::warningsFatal = 1; - ($result, $error) = PG_restricted_eval('PGML::Format($pgml)'); - if ($error) { - $result = $error; - $result =~ s/ at ~~(eval ~~d+~~) line ~~d+//; - $result = EscapeHTML($result); - $result =~ s/~~n/
/g; - $result = tag('span', style => 'color:#c00', $result); - } - warn join('', @PGML::warnings) . "~~n" if scalar(@PGML::warnings); - if ($inputs_ref->{showTeX}) { - $oldDisplay = $displayMode; - $displayMode = 'TeX'; - - # The variables need to be processed again before processing the problem. This redefines all of the variables - # as new objects. If this is not done, then errors occur for many of the examples with MathObjects because the - # the problem has already been processed above. Ignore the errors this time. Those have already been caught - # above. - PG_restricted_eval($vars) if $vars ne ''; - - ($tex, $error) = PG_restricted_eval('PGML::Format($pgml)'); - if ($error) { - $result = $error; - $result =~ s/ at ~~(eval ~~d+~~) line ~~d+//; - $result = EscapeHTML($result); - $result =~ s/~~n/
/g; - $result = tag('span', style => 'color:#c00', 'TeX Error: ' . tag('i', $result)); - } - $displayMode = $oldDisplay; - } - $pgml = EscapeHTML($pgml); -} else { - $pgml = ''; -} - -$prows = scalar(split(/~~n/, $pgml)); -$prows = 8 unless $prows >= 8; -$vrows = scalar(split(/~~n/, $vars)); -$vrows = 2 unless $vrows >= 2; - -RECORD_FORM_LABEL('vars'); -RECORD_FORM_LABEL('pgml'); -RECORD_FORM_LABEL('showHTML'); -RECORD_FORM_LABEL('showTeX'); - -TEXT(MODES(HTML => '
', TeX => '')); -TEXT($HR . $verror . $HR) if $verror; -TEXT(MODES( - HTML => tag( - 'div', - style => 'margin:1rem auto;width:fit-content;padding:1rem;' - . 'border:1px solid black;border-radius:4px;background-color:#e8e8e8;', - $result - ), - TeX => $result -)) - if defined $result && $result ne ''; - -if ($inputs_ref->{showHTML}) { - $result = EscapeHTML($result); - $result =~ s!~~n!
!g; - TEXT(tag('hr') . tag('small', tag('pre', $result)) . tag('hr')); -} -if ($inputs_ref->{showTeX}) { - $tex = EscapeHTML($tex); - $tex =~ s!~~n!
!g; - TEXT(tag('hr') . tag('small', tag('pre', $tex)) . tag('hr')); -} -TEXT(MODES(HTML => '
', TeX => '')); - -TEXT(MODES( - HTML => tag( - 'div', - style => 'width:fit-content;max-width:100%;text-align:left;margin:auto', - tag('label', for => 'vars', tag('small', tag('i', style => 'color:#555', 'Variable definitions:'))) - . tag('textarea', name => 'vars', id => 'vars', rows => $vrows, cols => 60, style => 'display:block', $vars) - . tag('label', for => 'pgml', tag('small', tag('i', style => 'color:#555', 'Text of problem:'))) - . tag('textarea', name => 'pgml', id => 'pgml', rows => $prows, cols => 60, style => 'display:block', $pgml) - . tag( - 'div', - style => 'margin-top:0.25rem;display:flex;justify-content:space-between;align-items:center', - tag( - 'div', - tag( - 'div', - tag( - 'label', - tag( - 'input', - type => 'checkbox', - name => 'showHTML', - value => 1, - $inputs_ref->{showHTML} ? (checked => undef) : () - ) - . ' Show HTML code ' - ) - ) - . tag( - 'div', - tag( - 'label', - tag( - 'input', - type => 'checkbox', - name => 'showTeX', - value => 1, - $inputs_ref->{showTeX} ? (checked => undef) : () - ) - . ' Show TeX code' - ) - ) - ) - . tag('div', tag('input', type => 'submit', name => 'action', value => 'Process this Text')) - ) - ), - TeX => "Variable definitions:$BR" - . qq!\hbox to .8\hsize{\hrulefill}$BR! - . "Text of problem:$BR" - . qq!\hbox to .8\hsize{\hrulefill}$BR$SPACE! - . "Show HTML code$BR$SPACE" - . "Show TeX code$BR" - . '[Process this Text]' -)); - -if ($displayMode ne 'TeX') { - $SP = "␣"; - TEXT(tag('script', << 'END_SCRIPT')); -window.addEventListener('DOMContentLoaded', () => { - const unescapeHTML = (html) => { - return html - .replace(/>/g, '>') - .replace(/</g, '<') - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/\n/g, '~~n') - .replace(/\\/g, '\'); - }; - for (const select of document.querySelectorAll('.example-selector')) { - select.addEventListener('change', () => { - if (select.value === '') return; - const selectedExample = select.options[select.selectedIndex]; - const dataType = { vars: 2, pgml: 8 }; - for (const id in dataType) { - const el = document.getElementById(id); - el.value = unescapeHTML(selectedExample.dataset[id]); - el.rows = Math.max(dataType[id], selectedExample.dataset[id].split(/\n/).length); - } - document.getElementById('resultsBox').style.display = 'none'; - window.scrollTo(0, 0); - }); - } -}); -END_SCRIPT - - TEXT(tag( - 'div', - style => 'margin:1rem auto 0;width:fit-content;' - . 'display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:1rem;', - tag( - 'fieldset', - style => 'border:1px solid #5555;border-radius:4px;padding:1rem;' - . 'display:flex;flex-direction:column;gap:0.25rem', - join( - '', - tag('legend', id => 'examples-label', style => 'font-size:20px', 'Examples:'), - Examples( - 'Math', - [ - 'TeX math' => [ - '', - 'In-line math: [`\\frac{x+1}{x-1}`], display math: [``\\frac{x+1}{x-1}``]' - . "\n\n" - . ' [``\\frac{x+1}{x-1}``] (indented)' - . "\n\n" - . '>>[``\\frac{x+1}{x-1}``] (centered)<<' - ] - ], - [ - 'Parsed math' => [ - '', - 'In-line math: [:(x+1)/(x-1):], display math: [::(x+1)/(x-1)::]' - . "\n\n" - . ' [::(x+1)/(x-1)::] (indented)' - . "\n\n" - . '>>[::(x+1)/(x-1)::] (centered)<<' - ] - ], - [ - 'Specify context' => [ - '$context = Context("Vector");', - 'Use vector context: [:<1,2x>:]{"Vector"} ' - . "\n" - . 'Use context object: [:<1,2x>:]{$context} ' - . "\n" - . 'Use current context: [:<1,2x>:]*' - ] - ], - ), - Examples( - 'Answers', - [ Numeric => [ '', 'The number twelve is [_______]{12}' ] ], - [ Formula => [ '', 'The formula is [__________]{"1+x"}' ] ], - [ - 'From variable' => [ - '$f=Formula("1+x^2"); $Df = $f->D;', - q!Suppose [`f(x) = [$f]`]. Then [`f'(x) =`] [____________]{$Df}! - ] - ], - [ MathObject => [ '', 'Twelve is [______]{Real(12)}' ] ], - [ 'MathObject 2' => [ '', '2 mod 10 is [______]{Real(2)->with(period=>10)}' ] ], - [ - 'MathObject 3' => - [ '$f = Formula("sqrt(x^2-1)")->with(limits=>[1,2]);', 'The answer is: [_____]{$f}' ] - ], - [ Traditional => [ '', 'Twelve is [______]{num_cmp(12)}' ] ], - [ - 'Checker Options' => - [ '', '[::Int(x,2x)::] = [________]{Formula("x^2")->cmp(upToConstant=>1)} [`+C`]' ] - ], - [ - 'Checker Options 1' => [ - '$cmp = Formula("x^2")->cmp(upToConstant=>1);', - '[::Int(x,2x)::] = [________]{$cmp} [`+C`]' - ] - ], - [ 'Answer Array' => [ '$M = Matrix([1,2],[3,4])', '[`[$M] =`] [___]*{$M}' ] ], - [ - 'MultiAnswer' => [ - '$mp = MultiAnswer(12,6)->with(checker=>sub {1}, singleResult=>1)', - '[_____]{$mp} and [_____]{$mp}' - ] - ], - [ 'Option Form' => [ '', 'The number 12 is [____]{answer=>12,width=>10}' ] ], - [ - 'External ANS' => [ - 'Context("Vector"); ANS(Vector(1,2,3)->cmp(showCoodinateHints=>0));', - "[:<1,2,3>:]* = [__________]" - ] - ], - ), - Examples( - 'Lists', - [ - Numeric => [ - '', - "Here is a list:\n" - . "1. This is the first list item\n" - . " continued on the next line.\n" - . "2. Additional items are easy to add.\n" - . "3. Continuation need not be indented,\n" - . "such as this line.\n\n" - . "A paragraph break ends the list...\n" - . "1. Unless you indent the paragraph...\n\n" - . " ...in which case it is part of the list item.\n" - . "2. See?" - ] - ], - [ - Alphabetic => [ - '', - "A list with alphabetic markers:\n" - . "a) You can use dots\n" - . "b) or parens to indicate the items\n\n" - . "A paragraph break ends the list.\n\n" - . "A list with roman numeral markers:\n" - . "i. Item 1\n" - . "ii. Item 2 \n" - . "Ending with three spaces also ends the list" - ] - ], - [ - 'Bullet lists' => [ - '', - "A list can be with stars:\n" - . "* Item 1\n" - . "* Item 1\n\n" - . "Or with plus or minus:\n" - . "+ Item 1\n" - . "+ Item 2\n\n" - . "Paragraphs can be used between items:\n\n" - . "o Item 1\n\n" - . "o Item 2\n\n" - . "End of lists." - ] - ], - [ - 'Sub-lists' => [ - '', - "1. A list\n" - . " - with a sub-list\n" - . " - of three items\n" - . " - (indent the sub list)\n" - . "2. Back to the main list\n" - ] - ], - ), - Examples( - 'Substitutions', - [ - Variables => [ - '$a = 1; $f = Formula("(x+1)/(x-1)");', - 'a = [$a], f = [$f]. In math: [`f = [$f]`] (TeX inserted automatically),' . "\n" - . 'parsed: [:f = [$f]:] (string inserted automatically).' - ] - ], - [ Commands => [ 'sub F {return (shift)+1}; $x = 5;', 'Add one to five: [@ F($x) @]' ] ], - [ - Comments => [ - '', - "This [% text %] is removed. \n" - . "So are these [% partial [@ and incomplete %] commands. \n" - . "Comments can be nested: [% one [% and two %] and three %]\n" - ] - ], - [ - 'No Escape' => [ - '$x = "has math: [:x+1:] and ${BBOLD}bold${EBOLD}";', - "Contents of substitutions will be escaped, unless\n" - . "followed by a star: \n" - . 'Escaped: [$BSMALL] not small [$ESMALL] ' . "\n" - . 'Verbatim: [$BSMALL]* small [$ESMALL]*' - . "\n\n" - . "Two stars forces the contents to be processed further: \n" - . 'Escaped: [$x] ' . "\n" - . 'Verbatim: [$x]* ' . "\n" - . 'Processed: [$x]**' . "\n" - ] - ], - ), - Examples( - 'Formatting', - [ - 'Line breaks' => [ - '', - "Force line break by \n" - . "ending a line with two spaces\n\n" - . "## even in a header ## \n" - . "## that runs over two lines ##\n" - ] - ], - [ - 'Par break' => [ - '', - 'A blank line is a paragraph break\n \nEven if it just contains white space\n' - ] - ], - [ - Indentation => [ - '', - "Indent a section by using four spaces or a tab\n" - . " This is indented,\n" - . " and continues on a second line.\n" - . " Another four spaces indents again.\n" - . " Go back to four to end the inner indenting.\n" - . "Note, however, that you only need to indent\n" - . "the first line of a paragraph to have all of it\n" - . "be indented. (That may need to be changed.)\n\n" - . "End the paragraph to go back to no indenting\n" - . " or use _three_ spaces to end the line \n" - . "and that will end the indenting" - ] - ], - [ - Centering => [ - '', - "Use angle brackets to center a phrase:\n\n" - . ">> This is centered <<\n\n" - . "You can center several lines as a paragraph:\n" - . ">> These lines will <<\n" - . ">> be combined <<\n\n" - . "Or you can force line breaks with two spaced at the end:\n" - . ">> These lines will << \n" - . ">> be centered separately <<\n\n" - . "A whole paragraph can be centered:\n" - . ">> This is a paragraph\n" - . "that will be centered <<\n" - ] - ], - [ - 'Right justify' => [ - '', - "Use right angle brackets to force a line or paragraph\n" - . "to be right-justified:\n" - . ">> At the right\n\n" - . ">> Several lines combined\n" - . ">> right justfied\n\n" - . ">> Or a whole paragaph\n" - . "that is pushed to the right\n\n" - . ">> Or two lines \n" - . ">> justified separately." - ] - ], - [ - Headings => [ - '', - "# Heading size 1 #\n" - . "## Heading size 2 ##\n" - . "### Heading size 3 ###\n" - . "#### Heading size 4 ####\n" - . "##### Heading size 5 #####\n" - . "###### Heading size 6 ######\n\n" - . "### Two separate lines ###\n" - . "### are combined ###\n\n" - . "### A whole paragraph\n" - . "can be a heading ###\n\n" - . "### End with two spaces ### \n" - . "### for two lines separately ###\n\n" - . "### The trailing hashes are optional.\n\n" - . ">> ## centered heading ## <<\n" - . ">> ## right-justified ##" - ] - ], - [ - Rules => [ - '', - "Three or more dashes or equals on a line by itself forms a rule\n\n" - . "-----\n" - . "You can specify the width and size if you want:\n" - . "----{200}\n----{'50%'}\n===={200}{5}\n===={size=>5}\n\n" - . "You can center and right-justify rules:\n>> ----{100} <<\n>> ----{100}\n" - ] - ], - [ - Emphasis => [ - '', - "These words are in *bold* or _italic_.\n\n" - . "Stars can be used in*side* a word,\n" - . "but underlines_don't_work_that_way." - ] - ], - [ - 'Smart Quotes' => [ - '', - "Quotes are ~~"smart~~" (~~"even here~~"), and don't forget about 'other' quotes.\n\n" - . "You can quote a quote: ~~\\"dumb quotes~~\\"." - ] - ], - [ - Preformatted => [ - '', - "Preformatted text starts with a colon and three spaces:\n" - . ": This is preformatted,\n" - . ": and can include any text, e.g., <, >, ~~$, etc.,\n" - . ": but [@ ~~"commands~~" @] and other *mark up* are performed normally.\n" - . ": Use [|verbatim mode|] if you want to include commands literally,\n" - . ": or use a slash to escape them: \\[~~$x].\n\n" - . "The formatting can be indented, too:\n" - . " Here is some indenting\n" - . " : with preformatting\n" - . " : on several lines.\n" - . " Now back to normal, but indented.\n" - ] - ], - [ - Verbatim => [ - '', - "Text that includes commands can be enclosed\n" - . "to prevent interpretation: \n" - . "[|This is not math [`x+1`] in here.|]\n\n" - . "You can use more vertical bars to make verbatim verbatims: \n" - . "[||This is [|verbatim|].||]\n\n" - . "Use backslashes to escape command characters if you need to: \n" - . "This occurred in the year\n" - . "1\\. (Prevent accidental list).\n\n" - . "Don't do comment: \\[% you will see this %]." - ] - ], - [ - 'Other Chars' => - [ '', 'Other characters quote themselves on their own: <, >, &, %, $, ^, etc.' ] - ], - ), - Examples( - 'Problems', - [ - Algebra => [ - 'Context("Interval"); $a = random(1,8,1); $b = random(8,15,1); $min = $a-$b; $max = $a+$b;', - "Solve the following inequality and enter your answer using interval notation:\n\n" - . ' [``|x-[$a]| > [$b]``]' - . "\n\n" - . 'Answer: [`x`] must be in [____________________________]{"(-inf,$min)U($max,inf)"}' - ] - ], - [ - Composition => [ - '$b=non_zero_random(-3,1,1)+1; # b=1 makes answers equal' . "\n" - . '$f = Formula("x+$b"); $g = Formula("(x-2)^2");' . "\n" - . '$F = "$f for x in [-1,5] using color:blue and weight:2";' . "\n" - . '$G = "$g for x in [0,4] using color:red and weight:2";' - . "\n\n" - . 'loadMacros("PGgraphmacros.pl");' . "\n" - . '$graph = init_graph(-2,-4,6,8,axes=>[0,0],grid=>[8,12],size=>[200,200]);' . "\n" - . 'plot_functions($graph,$F,$G);' . "\n" - . '$lf = new Label (5.3,$f->eval(x=>5)+.3,"f","blue","left","bottom");' . "\n" - . '$lg = new Label (.3,$g->eval(x=>0)+.3,"g","red","left","bottom");' . "\n" - . '$graph->lb($lf,$lg);', - "Let [`f`] be the linear function (in blue) and let [`g`] be the\n" - . "parabolic function (in red) below.\n\n" - . ' [@ image(insertGraph($graph),' . "\n" - . ' width=>200,height=>200,tex_size=>480) @]*' - . "\n\n" - . ' 1. [:(f o g)(2):] = [____]{$b}' . "\n" - . ' 2. [:(g o f)(2):] = [____]{$b**2}' . "\n" - . ' 3. [:(f o f)(2):] = [____]{2+2*$b}' . "\n" - . ' 4. [:(g o g)(2):] = [____]{4}' - . "\n\n" - ] - ], - [ - Derivative => [ - '$aa = random(3,8,1);' . "\n" - . '$f = Formula("atan(sqrt(${aa}x^2-1))");' . "\n" - . '$Df = $f->D->with(limits=>[1/sqrt($aa),1]);', - q!Let [`f(x) = [$f]`]. Find [`f'(x)`].! - . "\n\n" - . q![`f'(x)`] = [____________________________________]{$Df}! - ] - ], - [ - Logarithm => [ - '$a = random(3,5,1); $b = random(2,20,1); $c = random(2,20,1);', - 'Use the laws of logarithms to rewrite the expression' - . "\n\n" - . ' [::ln(root [$a] of xy)::]' - . "\n\n" - . 'in a form that does not contain any logarithm of a product,' . "\n" - . 'quotient or power.' - . "\n\n" - . 'After rewriting, we have' - . "\n\n" - . ' [::ln(root [$a] of xy) = A ln x + B ln y::]' - . "\n\n" - . 'with constants' - . "\n\n" - . ' [`A`] = [_______________]{1/$a} and ' . "\n" - . ' [`B`] = [_______________]{1/$b}.' - . "\n\n" - ] - ], - [ - Optimization => [ - '$a = random(200, 320, 10); $b = random(3, 6, 1); $c = random(12, 16, 1);' - . "\n\n" - . '$length = sqrt($a*($b+$c)/(2*$b)); $width = sqrt(2*$b*$a/($b+$c));' - . "\n\n" - . '$mp = MultiAnswer(Real($length), Real($width))->with(' . "\n" - . ' singleResult => 1, separator => " x ", tex_separator => "\\\\times",' - . "\n" - . ' checker => sub {' . "\n" - . ' my ($correct, $student) = @_;' . "\n" - . ' my ($a,$b) = @$correct; my ($A,$B) = @$student;' . "\n" - . ' return ($a == $A && $b == $B) || ($a == $B && $b == $A);' . "\n" . ' }' - . "\n" . ');', - 'A fence is to be built to enclose a rectangular area of [$a] square' . "\n" - . 'feet. The fence along three sides is to be made of material that' . "\n" - . 'costs [$b] dollars per foot, and the material for the fourth side' . "\n" - . 'costs [$c] dollars per foot. Find the dimensions of the enclosure' . "\n" - . 'that is most economical to construct.' - . "\n\n" - . 'Dimensions: [___________]{$mp} x [___________]{$mp} feet' - ] - ], - ) - ) - ) - . tag( - 'fieldset', - style => 'border:1px solid #5555;border-radius:4px;padding:1rem;' - . 'display:flex;flex-direction:column;gap:0.25rem', - join( - '', - tag('legend', id => 'reference-label', style => 'font-size:20px', 'For reference only:'), - Menu( - '- Math -', '[`tex`]', - '[``display-tex``]', '[:parsed-math:]', - '[::parsed-display-math::]', '[:parsed-math:]{context}', - '[:parsed-math:]* (uses current context)', - ), - Menu( - '- Answers -', '[______] (# of _ is width)', - '[___]{answer}', '[___]{answer}{width}', - '[___]{answer}{width}{name}', '[___]{answer=>...,width=>...,name=>...}', - '[___]* (ans_array not ans_rule)', - ), - Menu( - '- Lists -', - '1. (numeric list)', - 'a. (alpha list)', - 'A. (capital alphas)', - 'i. (roman numerals)', - 'I. (capital roman)', - '* (bullet list)', - '- (bullet list)', - '+ (square bullets)', - 'o (circle bullets)', - ), - Menu( - '- Substitutions -', - '[$variable]', - '[$variable]* (no escaping)', - '[$variable]** (parse results)', - '[@ perl-command @]', - '[@ perl-command @]* (no escaping)', - '[@ perl-command @]** (parse results)', - '[% comment %]', - '[<url>] (not implemented)', - '[!image!]{source}{width}{height}', - ), - Menu( - '- Formatting -', - "$SP$SP\n (line break)", - "$SP$SP$SP\n (format break)", - 'blankline (par break)', - "$SP$SP$SP$SP or \t (indent)", - '>> ... << (center)', - '>> ... (right justify)', - '--- (hrule)', - '---{width}', - '---{width}{size}', - '*bold*', - '_italic_', - '*_bold-italic_*', - ":$SP$SP$SP (preformatted)", - '[|verbatim|]', - ), - Menu( - '- Headings -', - '# heading 1 #', - '## heading 2 ##', - '### heading 3 ###', - '#### heading 4 ####', - '##### heading 5 #####', - '###### heading 6 ######', - ) - ) - ) - )); -} - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/Student_Orientation b/courses.dist/modelCourse/templates/Student_Orientation new file mode 120000 index 0000000000..d8e423ae44 --- /dev/null +++ b/courses.dist/modelCourse/templates/Student_Orientation @@ -0,0 +1 @@ +../../../webwork2/assets/pg/Student_Orientation \ No newline at end of file diff --git a/courses.dist/modelCourse/templates/achievements/default_achievements.axp b/courses.dist/modelCourse/templates/achievements/default_achievements.axp index 09c37b76f3..08ac002f62 100644 --- a/courses.dist/modelCourse/templates/achievements/default_achievements.axp +++ b/courses.dist/modelCourse/templates/achievements/default_achievements.axp @@ -3,8 +3,8 @@ crack_o_dawn,"Crack O' Dawn",102,secret,default,"Finish a homework set between 5 on_the_hour,"Watching the Clock",103,secret,"default,jitar","Finish a problem at the top of the hour.",10,,on_the_hour.at,on_the_hour.png last_minute,"Last Minute Math",104,secret,default,"Complete a homework within 30 minutes of the due date.",10,,last_minute.at,last_minute.png still_not_right,"It's Still Not Right",105,secret,"default,jitar","Input the exact same (incorrect) answer 10 times in a row.",5,,still_not_right.at,still_not_right.png -persistance,"Persistence is not Futile",106,secret,"default,jitar","Solve a problem after 20 incorrect submissions.",10,,persistance.at,persistance.png -super_persistance,"Green Never Looked So Good",107,secret,"default,jitar","Solve a problem after 100 incorrect submissions.",10,,super_persistance.at,super_persistance.png +persistence,"Persistence is not Futile",106,secret,"default,jitar","Solve a problem after 20 incorrect submissions.",10,,persistence.at,persistence.png +super_persistence,"Green Never Looked So Good",107,secret,"default,jitar","Solve a problem after 100 incorrect submissions.",10,,super_persistence.at,super_persistence.png super_speed_math,"Careful Planning and Quick Fingers",108,secret,default,"Spend less than 10 minutes entering answers to a homework set.",20,,super_speed_math.at,super_speed_math.png hows_your_finger,"Hows Your Finger?",109,secret,"default,jitar","Have more than 250 submissions on a homework problem. ",10,,hows_your_finger.at,hows_your_finger.png third_time,"Third Times the Charm",110,secret,"default,jitar","Solve a problem on the third submission",10,,third_time.at,third_time.png @@ -53,6 +53,6 @@ level_four,"Level 4 Journeyman",9904,level,"default,gateway,jitar","You have bee level_five,"Level 5 Fellowcraft",9905,level,"default,gateway,jitar","You have been awarded a Box of Transmogrification",,,level_five.at,level_five.png level_six,"Level 6 Adept",9906,level,"default,gateway,jitar","You have been awarded a Greater Rod of Revelation",,,level_six.at,level_six.png level_seven,"Level 7 Craftsman",9907,level,"default,gateway,jitar","You have been awarded a Robe of Longevity",,,level_seven.at,level_seven.png -level_eight,"Level 8 Artesian",9908,level,"default,gateway,jitar","You have been awarded a Cake of Enlargement",,,level_eight.at,level_eight.png +level_eight,"Level 8 Artisan",9908,level,"default,gateway,jitar","You have been awarded a Cake of Enlargement",,,level_eight.at,level_eight.png level_nine,"Level 9 Specialist",9909,level,"default,gateway,jitar","You have been awarded a Scroll of Resurrection",,,level_nine.at,level_nine.png level_ten,"Level 10 Professor",9910,level,"default,gateway,jitar","You have been awarded a Greater Tome of Enlightenment",,,level_ten.at,level_ten.png diff --git a/courses.dist/modelCourse/templates/achievements/extensions.at b/courses.dist/modelCourse/templates/achievements/extensions.at new file mode 100644 index 0000000000..5be5cf1373 --- /dev/null +++ b/courses.dist/modelCourse/templates/achievements/extensions.at @@ -0,0 +1,9 @@ +# This achievement executes unconditionally, and awards the user a certain number +# of ExtendDueDate, SuperExtendDueDate, and ResurrectHW reward items. How many of +# each is specified below. + +$globalData->{ExtendDueDate} += 10; +$globalData->{SuperExtendDueDate} += 5; +$globalData->{ResurrectHW} += 3; +return 1; + diff --git a/courses.dist/modelCourse/templates/achievements/extensions.axp b/courses.dist/modelCourse/templates/achievements/extensions.axp new file mode 100644 index 0000000000..04524eb33c --- /dev/null +++ b/courses.dist/modelCourse/templates/achievements/extensions.axp @@ -0,0 +1 @@ +extensions,Extensions,10001,one_time,default,"Award homework set extensions",0,,extensions.at,Fractal-Cornucopia.svg diff --git a/courses.dist/modelCourse/templates/achievements/notifications/default.html.ep b/courses.dist/modelCourse/templates/achievements/notifications/default.html.ep new file mode 100644 index 0000000000..726fb78a62 --- /dev/null +++ b/courses.dist/modelCourse/templates/achievements/notifications/default.html.ep @@ -0,0 +1,12 @@ +<%= $user->first_name %>, + +Congratulations, you just earned the "<%= $achievement->{name} %>" achievement! + +<%= $achievement->{description} %> + +% if ($nextLevelPoints) { +You have <%= $nextLevelPoints - $pointsEarned %> points remaining until your next level-up. + +% } +Great job! +--Prof. X diff --git a/courses.dist/modelCourse/templates/achievements/persistance.at b/courses.dist/modelCourse/templates/achievements/persistence.at similarity index 100% rename from courses.dist/modelCourse/templates/achievements/persistance.at rename to courses.dist/modelCourse/templates/achievements/persistence.at diff --git a/courses.dist/modelCourse/templates/achievements/super_persistance.at b/courses.dist/modelCourse/templates/achievements/super_persistence.at similarity index 100% rename from courses.dist/modelCourse/templates/achievements/super_persistance.at rename to courses.dist/modelCourse/templates/achievements/super_persistence.at diff --git a/courses.dist/modelCourse/templates/email/welcome.msg b/courses.dist/modelCourse/templates/email/welcome.msg index 036c20f686..43c25edfbb 100644 --- a/courses.dist/modelCourse/templates/email/welcome.msg +++ b/courses.dist/modelCourse/templates/email/welcome.msg @@ -1,6 +1,7 @@ ## template for a Welcome message to be emailed to class (delete this line) +## Note that the From: address will be replaced by the email address of the account +## from which the message is sent. From: teacher@somewhere.edu (Jan Teacher) -Reply-To: teacher@somewhere.edu Subject: online homework for Math 123 Message: Hi $FN, @@ -12,7 +13,7 @@ Your username/password is: $LOGIN/$SID You should change your password once you have logged in by visiting -User Settings in the sidebar navigation. +Account Settings in the sidebar navigation. Have fun, Jan diff --git a/courses.dist/modelCourse/templates/set0.def b/courses.dist/modelCourse/templates/set0.def deleted file mode 100644 index 98afe89458..0000000000 --- a/courses.dist/modelCourse/templates/set0.def +++ /dev/null @@ -1,77 +0,0 @@ -assignmentType = default -openDate = 01/07/2000 at 06:00am EST -reducedScoringDate = 01/20/2009 at 06:00am EST -dueDate = 01/20/2009 at 06:00am EST -answerDate = 01/21/2009 at 06:00am EST -enableReducedScoring = N -paperHeaderFile = set0/paperHeaderFile0.pg -screenHeaderFile = set0/screenHeaderFile0.pg -description = -restrictProbProgression = 0 -emailInstructor = 0 - -problemListV2 -problem_start -problem_id = 1 -source_file = set0/prob1.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 2 -source_file = set0/prob1a.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 3 -source_file = set0/prob1b.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 4 -source_file = set0/prob2.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 5 -source_file = set0/prob3.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 6 -source_file = set0/prob4/prob4.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 7 -source_file = set0/prob5.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end - diff --git a/courses.dist/modelCourse/templates/set0/paperHeaderFile0.pg b/courses.dist/modelCourse/templates/set0/paperHeaderFile0.pg deleted file mode 100644 index 2ae97e50a8..0000000000 --- a/courses.dist/modelCourse/templates/set0/paperHeaderFile0.pg +++ /dev/null @@ -1,25 +0,0 @@ -##Problem set header for set 0 - -&DOCUMENT; - -loadMacros( -"PG.pl", -"PGbasicmacros.pl", -"PGchoicemacros.pl", -"PGanswermacros.pl" -); - -TEXT(EV2(< -HINT - -

-

- - - - - -
- - - - WeBWorK -

-
- -

Hint

-A line representing a function slanting upwards to the right is increasing, since as you increase -the inputs, the outputs increase as well. -

- If the line is horizontal then the outputs don't change -so the function is constant. -

-When you zoom in on most functions they look like straight lines. (The functions for which -this works are called differentiable functions --- there ARE functions which are not differentiable, -but we'll get to those later.) In any case you can tell whether a function is increasing or -decreasing by zooming in and determining whether the straight line is increasing or decreasing. -

-This means that the study of (differentiable) functions is largely a matter of understanding what -happens with straight line (or linear) functions. -

-

Remark on hints

-WeBWorK hints may or may not be helpful. - -Remember that WeBWorK's primary mission is to tell you whether your answer is correct. Programming -a computer to do this is hard enough, programming a computer to accurately understand why you -getting the wrong answer and to offer effective help is much harder. -

- -If you are having trouble with a problem -it is good to seek help from a human!
-This could be a fellow student, the TA or the professor. It is also helpful to look -at the relevant chapter of the textbook. You can use the index to look up key words -if you're not sure where to look. -

Don't -waste too much time guessing at answers! -

-Read the book, your notes. Talk to someone! Print out a hard copy of the problem set -and take it down to the Pit to work on! - -

-You can use the Feedback button -at the bottom of each problem page to send e-mail to the TA and to the professor. - - - diff --git a/courses.dist/modelCourse/templates/set0/prob5.pg b/courses.dist/modelCourse/templates/set0/prob5.pg deleted file mode 100644 index 5b8bb88121..0000000000 --- a/courses.dist/modelCourse/templates/set0/prob5.pg +++ /dev/null @@ -1,43 +0,0 @@ -##DESCRIPTION -## practice problem -##ENDDESCRIPTION - -##KEYWORDS('sample') - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( -"PG.pl", -"PGbasicmacros.pl", -"PGchoicemacros.pl", -"PGanswermacros.pl", -"PGauxiliaryFunctions.pl" -); - -$showPartialCorrectAnswers = 0; - - -$a = random(100,200,1); -$b = random(250,350,1); -$c = random(-31,-3,1); -TEXT(EV2(<EV3(<<'EOT'), HTML=>"", Latex2HTML=>"" )); -\noindent {\large \bf $studentName} -\hfill -\noindent {\large \bf MAA Minicourse New Orleans January 2001} -\par -\noindent WeBWorK assignment number \{ protect_underbar($setNumber) \} closes $formattedDueDate;. -\hrule -EOT - - -################## -# EDIT BELOW HERE -################## - -BEGIN_TEXT -$BR -$BR -Welcome to the MAA short course on $BBOLD WeBWorK $EBOLD. -$PAR -Here is a synopsis of the tutorial examples presented in this set. They have been designed for learning the PG language, and are not necessarily the best questions to use for mathematics instruction. -$PAR -$BBOLD 1. Hello world example: $EBOLD Illustrates the basic structure of a PG problem. -$PAR -$BBOLD 2. Standard example: $EBOLD This covers what you need to know to ask the majority of the questions you would want to ask in a calculus course. Problems with text answers, numerical answers and answers involving expressions are covered. -$PAR -$BBOLD 3. Simple multiple choice example: $EBOLD Uses lists(arrays) to implement a multiple choice question. -$PAR -$BBOLD 4. Multiple choice example: $EBOLD Uses the multiple choice object to implement a multiple choice question. -$PAR -$BBOLD 5. Matching list example: $EBOLD -$PAR -$BBOLD 6. True/false example: $EBOLD -$PAR -$BBOLD 7. Pop-up true/false example: $EBOLD Answers are chosen from a pop-up list. -$PAR -$BBOLD 8. On-the-fly graphics example 1: $EBOLD The graphs are regenerated each time you press the submit button -$PAR -$BBOLD 9. On-the-fly-graphics example 2: $EBOLD -- Adds some randomization to the first example. -$PAR -$BBOLD 10. Static graphics example: $EBOLD Presents graphs created on a separate application (e.g. Mathematica) and saved. -$PAR -$BBOLD 11. Hermite graph example: $EBOLD A particularly useful way of generating predictable graphs by specifying the value and first derivative of a function at each point. Piecewise linear graphs are also included in this example. -$PAR -$BBOLD 12. HTML links example: $EBOLD Shows how to link other web resources to your WeBWorK problem. -$PAR -$BBOLD 13. JavaScript example 1: $EBOLD An example which takes advantage of this interactive media! This one requires students to calculate the derivative of a function from the definition. -$PAR -$BBOLD 14. JavaScript example 2: $EBOLD A variant of the previous example that generates the example function as a cubic spline so that students can't read the javaScript code to find out the answer. -$PAR -$BBOLD 15. Vector field example $EBOLD Generates vector field graphs on-the-fly. -$PAR -$BBOLD 16. Conditional question example: $EBOLD Illustrates how you can create a problem which first asks an easy question, and once that has been answered correctly, follows up with a more involved question on the same material. -$PAR -$BBOLD 17 Java applet example: $EBOLD A preliminary example of how to include Java applets in WeBWorK problems. -$HR -END_TEXT - -################## -# EDIT ABOVE HERE -################## -BEGIN_TEXT -The primary purpose of WeBWorK is to let you know if you are getting the right answer or to alert -you if you get the wrong answer. Usually you can attempt a problem as many times as you want before -the close date. However, if you are having trouble figuring out your error, you should -consult the book, or ask a fellow student, one of the TA's or -your professor for help. Don't spend a lot of time guessing -- it's not very efficient or effective. -The computer has NO CLUE about WHY your answer is wrong. Computers are good at checking, -but for help go to a human. - -$PAR -Give 4 or 5 significant digits for (floating point) numerical answers. -For most problems when entering numerical answers, you can if you wish -enter elementary expressions such as \( 2\wedge3 \) instead of 8, \( sin(3*pi/2) \)instead -of -1, \( e\wedge (ln(2)) \) instead of 2, -\( (2+tan(3))*(4-sin(5))\wedge6-7/8 \) instead of 27620.3413, etc. -$PAR - Here's the -\{ htmlLink(qq!http://webwork.maa.org/wiki/Available_Functions!,"list of the functions") \} - which WeBWorK understands. - -Along with the \{htmlLink(qq!http://webwork.maa.org/wiki/Units!, "list of units")\} which WeBWorK understands. This can be useful in -physics problems. -$PAR -You can use the Feedback button on each problem -page to send e-mail to the professors. - -END_TEXT - -ENDDOCUMENT(); # This should be the last executable line in the problem. - diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/conditionalquestionexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/conditionalquestionexample.pg deleted file mode 100644 index 4b067b39ee..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/conditionalquestionexample.pg +++ /dev/null @@ -1,83 +0,0 @@ -DOCUMENT(); -loadMacros( - "PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl" -); -TEXT($BBOLD, "Conditional questions example", $EBOLD, $BR,$BR); -$showPartialCorrectAnswers = 1; - -$a1 = random(3,25,1); -$b1 = random(2,27,1); -$x1 = random(-11,11,1); -$a2 = $a1+5; - -BEGIN_TEXT -If \( f(x) = $a1 x + $b1 \), find \( f'( $x1 ) \). -$BR $BR \{NAMED_ANS_RULE('first_answer',10) \} -$BR -END_TEXT - - - -$ans_eval1 = num_cmp($a1); -NAMED_ANS(first_answer => $ans_eval1); - -# Using named answers allows for more control. Any unique label can be -# used for an answer. -# (see http://webwork.math.rochester.edu/docs/docs/pglanguage/pgreference/managinganswers.html -# for more details on answer evaluator formats and on naming answers -# so that you can refer to them later. Look also at the pod documentation in -# PG.pl and PGbasicmacros.pl which you can also reach at -# http://webwork.math.rochester.edu/docs/techdescription/pglanguage/index.html) - -# Check to see that the first answer was answered correctly. If it was then we -# will ask further questions. -$first_Answer = $inputs_ref->{first_answer}; # We need to know what the answer - # was named. -$rh_ans_hash = $ans_eval1->evaluate($first_Answer); - -# warn pretty_print($rh_ans_hash); # this is useful technique for finding errors. - # When uncommented it prints out the contents of - # the ans_hash for debugging - -# The output of each answer evaluator consists of a single %ans_hash with (at -# least) these entries: -# $ans_hash{score} -- a number between 0 and 1 -# $ans_hash{correct_ans} -- The correct answer, as supplied by the instructor -# $ans_hash{student_ans} -- This is the student's answer -# $ans_hash{ans_message} -- Any error message, or hint provided by -# the answer evaluator. -# $ans_hash{type} -- A string indicating the type of answer evaluator. -# -- Some examples: -# 'number_with_units' -# 'function' -# 'frac_number' -# 'arith_number' -# For more details see -# http://webwork.math.rochester.edu/docs/docs/pglanguage/pgreference/answerhashdataype.html - -# If they get the first answer right, then we'll ask a second part to the -# question ... -if (1 == $rh_ans_hash->{score} ) { - - # WATCH OUT!!: BEGIN_TEXT and END_TEXT have to be on lines by - # themselves and left justified!!! This means you can't indent - # this section as you might want to. The placement of BEGIN_TEXT - # and END_TEXT is one of the very few formatting requirements in - # the PG language. - -BEGIN_TEXT - $PAR Right! Now - try the second part of the problem: $PAR $HR - If \( f(x) = $a2 x + \{$b1+5\} \), find \( f'( x) \). - $BR $BR \{ NAMED_ANS_RULE('SecondAnSwEr',10) \} - $BR -END_TEXT - -$ans_eval2 = num_cmp($a2); - - NAMED_ANS(SecondAnSwEr => $ans_eval2); - -} -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/hello.pg b/courses.dist/modelCourse/templates/setMAAtutorial/hello.pg deleted file mode 100644 index a27388830d..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/hello.pg +++ /dev/null @@ -1,18 +0,0 @@ -DOCUMENT(); -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", - ); - -BEGIN_TEXT -Complete the sentence: $PAR -\{ ans_rule(20) \} world! -END_TEXT - -ANS( str_cmp( "Hello" ) ); # here is the answer, a string. - - - - -ENDDOCUMENT(); - \ No newline at end of file diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/hermitegraphexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/hermitegraphexample.pg deleted file mode 100644 index 8af5c95cbf..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/hermitegraphexample.pg +++ /dev/null @@ -1,124 +0,0 @@ -DOCUMENT(); -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", - "PGnumericalmacros.pl", - "PGgraphmacros.pl" -); -TEXT($BBOLD, "Hermite polynomial graph example", $EBOLD, $BR,$BR); -$showPartialAnswers = 1; - -$graph = init_graph(-5,-5,5,5,'axes'=>[0,0],'grid'=>[10,10]); - -my (@x_values1, @y_values1); -foreach $i (0..10) { - $x_values1[$i] =$i-5; - $y_values1[$i] = random(-4,4,1); -} - -# creates a reference to a perl subroutine for the piecewise linear function -# passing through the defined points -$fun_rule = plot_list(~~@x_values1, ~~@y_values1); - -#new function is to be plotted in graph -$f1=new Fun($fun_rule, $graph); -$f1->color('black'); - -$trans = non_zero_random(-2,2,1); -# add a new function to the graph which is a translate of the first -$fun_rule2 = sub{ my $x = shift; &$fun_rule($x-$trans) }; -$f2 = new Fun($fun_rule2, $graph); -$f2->color('orange'); - -$graph->stamps(open_circle(-1,&$fun_rule(-1),'black') ); -# indicates open interval at the left endpoint -$graph->stamps(closed_circle(4,&$fun_rule(4), 'black') ); -# and a closed interval at the right endpoint -# Be careful about getting the stamps properly located on the translated -# function below: -$graph->stamps(open_circle(-1 + $trans, &$fun_rule(-1),'orange') ); -# indicates open interval at the left endpoint -$graph->stamps(closed_circle(4 +$trans, &$fun_rule(4), 'orange') ); -# and a closed interval at the right endpoint - -$graph2 = init_graph(-4,-4,4,4,'axes'=>[0,0],'grid'=>[8,8]); -$b1= random(-3.5,3.5,.5); -$b2= random(-3.5,3.5,.5); -$b3= random(-3.5,3.5,.5); -@x_val3 = (-4,-3,-2,-1, 0, 1, 2, 3, 4 ); -@y_val3 = ( 0, 1, 2, 0,$b1, $b2, $b3, 1, 2 ); -@yp_val3= ( .1, 1, 0,-2, 0, 1, 2, -3, 1 ); -$hermite = new Hermite( - ~~@x_val3, # x values - ~~@y_val3, # y values - ~~@yp_val3 # y prime values - ); -$spline_rule = $hermite->rf_f; -$f3 = new Fun($spline_rule, $graph2); -$f3->color('green'); -$graph2->stamps(closed_circle(-4, &$spline_rule(-4), 'green') ) ; -$graph2->stamps(closed_circle( 4, &$spline_rule( 4), 'green') ) ; - -# Insert the graphs and the text. -BEGIN_TEXT - -$PAR -We have developed other ways to specify graphs which are to be created 'on the fly'. -All of these new methods consist of adding macro packages to WeBWorK. Since they -do not require the core of WeBWorK to be changed, these enhancements can be added by -anyone using WeBWorK. -$PAR - These two piecewise linear graphs were created by specifying the points at the nodes. - $BR Click on the graph to view a larger image. -$PAR -\{ image(insertGraph($graph),tex_size => 300, width=> 300, height=> 300 ) \} -$HR -If the black function is written as \(f(x)\), then the orange function -would be written as \( f( \) \{ ans_rule \} \( ) \). -\{ANS(function_cmp("x-$trans")),'' \} -END_TEXT -# $PAR -# The numerical calculations were all written in Perl using -# numerical routines adapted from the Numerical Analysis book by Burden and Faires. -# $BR -# We are also working on a macro which will automatically -# identify the maximum, minimum and inflection points of an arbitary hermite -# cubic spline from its specifying values. This will allow automatic generation -# of problems in which the maximum, minimum and inflection points are to be -# deduced from a graph. -# -# Get the internal local maximums -@critical_points = keys %{$hermite->rh_critical_points}; -@critical_points = num_sort( @critical_points); -@minimum_points = (); -foreach my $x (@critical_points) { - push(@minimum_points, $x) if &{$hermite->rf_fpp}($x) >0 ; -} -# TEXT(pretty_print(~~@minimum_points)); # (for debugging purposes) -$answer_string = ""; -foreach my $x (@minimum_points) { - $answer_string .= EV2(' \{ ans_rule(10) \} '); -} - -BEGIN_TEXT -$HR -This graph was created using a hermite spline by specifying points at - -\{ begintable(1+scalar( @x_val3 ) ) \} -\{ row('x', @x_val3)\} -\{ row('y', @y_val3) \} -\{ row('yp',@yp_val3) \} -\{endtable() \} - -$PAR -\{ begintable(2) \} -\{row( image(insertGraph($graph2), tex_size => 300,width=>300, height=> 300), - "List the internal local minimum points $BR in increasing order: $BR $answer_string" - ) \} -\{ endtable() \} - -$PAR -END_TEXT -ANS(num_cmp([ @minimum_points ], tol => .3)); - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-24438.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-24438.gif deleted file mode 100644 index df1f85cbd2..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-24438.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-31126.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-31126.gif deleted file mode 100644 index b0ad26bb6e..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-31126.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-34859.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-34859.gif deleted file mode 100644 index 702784afdd..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-34859.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-42639.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-42639.gif deleted file mode 100644 index dfebffd690..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-42639.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-76239.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-76239.gif deleted file mode 100644 index 741fdd8452..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-76239.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-89540.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-89540.gif deleted file mode 100644 index aa9a75d45a..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-89540.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-91734.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-91734.gif deleted file mode 100644 index cd5aae4ebd..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-91734.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-96355.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-96355.gif deleted file mode 100644 index 23ac97828b..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1-96355.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1.gif deleted file mode 100644 index 073055392b..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/1.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-42653.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-42653.gif deleted file mode 100644 index 709cd644e2..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-42653.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-49261.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-49261.gif deleted file mode 100644 index 3767c0de47..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-49261.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-54427.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-54427.gif deleted file mode 100644 index 11c71d8010..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-54427.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-62384.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-62384.gif deleted file mode 100644 index eae5ade6ae..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-62384.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-64591.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-64591.gif deleted file mode 100644 index 4bd408e32f..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-64591.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-70190.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-70190.gif deleted file mode 100644 index 3e646a41ac..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-70190.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-81779.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-81779.gif deleted file mode 100644 index 944f3efcf6..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-81779.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-92879.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-92879.gif deleted file mode 100644 index 11506db3bc..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2-92879.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2.gif deleted file mode 100644 index b85a6d3ad3..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/2.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-14197.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-14197.gif deleted file mode 100644 index c28c1b68ee..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-14197.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-14538.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-14538.gif deleted file mode 100644 index c57d2fbe84..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-14538.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-37616.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-37616.gif deleted file mode 100644 index a9c2db85c3..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-37616.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-46739.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-46739.gif deleted file mode 100644 index 4244a8ca61..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-46739.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-52898.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-52898.gif deleted file mode 100644 index afe8cd9d75..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-52898.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-68458.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-68458.gif deleted file mode 100644 index 8690563535..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-68458.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-89262.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-89262.gif deleted file mode 100644 index 5c954beec1..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-89262.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-99389.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-99389.gif deleted file mode 100644 index f948cc3e83..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3-99389.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3.gif b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3.gif deleted file mode 100644 index 5f8f80e7d8..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/3.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/htmllinksexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/htmllinksexample.pg deleted file mode 100644 index 2d2b34176a..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/htmllinksexample/htmllinksexample.pg +++ /dev/null @@ -1,80 +0,0 @@ -DOCUMENT(); -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl" -); -$showPartialCorrectAnswers = 0; - -TEXT($BBOLD, "HTML links example", $EBOLD, $BR,$BR) - -BEGIN_TEXT -This example shows how to link to resources outside the problem itself. -$PAR -Linking to other web pages over the internet is easy. For example, -you can get more information about the buffon needle problem and how it is used by ants to find new nest sites by linking to - \{ htmlLink("http://www.maa.org/mathland/mathtrek_5_15_00.html", - "Ivars Peterson's column on the MAA site") \}. -$PAR -END_TEXT - -# You can write the HTML code yourself, but -# that will look funny when the problem is printed in -# hard copy, so it is probably better to use the -# htmlLink('url','text') function which -# will create something readable when the problem is printed. - -BEGIN_TEXT -All of the files in the html directory of your WeBWorK course site can be read -by anyone with a web browser and the URL (the address of the file). This is a good -place to put files that are referenced by more than one problem in your WeBWorK course. -$PAR -Here is the link to -the -\{ htmlLink(alias("${htmlDirectory}calc.html"), - 'to the calculator page', - qq!target="ww_calculator" - ONCLICK="window.open( this.href, this.target, - 'width=250,height=350,scrollbars=no,resizable=off' - )" -!) \} -stored in the top level of the -html directory of the tutorialCourse. -$PAR -END_TEXT - -# To link to files on your own computer use the alias function whose -# job it is to find the file in question. -# You need to do this access indirectly, because WeBWorK is set up to -# restrict access to most files -- (you don't want everyone reading -# the source text of the WeBWorK problems, they could reconstruct the answer.) -# -# Note that you need double quotes around "${htmlDirectory}calc.html" so that -# the string in $htmlDirectory will be -# concatenated with calc.html to form a string describing -# the DIRECTORY in which the file is to be found. Alias converts -# the directory to a URL - -BEGIN_TEXT -Finally there are files, such as picture files, which are -stored with the problem itself in the same directory. - $BR \{ image("2-70190.gif", width=>200, height=>200) \} - -END_TEXT - -# Image automatically uses alias -# to search for files. - -BEGIN_TEXT -$PAR -And the table below has three more graphs which are stored -in the directory containing the current problem. $PAR -END_TEXT - -TEXT( - begintable(3), - row( image( [ ( '1-24438.gif', '2-49261.gif', '3-37616.gif') ], - tex_size=>200, width=>200, height=>200 )), - endtable() -); - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/javaappletexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/javaappletexample.pg deleted file mode 100644 index c42740511e..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/javaappletexample.pg +++ /dev/null @@ -1,95 +0,0 @@ -DOCUMENT(); - -loadMacros("PG.pl", - "PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", - ); - -TEXT($BBOLD, "Java applet example", $EBOLD, $BR,$BR); -# define function to be evaluated -$a= random(1,3,1); -$b= random(-4,4,.1); -$c = random(-4,4,1); -$x0=random(-2,2,1); -$function = FEQ(" ${a}x^2+${b}x +$c "); # This function will be redefined for javaScript as well. -sub fp { # define a subroutine to calculate the derivative - my $x = shift; - 2*$a*$x+$b; -} -$ans = fp($x0); - -BEGIN_TEXT -$PAR -This problem illustrates how you can embed Java applet code in a WeBWorK example -to create an interactive homework problem that could never be provided by a text book. -$PAR -WeBWorK can use existing $BBOLD javaScript$EBOLD and $BBOLD Java $EBOLD -code to augment its capabilities. -$HR - -END_TEXT -$javaApplet = < - - - - - - - -

-mathbean applet from David Ecks -
-EOF -# only print out the java applet code when viewing on the screen -TEXT(MODES( - TeX => " \fbox{ The java applet was displayed here - }", - HTML => $javaApplet, -)); - -$a1= random(-3,3,.5); -$a2= random(-3,3,.5); -$a3= random(-3,3,.5); -$b1 = ($a1/2)**2; # remember to use ** for exponentiation when - # calculating in pure Perl! -$b2= ($a2 / 2)**2; -$b3 = ($a3 / 2)**2; - -ANS( num_cmp( $b1, reltol => 10, format=>'%0.2g')); -ANS( num_cmp( $b2, reltol => 10, format=>'%0.2g')); -ANS( num_cmp( $b3, reltol => 10, format=>'%0.2g')); - -BEGIN_TEXT - -$PAR -The graph above represents the function -\[f(x) = x^2 + a x +b \] -where \( a \) and \( b \) are parameters. $PAR - -For each value of \( a \) find the value of \( b \) which -makes the graph just touch the x-axis. -$BR -if a= $a1 then \{ ans_rule(10) \}$BR -if a= $a2 then \{ ans_rule(10) \}$BR -if a= $a3 then \{ ans_rule(10) \} $PAR - -Does this relationship between a and b specify b as a function of a? - \{ ans_rule(4) \} (Yes or No)$BR - -Does this relationship between a and b specify a as a function of b? - \{ ans_rule(4) \} (Yes or No)$BR - -Write a formula for calculating this value of \( b \) from \( a \).$BR -b = \{ ans_rule(40) \} - -END_TEXT -ANS(str_cmp('Yes') ); -ANS(str_cmp('No') ); -ANS(function_cmp( '(a/2)^2', 'a') ); - - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/javascriptexample1.pg b/courses.dist/modelCourse/templates/setMAAtutorial/javascriptexample1.pg deleted file mode 100644 index b12f48a0aa..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/javascriptexample1.pg +++ /dev/null @@ -1,145 +0,0 @@ -DOCUMENT(); - -loadMacros( - "PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", -); -TEXT($BBOLD, "JavaScript Example 1", $EBOLD, $BR,$BR); -# define function to be evaluated -$a= random(1,3,1); -$b= random(-4,4,.1); -$c = random(-4,4,1); -$x0=random(-2,2,1); - -# function = ${a}x^2+${b}x +${c} -# This is just to provide the correct answer. -# This function will be defined for javaScript below. -sub fp { # define a perl subroutine to calculate the derivative - my $x = shift; - 2*$a*$x+$b; -} -$ans = fp($x0); - -## This text will be placed in the header section of the HTML page -## not in the body where TEXT output is placed. -## Not processing is done. - -HEADER_TEXT(< - - - -EOF - -TEXT(MODES( TeX => "", - Latex2HTML => "\begin{rawhtml} - ~~n\end{rawhtml} - ", - HTML_tth => "~~n", - HTML => "~~n" -)); - -$functionArrow = MODES( - TeX => "\(- f\rightarrow\)", - Latex2HTML => "\(- f\rightarrow \) ", - HTML_tth => "-- f -- >   ", - HTML => '-- f -- >   ' -); - -# The following string contains a combination of HTML and javaScript -# which displays the input table for the javaScript calculator - -$javaScript =< - - - - - - - - - - - - - - - - -
- - - - - -
- - - - - -
- - - - - -
- - -ENDOFSCRIPT - - - -BEGIN_TEXT - -Find the derivative of the function f(x). The windows below will tell -you the value of f for any input x. (I call this an "oracle function", since -if you ask, it will tell.) -$PAR -\(f '( $x0 ) \) = \{ans_rule(50 ) \} -$PAR -You may want to use a -\{ htmlLink(alias("${htmlDirectory}calc.html"), - 'calculator', - qq! TARGET = "ww_calculator" - ONCLICK="window.open( this.href,this.target, - 'width=200, height=350, scrollbars=no, resizable=off' - )" -!) \} - -to find the result. - You can also enter numerical expressions and have - WeBWorK do the calculations for you. -END_TEXT - -# Here is where we actually print the javaScript, or alternatives for printed output. - -TEXT(MODES( - TeX => " \fbox{ The java Script calculator was displayed here - }", - HTML => $javaScript, - )); - -ANS(num_cmp($ans,reltol => 1) ); #We are allowing 1 percent error for the answer. - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/javascriptexample2.pg b/courses.dist/modelCourse/templates/setMAAtutorial/javascriptexample2.pg deleted file mode 100644 index 53be40e27a..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/javascriptexample2.pg +++ /dev/null @@ -1,153 +0,0 @@ -DOCUMENT(); - -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", - "PGnumericalmacros.pl", # needed for the javaScript spline code - ); -TEXT($BBOLD, "JavaScript Example 2", $EBOLD, $BR,$BR); - -# define function to be evaluated -$a= random(1,3,1); -$b= random(-4,4,.1); -$c = random(-4,4,1); -$x0=random(-2,2,1); - -# function = ${a}x^2+${b}x +${c}sin(x) -# This is just to provide the correct answer. -# This function will be defined for javaScript below. -sub fp { # define a perl subroutine to calculate the derivative - my $x = shift; - 2*$a*$x+$b; -} -$ans = fp($x0); - -# approximate the function by a cubic spline -sub fun{ - my $x = shift; - ${a}*$x**2+$b*$x +$c; -} -@x = (); -@y = (); -for ( $x1 = -3; $x1<3; $x1 = $x1+.1) { - push(@x, $x1); - push(@y, fun($x1) ); -} -#warn join(" ", @x) ; # test the calculation of the data points -#warn join(" ", @y) ; -$javascript= javaScript_cubic_spline(~~@x, ~~@y, name =>'func'); - -#$javascript =~s/'func') ); - - -TEXT(MODES( TeX => "", - Latex2HTML => "\begin{rawhtml} - ~~n\end{rawhtml} - ", - HTML_tth => "~~n", - HTML => "~~n" -)); - -$functionArrow = MODES( - TeX => "\(- f\rightarrow\)", - Latex2HTML => "\(- f\rightarrow \) ", - HTML_tth => "-- f -- >   ", - HTML => '-- f -- >   ' -); - -# The following string contains a combination of HTML and javaScript -# which displays the input table for the javaScript calculator - -$javaScript =< - - - - - - - - - - - - - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - -ENDOFSCRIPT - -BEGIN_TEXT -Find the derivative of the function f(x). The windows below will tell -you the value of f for any input x. (I call this an "oracle function", since -if you ask, it will tell.) -$PAR -\(f'( $x0 ) \) = \{ans_rule(50 ) \} -$PAR -You may want to use a -\{ htmlLink(alias("${htmlDirectory}calc.html"), - 'calculator', - qq! TARGET = "ww_calculator" - ONCLICK="window.open( this.href,this.target, - 'width=200, height=350, scrollbars=no, resizable=off' - )" -!) \} - -to find the result. You can also enter numerical expressions and -have WeBWorK do the calculations for you. -END_TEXT - -# Here is where we actually print the javaScript, or alternatives for printed output. -TEXT(MODES( - TeX => " \fbox{ The java Script calculator was displayed here - }", - Latex2HTML => "\begin{rawhtml} $javaScript \end{rawhtml}", - HTML_tth => $javaScript, - HTML => $javaScript, - )); - - - - -ANS(num_cmp($ans,reltol => 1) ); #We are allowing 1 percent error for the answer. - - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/liteApplet1.pg b/courses.dist/modelCourse/templates/setMAAtutorial/liteApplet1.pg deleted file mode 100644 index a00e0178be..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/liteApplet1.pg +++ /dev/null @@ -1,67 +0,0 @@ -DOCUMENT(); - -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", -); - -$showPartialCorrectAnswers = 1; - -# The link to the java applet is hard wired to use the java applet -# served from the University of Rochester WeBWorK machine. -# It is possible to set this up so that the java applet is served -# from any machine -# For details use the Feedback button to contact the authors of WeBWorK - -BEGIN_TEXT -This is a lite applet designed by Frank Wattenberg. -$BR -\{htmlLink( '/webwork2_course_files/demoCourse/live_map_instructions.html ', -'Instructions for using the map',' target="intro" ' )\} -$HR -END_TEXT -$appletText = -appletLink( -q! archive="/courses/system_html/applets/Image_and_Cursor_All/Image_and_Cursor.jar" -code="Image_and_Cursor" width = 500 height = 458 -!, -q!Your browser does not support Java, so nothing is displayed. - - - - - - - -! -); -sub dist { - my $ra_pt1 = shift; - my $ra_pt2 =shift; - my $conversion = 300 /(145 - 72); # number of km per pixel - return $conversion* sqrt( ($ra_pt1->[0] - $ra_pt2->[0])**2 + ($ra_pt1->[1] - $ra_pt2->[1])**2); -} - -$kandahar = [132,101]; -$kabul = [209,185]; -$mazur_e_sharif = [170, 243]; -$shindand = [46, 155]; - -$questions = EV3( -"$PAR How far is it from Kandahar to Kabul? " , ans_rule(30), -" $PAR How far is it from Kabul to Mazar-e-Sharif? ", ans_rule(30), -" $PAR How far is it from Kandahar to Shindand? " , ans_rule(30), -); -#TEXT( -#begintable(2), -#row( $appletText, $questions), -#endtable() -#); -TEXT($appletText, $questions); -ANS(num_cmp(dist($kandahar,$kabul), reltol => 3, units=>'km')); -ANS(num_cmp(dist($kabul, $mazur_e_sharif), reltol => 3, units=>'km')); -ANS(num_cmp(dist($kandahar,$shindand), reltol => 3, units=>'km')); - - - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/liteApplet2.pg b/courses.dist/modelCourse/templates/setMAAtutorial/liteApplet2.pg deleted file mode 100644 index 00b754242d..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/liteApplet2.pg +++ /dev/null @@ -1,72 +0,0 @@ -DOCUMENT(); - -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", -); - -$showPartialCorrectAnswers = 1; - -BEGIN_TEXT -This is a lite applet designed by Frank Wattenberg. -$BR -\{htmlLink( '/webwork2_course_files/demoCourse/live_map_instructions.html ', -'Instructions for using the map',' target="intro" ' )\} -$HR -END_TEXT -TEXT( -appletLink( -q! archive="/courses/system_html/applets/Image_and_Cursor_All/Image_and_Cursor.jar" -code="Image_and_Cursor" width = 500 height = 458 -!, -q!Your browser does not support Java, so nothing is displayed. - - - - - - - -! -), -); -sub dist { - my $ra_pt1 = shift; - my $ra_pt2 =shift; - $conversion = 300 /(145 - 72); # number of km per pixel - return $conversion * sqrt( ($ra_pt1->[0] - $ra_pt2->[0])**2 + ($ra_pt1->[1] - $ra_pt2->[1])**2); -} -@cities = ( - { name => 'Kandahar', location => [132,101] }, - { name => 'Kabul', location => [209,185] }, - { name => 'Mazur e Sharif', location => [170, 243] }, - { name => 'Shindand', location => [46, 155] }, - { name => 'Zaranj', location => [39, 93] } -); -@index = NchooseK(scalar(@cities), 3 ); -sub cityName { - my $index = shift ; - $cities[$index -1]->{name}; -} -sub cityLoc { - my $index = shift; - $cities[$index-1]->{location}; -} - -$conversion = 300 /(145 - 72); # number of km per pixel -BEGIN_TEXT -$PAR -How far is it from \{cityName($index[1])\} to \{cityName($index[2])\}? \{ans_rule(30)\} -$PAR -How far is it from \{cityName($index[1])\} to \{cityName($index[3])\}? \{ans_rule(30)\} -$PAR -How far is it from \{cityName($index[2])\} to \{cityName($index[3])\}? \{ans_rule(30)\} -END_TEXT - -ANS(num_cmp(dist(cityLoc($index[1]),cityLoc($index[2])), reltol=>3, units=>'km')); -ANS(num_cmp(dist(cityLoc($index[2]), cityLoc($index[2])), reltol=>3, units=>'km')); -ANS(num_cmp(dist(cityLoc($index[2]),cityLoc($index[2])), reltol=>3, units=>'km')); - - - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/matchinglistexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/matchinglistexample.pg deleted file mode 100644 index 300c8db8e9..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/matchinglistexample.pg +++ /dev/null @@ -1,129 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", - ); - - -TEXT($BBOLD, "Matching list example", $EBOLD, $BR,$BR); - - -# Since this is a matching question, we do not usually wish to tell students -# which parts of the matching question have been answered correctly and which -# areincorrect. That is too easy. To accomplish this we set the following -# flag to zero. -$showPartialCorrectAnswers = 0; - -# Make a new match list -$ml = new_match_list(); -# enter questions and matching answers -$ml -> qa ( - "\( \sin(x) \)", # Notice the use of the LateX construction - "\( \cos(x) \)", # for math mode: \\( ... \\) and the use of TeX - "\( \cos(x) \)", # symbols such as \\sin and \\tan. - "\( -\sin(x) \)", - "\( \tan(x) \)", - "\( \sec^2(x) \)", # Remember that in these strings we are - # only specifying typography,via TeX, - "\( x^{20} \)", #not any calculational rules. - "\( 20x^{19} \)", - "\( \sin(2x) \)", - "\( 2\cos(2x) \)", - "\( \sin(3x) \)", - "\( 3\cos(3x) \)" -); - - -# Calculate coefficients for another question -$b=random(2,5); -$exp= random(2,5); -$coeff=$b*$exp; -$new_exp = $exp-1; - -# Store the question and answers in the match list object. -$ml -> qa ( - '\( ${b}x^$exp \)', - '\( ${coeff}x^{$new_exp} \)', -); - -# Add another example -$b2=random(2,5); -$exp2= random(2,5); -$coeff2=$b2*$exp; -$new_exp2 = $exp-1; -$ml -> qa ( - "\( ${b2}x^$exp2 \)", - "\( ${coeff2}x^{$new_exp2} \)", -); - -# Choose four of the question and answer pairs at random. -$ml ->choose(4); -# Using choose(8) would choose all eight questions, -# but the order of the questions and answers would be -# scrambled. - -# The following code is needed to make the enumeration work right within tables -# when LaTeX output is being used. -# It is an example of the powerful tools of TeX and perl which are available -# for each PG problem author. -# Once we figure out the best way to protect enumerated lists automatically -# we will include it in the tables macro. Meantime, it is better to have -# have to do it by hand, rather than to have the wrong thing done automatically. - -$BSPACING = MODES( TeX => '\hbox to .5\linewidth {\hspace{0.5cm}\vbox {', - HTML =>' ', - Latex2HTML => ' ' -); -$ESPACING = MODES(TeX => '}}', HTML =>'', Latex2HTML => ''); -sub protect_enumerated_lists { - my @in = @_; - my @out = (); - foreach my $item (@in) { - push(@out, $BSPACING . $item . $ESPACING); - } - @out; -} -# End of code for protecting enumerated lists in TeX. - -# Now print the text using $ml->print_q for -# the questions and $ml->print_a to print the answers. - -BEGIN_TEXT -$PAR - -Place the letter of the derivative next to each function listed below: $BR -\{ $ml -> print_q \} -$PAR -\{$ml -> print_a \} -$PAR -END_TEXT - -ANS( str_cmp( $ml->ra_correct_ans ) ) ; -# insist that the first two questions (labeled 0 and 1) are always included -$ml ->choose([0,1],1); -BEGIN_TEXT -Let's print the questions again, but insist that the -first two questions (about sin and cos) always be included. -Here is a second way to format this question, using tables: -$PAR -\{begintable(2)\} -\{row(protect_enumerated_lists( $ml->print_q, $ml -> print_a) )\} -\{endtable()\} -$PAR -And below is yet another way to enter a table of questions and answers: -$PAR -END_TEXT -ANS( str_cmp( $ml->ra_correct_ans ) ) ; -# Finally add a last answer -$ml ->makeLast("The derivative is not provided"); -BEGIN_TEXT - \{ begintable(2) \} - \{ row( protect_enumerated_lists($ml->print_q, $ml ->print_a))\} - \{endtable()\} -END_TEXT -# Enter the correct answers to be checked against the answers to the students. -ANS( str_cmp( $ml->ra_correct_ans ) ) ; - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/multiplechoiceexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/multiplechoiceexample.pg deleted file mode 100644 index 42d81e42b2..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/multiplechoiceexample.pg +++ /dev/null @@ -1,45 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", - -); -TEXT($BBOLD, "Multiple choice example", $EBOLD, $BR,$BR); - -$showPartialCorrectAnswers = 0; -# Make a new multiple choice object. -$mc = new_multiple_choice(); -# $mc now "contains" the multiple choice object. - -# Insert some questions and matching answers in the q/a list -$mc -> qa (# Notice that the first string is the question - "What is the derivative of tan(x)?", - # The second string is the correct answer - "\( \sec^2(x) \)", -); -$mc ->extra( - "\( -\cot(x) \)", - "\( \tan(x) \)", - # Use double quotes " ... " to enter a string - "\( \cosh(x) \)", - "\( \sin(x) \)", - "\( \cos^3(x) \)", - "\( \text{sech}(x) \)" - # Remember that in these strings we are only specifying typography, - # via TeX, not any calculational rules. -); -# Print the question using $mc->print_q -# Use $mc->print_a to print the list of possible answers. -# These need to be done inside BEGIN_TEXT/END_TEXT to make sure that the -# equations inside the questions and answers are processed properly. - -BEGIN_TEXT - -\{$mc -> print_q \} -$PAR -\{$mc -> print_a\} -END_TEXT -# Enter the correct answers to be checked against the answers to the students. -ANS( str_cmp( $mc->correct_ans ) ) ; - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/ontheflygraphicsexample1.pg b/courses.dist/modelCourse/templates/setMAAtutorial/ontheflygraphicsexample1.pg deleted file mode 100644 index f3423c4e1e..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/ontheflygraphicsexample1.pg +++ /dev/null @@ -1,117 +0,0 @@ -DOCUMENT(); -loadMacros( - "PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", - "PGgraphmacros.pl" -); - -TEXT($BBOLD, "On-the-fly Graphics Example1", $EBOLD, $BR,$BR); -$showPartialCorrectAnswers = 0; - -# First we define a graph with x and y in the range -4 to 4, axes (strong lines) -# defined at the point [0,0] and -# with 8 gridlines horizontally and 8 grid lines veritically. -# $graph is a graph object (or more appropriately, a pointer to a graph object). - -# We will define a function and it's first and second derivatives defined -# on the domain [-4,4] -$dom = 4; -$graph = init_graph(-$dom,-$dom,$dom,$dom,'axes'=>[0,0],'grid'=>[8,8]); - -# Here are the basic colors -- we'll mix them up in the next example -@colors = ("blue", "red", "green"); #orange, yellow, -@scrambled_colors = @colors; -@labels = ('A', 'B', 'C'); -@scrambled_labels = @labels; - -$a=random(0, 6.3, .1); -$b=random(1.1, 1.5, .1); -# now define the functions too be graphed -# defining strings need to be on one line (\n is not handled correctly) -# The three variables $f, $fp, and $fpp contain strings -# with the correct syntax to be inputs into the plot_function -# macro. The FEQ macro (Format EQuation) cleans up the writing of the function. -# Otherwise we would need to worry about the signs of $a, $b and so forth. -# For example if $b were negative, then after interpolation -# $a+$b might look like 3+-5. FEQ replaces the +- pair by -, which is what you want. - -# The first string (for $f) should be read as: "The function is calculated -# using sin($a+$b*cos(x)) -# and is defined for all x in the -# interval -$dom to +$dom. Draw the function using the first color -# in the permuted color list @scrambled_colors -# and using a weight (width) of two pixels." - -$f = FEQ( - "sin($a+$b*cos(x)) for x in <-$dom,$dom> using color:$scrambled_colors[0] and weight:2" -); -$fp = FEQ( - "cos($a+${b}*cos(x))*(-$b)*sin(x) for x in <-$dom,$dom> using color=$scrambled_colors[1] and weight:2" -); -# The multiplication signs are not actually needed, although they are allowed. - $fpp = FEQ("-sin($a+${b}*cos(x))*$b*$b* sin(x)* sin(x)+ cos($a+$b* cos(x))*(-$b)*cos(x) for x in <-$dom,$dom> using color=$scrambled_colors[2] and weight=2" -); - - - -# Install the functions into the graph object. -# Plot_functions converts the string to a subroutine which performs the -# necessary calculations and -# asks the graph object to plot the functions. - -($fRef,$fpRef,$fppRef) = plot_functions( $graph, - $f,$fp,$fpp - ); - -# The output of plot_functions is a list of pointers to functions which -# contain the appropriate data and methods. -# So $fpRef->rule points to the method which will calculate the value -# of the function. -# &{$fpRef->rule}(3) calculates the value of the function at 3. - -# create labels for each function -# The 'left' tag determines the justification of the label to the defining point. - - -$label_point=-0.75; -$label_f = new Label ( $label_point,&{$fRef->rule}($label_point), - $scrambled_labels[0], $scrambled_colors[0],'left'); - # NOTE: $fRef->ruleis a reference to the subroutine which calculates the - # function. It was defined in the output of plot_functions. - # It is used here to calculate the y value of the label corresponding - # to the function, and below to find the y values for the labels - # corresponding to the first and second derivatives. - -$label_fp = new Label ( $label_point,&{$fpRef->rule}($label_point), - $scrambled_labels[1],$scrambled_colors[1],'left'); -# Place the second letter in the permuted letter list at the point -# (-.75, fp(-.75)) using the second color in the permuted color list. - -$label_fpp = new Label ( $label_point,&{$fppRef->rule}($label_point), - $scrambled_labels[2],$scrambled_colors[2],'left'); - -# insert the labels into the graph -$graph->lb($label_f,$label_fp,$label_fpp); - -# make sure that the browser will fetch -# the new picture when it is created by changing the name of the -# graph each time the problem seed is changed. This helps prevent caching problems -# on browsers. - - $graph->gifName($graph->gifName()."-$newProblemSeed"); -# Begin writing the problem. -# This inserts the graph and then asks three questions: - -BEGIN_TEXT -\{ image(insertGraph($graph)) \} $PAR -Identify the graphs A (blue), B( red) and C (green) as the graphs -of a function and its -derivatives (click on the graph to see an enlarged image):$PAR -\{ans_rule(4)\} is the graph of the function $PAR -\{ans_rule(4)\} is the graph of the function's first derivative $PAR -\{ans_rule(4)\} is the graph of the function's second derivative $PAR -END_TEXT -ANS(str_cmp( [@scrambled_labels] ) ); - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/ontheflygraphicsexample2.pg b/courses.dist/modelCourse/templates/setMAAtutorial/ontheflygraphicsexample2.pg deleted file mode 100644 index 16144c5b56..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/ontheflygraphicsexample2.pg +++ /dev/null @@ -1,124 +0,0 @@ -DOCUMENT(); - -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", - "PGgraphmacros.pl" -); - -TEXT($BBOLD, "On-the-fly Graphics Example2", $EBOLD, $BR,$BR); - -# First we define a graph with x and y in the range -4 to 4, axes (strong lines) -# defined at the point [0,0] and -# with 8 gridlines horizontally and 8 grid lines veritically. -# $graph is a graph object (or more appropriately, a pointer to a graph object). - -# We will define a function and it's first and second derivatives -# defined on the domain [-4,4] - -$dom = 4; -$graph = init_graph(-$dom,-$dom,$dom,$dom,'axes'=>[0,0],'grid'=>[8,8]); - -# We need to scramble the colors and the labels -- otherwise every student -# will have the function is A, the derivative is B, etc. -# and the colors won't be scrambled either. - -#This provides a permutation of the numbers (0,1,2); - -@slice = NchooseK(3,3); - -# Here are the basic colors - -@colors = ("blue", "red", "green"); #orange, yellow, - -# This lists the colors in the order defined by the list in @slice -# It effectively applies the same permutation to the list of colors -# and to the list of labels. A will always be blue, B red, and C green -#and applies the same permutation to the list of labels - -@scrambled_colors = @colors[@slice]; -@labels = ('A', 'B', 'C'); -@scrambled_labels = @labels[@slice]; - -# The rest of this example is the same as ontheflygraphicsexample1 - -# function definitions need to be on one line -$a=random(0, 6.3, .1); -$b=random(1.1, 1.5, .1); - -# The three variables $f, $fp, and $fpp contain strings -# with the correct syntax to be inputs into the plot_function -# macro. The FEQ macro (Format EQuation) cleans up the writing of the function. -# Otherwise we would need to worry about the signs of $a, $b and so forth. -# For example if $b were negative, then after interpolation -# $a+$b might look like 3+-5. FEQ replaces the +- pair by -, which is what you want. - -# The first string (for $f) should be read as: "The function is calculated -# using sin($a+$b*cos(x)) -# and is defined for all x in the -# interval -$dom to +$dom. Draw the function using the first color -# in the permuted color list @scrambled_colors -# and using a weight (width) of two pixels." - -$f = FEQ("sin($a+$b*cos(x)) for x in <-$dom,$dom> using color:$scrambled_colors[0] and weight:2"); -$fp = FEQ("cos($a+${b}*cos(x))*(-$b)*sin(x) for x in <-$dom,$dom> using color=$scrambled_colors[1] and weight:2"); -$fpp = FEQ("-sin($a+${b}*cos(x))*$b*$b* sin(x)* sin(x)+ cos($a+$b* cos(x))*(-$b)*cos(x) for x in <-$dom,$dom> using color=$scrambled_colors[2] and weight=2"); - - -# Install the functions into the graph object. -# Plot_functions converts the string to a subroutine which performs the -# necessary calculations and -# asks the graph object to plot the functions. - -($fRef,$fpRef,$fppRef) = plot_functions( $graph, - $f,$fp,$fpp - ); -# The output of plot_functions is a list of pointers to functions which -# contain the appropriate data and methods. -# So $fpRef->rule points to the method which will calculate the value -# of the function. -# &{$fpRef->rule}(3) calculates the value of the function at 3. - -# create labels for each function -# The 'left' tag determines the justification of the label to the defining point. - - -$label_point=-0.75; -$label_f = new Label ( $label_point,&{$fRef->rule}($label_point), - $scrambled_labels[0],$scrambled_colors[0],'left') ; - # NOTE: $fRef->ruleis a reference to the subroutine which calculates the - # function. It was defined in the output of plot_functions. - # It is used here to calculate the y value of the label corresponding - # to the function, and below to find the y values for the labels - # corresponding to the first and second derivatives. - -$label_fp = new Label ( $label_point,&{$fpRef->rule}($label_point), - $scrambled_labels[1],$scrambled_colors[1],'left') ; -# Place the second letter in the permuted letter list at the point -# (-.75, fp(-.75)) using the second color in the permuted color list. - -$label_fpp = new Label ( $label_point,&{$fppRef->rule}($label_point),$scrambled_labels[2],$scrambled_colors[2],'left'); - -# insert the labels into the graph -$graph->lb($label_f,$label_fp,$label_fpp); - -# make sure that the browser will fetch -# the new picture when it is created by changing the name of the -# graph each time the problem seed is changed. -$graph->gifName($graph->gifName()."-$newProblemSeed"); - -# Begin writing the problem. -# This inserts the graph and then asks three questions: - -BEGIN_TEXT -\{ image(insertGraph($graph),width => 200, height => 200) \} $PAR -Identify the graphs A (blue), B( red) and C (green) as the graphs of a function and its -derivatives (click on the graph to see an enlarged image):$PAR -\{ans_rule(4)\} is the graph of the function $PAR -\{ans_rule(4)\} is the graph of the function's first derivative $PAR -\{ans_rule(4)\} is the graph of the function's second derivative $PAR -END_TEXT - -ANS(str_cmp( [@scrambled_labels] ) ); - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/paperHeader.pg b/courses.dist/modelCourse/templates/setMAAtutorial/paperHeader.pg deleted file mode 100644 index fd1a6af434..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/paperHeader.pg +++ /dev/null @@ -1,20 +0,0 @@ -## Paper set header for setSampleGraders - -DOCUMENT(); - -loadMacros( -"PG.pl", -"PGbasicmacros.pl", -"PGchoicemacros.pl", -"PGanswermacros.pl" -); - - -BEGIN_TEXT -This set shows how to write simple WeBWorK problems and introduces you to the most common -constructions. - -END_TEXT - - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/popuplistexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/popuplistexample.pg deleted file mode 100644 index b31fc8a5e5..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/popuplistexample.pg +++ /dev/null @@ -1,65 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", -); - -TEXT($BBOLD, "True False Pop-up Example", $EBOLD, $BR,$BR); -$showPartialCorrectAnswers = 0; - -# Make a new select list -$tf = new_select_list(); -# $tf now "contains" the select list object. - -# change the printing mechanism of the object to -# use pop-up list instead of an answer rule. -$tf->rf_print_q(~~&pop_up_list_print_q); - -# What should the pop-up list contain, and what string should it -# submit for an answer when selected? -# These are specified in the statment below. -# To enter T as an answer choose the list element "True" -# To enter F as an answer choose the list element "False" -# The first choice is a blank to make the students do SOMETHING!!! -$tf -> ra_pop_up_list( [ No_answer => "  ?", T => "True", F => "False"] ); -# Note how the list is constructed [ answer => list element text, answer => list element text ] - -# Insert some questions and their answers. - -$tf -> qa ( # each entry has to end with a comma -"All continuous functions are differentiable.", -"F", -"All differentiable functions are continuous.", -"T", -"All polynomials are differentiable.", -"T", -"All functions with positive derivatives are increasing.", -"T", -"All compact sets are closed", -"T", -"All closed sets are compact", -"F", -"All increasing functions have positive deriviatives", -"F", -"All differentiable strictly increasing functions have non-negative derivatives - at every point", -"T", -); - -# Choose two of the question and answer pairs at random. -$tf ->choose(4); # Using choose(3) would choose all three - # questions, but the order of the questions - # and answers would be scrambled. - -# Now print the text using $ml->print_q for the questions. -BEGIN_TEXT -$PAR -Indicate whether each statement is true or false. $BR -\{ $tf-> print_q \} -$PAR -END_TEXT -# Enter the correct answers to be checked against the answers to the students. -ANS( str_cmp( $tf->ra_correct_ans ) ) ; - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/prob3.pg b/courses.dist/modelCourse/templates/setMAAtutorial/prob3.pg deleted file mode 100644 index 01696bcc78..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/prob3.pg +++ /dev/null @@ -1,154 +0,0 @@ -# -# -#
-# Description
-# The first example using match lists
-# EndDescription
-
-
-DOCUMENT();        # This should be the first executable line in the problem.
-
-loadMacros("PGbasicmacros.pl",
-           "PGchoicemacros.pl",
-           "PGanswermacros.pl",
-           "PGgraphmacros.pl",
-           "PGnumericalmacros.pl"
-           );
-
-# TEXT( ... , ... , )
-# Is the simplest way of printing text, each string in the input is immediately printed.
-# It does not do any of the simplifying and evaluating tricks performed by the BEGIN_TEXT/END_TEXT construction.
-
-# Since this is a matching questions, we do not usually wish to tell students which
-# parts of the matching question have been answered correctly and which are
-# incorrect.  That is too easy.  To accomplish this we set the following flag to zero.
-$showPartialCorrectAnswers = 0;
-
-
-#####################################################################
-# This section allows you to manipulate the problem seed while working on the problem
-# thus seeing different versions of the problem. Skip the details of how this works
-# for now.
-
-# allow the student to change the seed for this problem.
-$newProblemSeed = ( defined( ${$inputs_ref}{'newProblemSeed'} ) )?  ${$inputs_ref}{'newProblemSeed'} : $problemSeed;
-$PG_random_generator->srand($newProblemSeed);
-
-BEGIN_TEXT     
-
-To see a different version of the problem change
-the problem seed and press the 'Submit Answer' button below.$PAR Problem Seed:
-\{  M3(
-qq! Change the problem seed to change the problem:$problemSeed!,
-qq! Change the problem seed to change the problem:
-    \begin{rawhtml}
-    
-    \end{rawhtml}!,
-qq! !
-)
-\}
-
-$HR  
-END_TEXT
-#####################################################################
-
-
-# Make a new match list
-$ml = new_match_list();
-
-# $ml now "contains" the match list object.  (Actually $ml is a scalar variable which contains a pointer to 
-# the match list object, but you can think of the match list object as being shoe horned into the variable $ml.
-# You need to remember that $ml contains (a pointer to) an object, and not ordinary data such as a number or string.
-
-# Some people use the convention $o_ml to remind them that the variable contains an object, but for short problems
-# that is probably not necessary.
-
-# An object contains both data (in this case the list of questions and answers) and subroutines (called methods)
-# for manipulating that data.
-
-
-# Insert some  questions and matching answers in the q/a list by calling on the objects qa method.
-# using the construction $ml ->qa(..list of alternating questions and matching answers ...).
-# Think of this as asking the object $ml to store the  matching questions 
-# and answers given in the argument to the method qa.
-
-$ml -> qa (
-"\( \sin(x) \)",        # Notice the use of the LateX construction for math mode: \\( ...  \\)
-"\( \cos(x) \)",		# and the use of TeX symbols such as \\sin and \\tan
-"\( \cos(x) \)",        # Use " ... " to enter a string
-"\( -\sin(x) \)",
-"\( \tan(x) \)",
-"\( \sec^2(x) \)"       # Remember that in these strings we are only specifying typography, 
- 						# via TeX, not any calculational rules.
-);
-
-#
-# Calculate coefficients for another question
-$b=random(2,5);
-$exp= random(2,5);
-$coeff=$b*$exp;
-$new_exp = $exp-1;
-
-# Store the question and answers in the match list object. 
-$ml -> qa (
-"\( ${b}x^$exp \)",
-"\( ${coeff}x^{$new_exp} \)",
-);
-
-# Add another example
-$b2=random(2,5);
-$exp2= random(2,5);
-$coeff2=$b2*$exp;
-$new_exp2 = $exp-1;
-$ml -> qa (
-"\( ${b2}x^$exp2 \)",
-"\( ${coeff2}x^{$new_exp2} \)",
-);
-
-
-# Choose two of the question and answer pairs at random.
-$ml ->choose(2);  # Using choose(3) would choose all three questions, but the order of the questions and answers would be 
-                  # scrambled.
-
-
-# Now print the text using $ml->print_q for the questions and $ml->print_a to print the answers.
-
-BEGIN_TEXT
-$PAR
-
-Match the functions and their derivatives: $BR
-
-\{ $ml -> print_q \}
-
-$PAR
-
-\{$ml -> print_a \}
-END_TEXT
-
-# Enter the correct answers to be checked against the answers to the students.
-
-ANS( str_cmp( $ml->ra_correct_ans )   ) ;
-
-# That's it.
-
-#########################################################  
-
-BEGIN_TEXT
-
- -You can view the -\{ htmlLink(alias("${htmlDirectory}/links/set$setNumber/prob3.html"),"source", q!TARGET="source"!)\} -for this problem. -END_TEXT - -TEXT( -"$PAR Return to ", htmlLink($$inputs_ref{returnPage},$$inputs_ref{returnPage}), -) if exists($$inputs_ref{returnPage}); -######################################################### - - - -ENDDOCUMENT(); # This should be the last executable line in the problem. -#
-# -# diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/prob4.pg b/courses.dist/modelCourse/templates/setMAAtutorial/prob4.pg deleted file mode 100644 index 084d6af438..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/prob4.pg +++ /dev/null @@ -1,94 +0,0 @@ -#
-#Description
-# Testing knowledge of differentiation rules
-#EndDescription
-
-DOCUMENT();        # This should be the first executable line in the problem.
-
-loadMacros("PGbasicmacros.pl",
-           "PGchoicemacros.pl",
-           "PGanswermacros.pl",
-           "PGgraphmacros.pl",
-           "PGnumericalmacros.pl"
-           );
- 
-$showPartialCorrectAnswers = 0;
-
-
-
-# allow the student to change the seed for this problem.
-$newProblemSeed = ( defined( ${$inputs_ref}{'newProblemSeed'} ) )?  ${$inputs_ref}{'newProblemSeed'} : $problemSeed;
-$PG_random_generator->srand($newProblemSeed);
-BEGIN_TEXT
-
-To see a different version of the problem change
-the problem seed and press the 'Submit Answer' button below.$PAR Problem Seed:
-\{  M3(
-qq! Change the problem seed to change the problem:$problemSeed!,
-qq! Change the problem seed to change the problem:
-    \begin{rawhtml}
-    
-    \end{rawhtml}!,
-qq! !
-)
-\}
-
-$HR  
-END_TEXT
-
-########################################################################
-# Make a new select list
-$ml = new_select_list();
-#$ml -> rf_print_q(~~&my_print_q);
-# New versions using the macros in PGchoicemacros.pl
-$ml->rf_print_q(~~&pop_up_list_print_q);
-$ml -> ra_pop_up_list([ No_answer => "  ?",SR => "Sum Rule",PR => "Product Rule",CR => "Chain rule",QR => "Quotient rule" ] );
-
-
-$ml -> qa (
-"\( (f(x) + g(x) )' = f'(x) + g'(x) \)",
-"SR",
-"\( ( f(x)g(x) )' = f'(x)g(x) + f(x)g'(x) \)",
-"PR",
-"\( ( f(g(x)) )' = f'(g(x))g'(x) \) ",
-"CR",
-"\( \frac{d}{dx} \sin(\cos(x)) = - \cos(\cos(x))\sin(x) \)",
-"CR",
-"\( (f(x) - g(x) )' = f'(x) - g'(x) \)",
-"SR",
-);
-
-$ml ->choose(5);
-
-#coda
-
-
-
-
-BEGIN_TEXT
- $PAR
-
-For each example below, list the label of the  differentiation rule used in that example: $BR
-
-\{ $ml -> print_q \}
-
-$PAR
-You can view the 
-\{ htmlLink(alias("${htmlDirectory}links/setDerivativeRules/prob2.html"), "source",q!TARGET="source"!) \}
-for this problem.
-or consult the 
-\{ htmlLink("/webwork_system_html/docs/techdescription/pglanguage/index.html","documentation") \}  for  more details on the PG language.
-
-END_TEXT
-
-install_problem_grader(~~&std_problem_grader);
-
-ANS( str_cmp( $ml->ra_correct_ans )   ) ;
-
-BEGIN_TEXT
-$PAR
-There are only a few examples in this problem.  A production verison
-would need more examples to choose from.
-END_TEXT
-ENDDOCUMENT();        # This should be the last executable line in the problem.
-#
diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/screenHeader.pg b/courses.dist/modelCourse/templates/setMAAtutorial/screenHeader.pg deleted file mode 100644 index bb7de4e6a7..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/screenHeader.pg +++ /dev/null @@ -1,21 +0,0 @@ -DOCUMENT(); -loadMacros("PG.pl", - "PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl" - ); - - -$usd = '$'; -BEGIN_TEXT - -This set shows how to write simple WeBWorK problems and introduces you to the most common -constructions. - - - -END_TEXT -TEXT("Return to ", htmlLink('http://cartan.math.rochester.edu/WeBWorKdiscussion/discuss/msgReader$14', -"discussion page"), "for more information or to make comments."); -ENDDOCUMENT(); - diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/simple_drawing.pg b/courses.dist/modelCourse/templates/setMAAtutorial/simple_drawing.pg deleted file mode 100644 index f4d86c4e7a..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/simple_drawing.pg +++ /dev/null @@ -1,40 +0,0 @@ -##DESCRIPTION -## A very simple drawing problem -##ENDDESCRIPTION - -##KEYWORDS('algebra') - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( -"PG.pl", -"PGbasicmacros.pl", -"PGchoicemacros.pl", -"PGanswermacros.pl", -"PGgraphmacros.pl", -"PGauxiliaryFunctions.pl" -); - - -$graph = init_graph(-5,-5,5,5,ticks=>[4,4],axes=>[0,0],pixels=>[400,400]); - -$graph->moveTo(-2,1); -$graph->lineTo(2,2,'blue'); -$graph->lineTo(-1,2,'red'); -$graph->lineTo(-2,1,'green'); -$graph->fillRegion([0,1.7,'yellow']); -BEGIN_TEXT -\{image(insertGraph($graph),width=>400,height=>400)\} - - -END_TEXT - -# At the moment there is no easy way to change the weight of the lines being drawn. To do so one would want -# to incorporate some of the code in Fun.pm into WWPlot.pm itself. The code involves gdBrushed. -# Since GD has -# gone through many revisions since the WWPlot.pm code was written it may now be possible to write some of -# this code more efficiently. - - - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/simplemultiplechoiceexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/simplemultiplechoiceexample.pg deleted file mode 100644 index 2f4650ec46..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/simplemultiplechoiceexample.pg +++ /dev/null @@ -1,33 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. -loadMacros( - "PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", -); -TEXT($BBOLD, "Multiple choice example", $EBOLD, $BR,$BR); - - -$showPartialCorrectAnswers = 0; -$question = "What is the derivative of tan(x)?"; -# An example of a list or array variable. It begins with @. -@answer_list = ( "\( \sec^2(x) \)", # correct - "\( -\cot(x) \)", - "\( \tan(x) \)", - "\( \cosh(x) \)", - "\( \sin(x) \)", -); -# These commands permute the order of the answers. -#@permutation = NchooseK(5,5); # random permutation of the five answers -@permutation = (1,0,2,3,4); # example of fixed permutation -@permuted_answer_list = @answer_list[@permutation]; -@inverted_alphabet = @ALPHABET[invert( @permutation )]; # needed to check the answers - -# Use the macro OL to print an Ordered List of the answerslabeled with letters. -BEGIN_TEXT -$BR $question -$PAR \{ OL( @permuted_answer_list ) \} -$PAR Enter the letter corresponding to the correct answer: \{ ans_rule(10) \} -END_TEXT -ANS( str_cmp( $inverted_alphabet[0] ) ) ; - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/standardexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/standardexample.pg deleted file mode 100644 index 15540480c2..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/standardexample.pg +++ /dev/null @@ -1,46 +0,0 @@ -DOCUMENT(); -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", - "PGauxiliaryFunctions.pl" -); - -TEXT($BBOLD, "Standard Example", $EBOLD, $BR,$BR); - -# A question requiring a string answer. -$str = 'world'; -#$str = "Dolly"; -BEGIN_TEXT -Complete the sentence: $BR -\{ ans_rule(20) \} $str; -$PAR -END_TEXT - -ANS( str_cmp( "Hello") ); - -# A question requiring a numerical answer. -#define the variables -$a = 3; -$b = 5; -#$a=random(1,9,1); -#$b=random(2,9,1); - -BEGIN_TEXT -Enter the sum of these two numbers: $BR - \($a + $b = \) \{ans_rule(10) \} -$PAR -END_TEXT - -$sum = $a + $b; -ANS( num_cmp( $sum ) ); - -# A question requiring an expression as an answwer -BEGIN_TEXT -Enter the derivative of \[ f(x) = x^{$b} \] $BR -\(f '(x) = \) \{ ans_rule(30) \} -$PAR -END_TEXT -$new_exponent = $b-1; -$ans2 = "$b*x^($new_exponent)"; -ANS( fun_cmp( $ans2 ) ); -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-24438.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-24438.gif deleted file mode 100644 index df1f85cbd2..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-24438.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-24438.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-24438.png deleted file mode 100644 index 580193f1ec..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-24438.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-31126.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-31126.gif deleted file mode 100644 index b0ad26bb6e..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-31126.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-31126.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-31126.png deleted file mode 100644 index 4a6286ab5c..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-31126.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-34859.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-34859.gif deleted file mode 100644 index 702784afdd..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-34859.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-34859.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-34859.png deleted file mode 100644 index 664c466d85..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-34859.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-42639.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-42639.gif deleted file mode 100644 index dfebffd690..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-42639.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-42639.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-42639.png deleted file mode 100644 index 1180e668cf..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-42639.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-76239.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-76239.gif deleted file mode 100644 index 741fdd8452..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-76239.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-76239.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-76239.png deleted file mode 100644 index ad13276793..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-76239.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-89540.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-89540.gif deleted file mode 100644 index aa9a75d45a..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-89540.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-89540.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-89540.png deleted file mode 100644 index 5ee4d64c9d..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-89540.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-91734.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-91734.gif deleted file mode 100644 index cd5aae4ebd..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-91734.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-91734.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-91734.png deleted file mode 100644 index 82a533be4b..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-91734.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-96355.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-96355.gif deleted file mode 100644 index 23ac97828b..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-96355.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-96355.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-96355.png deleted file mode 100644 index 53203cf0e7..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1-96355.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1.gif deleted file mode 100644 index 073055392b..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1.png deleted file mode 100644 index f8df18ec73..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/1.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-42653.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-42653.gif deleted file mode 100644 index 709cd644e2..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-42653.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-42653.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-42653.png deleted file mode 100644 index 11a642e8e2..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-42653.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-49261.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-49261.gif deleted file mode 100644 index 3767c0de47..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-49261.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-49261.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-49261.png deleted file mode 100644 index 273247474d..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-49261.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-54427.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-54427.gif deleted file mode 100644 index 11c71d8010..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-54427.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-54427.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-54427.png deleted file mode 100644 index 538e6f5a62..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-54427.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-62384.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-62384.gif deleted file mode 100644 index 538e6f5a62..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-62384.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-62384.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-62384.png deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-64591.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-64591.gif deleted file mode 100644 index 4bd408e32f..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-64591.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-64591.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-64591.png deleted file mode 100644 index 9d3cfa05c1..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-64591.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-68458.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-68458.png deleted file mode 100644 index 4f821ea915..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-68458.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-68458.pnm b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-68458.pnm deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-70190.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-70190.gif deleted file mode 100644 index 3e646a41ac..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-70190.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-70190.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-70190.png deleted file mode 100644 index cc82c39ef1..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-70190.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-81779.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-81779.gif deleted file mode 100644 index 944f3efcf6..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-81779.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-81779.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-81779.png deleted file mode 100644 index b72c870a2b..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-81779.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-92879.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-92879.gif deleted file mode 100644 index 11506db3bc..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-92879.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-92879.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-92879.png deleted file mode 100644 index febe0715b8..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2-92879.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2.gif deleted file mode 100644 index b85a6d3ad3..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2.png deleted file mode 100644 index 0a68a46210..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/2.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14197.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14197.gif deleted file mode 100644 index c28c1b68ee..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14197.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14197.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14197.png deleted file mode 100644 index 65fb892896..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14197.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14538.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14538.gif deleted file mode 100644 index c57d2fbe84..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14538.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14538.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14538.png deleted file mode 100644 index 99e6748686..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-14538.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-37616.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-37616.gif deleted file mode 100644 index a9c2db85c3..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-37616.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-37616.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-37616.png deleted file mode 100644 index 212e96cfe2..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-37616.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-46739.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-46739.gif deleted file mode 100644 index 4244a8ca61..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-46739.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-46739.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-46739.png deleted file mode 100644 index 43d18b86ec..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-46739.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-52898.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-52898.gif deleted file mode 100644 index afe8cd9d75..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-52898.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-52898.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-52898.png deleted file mode 100644 index 93a35d4e35..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-52898.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-68458.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-68458.gif deleted file mode 100644 index 8690563535..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-68458.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-68458.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-68458.png deleted file mode 100644 index 4f821ea915..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-68458.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-89262.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-89262.gif deleted file mode 100644 index 5c954beec1..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-89262.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-89262.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-89262.png deleted file mode 100644 index 37c3e8418e..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-89262.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-99389.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-99389.gif deleted file mode 100644 index f948cc3e83..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-99389.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-99389.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-99389.png deleted file mode 100644 index 51f7c7e1b3..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3-99389.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3.gif b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3.gif deleted file mode 100644 index 5f8f80e7d8..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3.png b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3.png deleted file mode 100644 index c16fb935e9..0000000000 Binary files a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/3.png and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/staticgraphicsexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/staticgraphicsexample.pg deleted file mode 100644 index 02ed678ce5..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/staticgraphicsexample.pg +++ /dev/null @@ -1,113 +0,0 @@ -DOCUMENT(); -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl" -); -TEXT($BBOLD, "Static graphics Example", $EBOLD, $BR,$BR); - -$showPartialCorrectAnswers = 0; -# Define which of the three sets of pictures to use - -# The pictures are labeled 1.png, 2.png and 3.png and -# stored in the same directory as staticgraphicsexample.png -# These are the corresponding transformed pictures. -# Be careful with the labeling, since the URL's could give the -# correct answers away. -# (In this example the middle integer tells you -# the correct position.) - -$pictID[1] = [ -"1-31126.png", # "\( F(x+3)\)", -"1-76239.png", # "\(F(x-3) \)" , -"1-96355.png", # "\( -F(-x)\)", -"1-24438.png", # "\( F(-x) \)", -"1-89540.png", # "\( 5F(x) \)", -"1-42639.png", # "\( F(3x) \)" , -"1-91734.png", # "\( F(x/3) \)", -"1-34859.png", # "\( F(x^2) \)", -]; -$pictID[2] = [ -"2-70190.png", # ditto -"2-49261.png", -"2-62384.png", -"2-54427.png", -"2-64591.png", -"2-42653.png", -"2-81779.png", -"2-92879.png", -]; -$pictID[3] = [ -"3-14197.png", -"3-89262.png", -"3-99389.png", -"3-68458.png", -"3-14538.png", -"3-37616.png", -"3-46739.png", -"3-52898.png", -]; -$ml = new_match_list(); - -$pictSet=random(1,3,1); # Choose one of the three picture sets -$pictSet=1; -$pictSetname = $pictSet.".png"; -$ml->qa ( -"\( F(x+3)\) ", -image($pictID[$pictSet][0],tex_size=>200), -"\(F(x-3) \)" , -image($pictID[$pictSet][1],tex_size=>200), -"\( -F(-x)\) ", -image($pictID[$pictSet][2],tex_size=>200), -"\( F(-x) \)", -image($pictID[$pictSet][3],tex_size=>200), -"\( 5F(x) \)", -image($pictID[$pictSet][4],tex_size=>200), -"\( F(3x) \)" , -image($pictID[$pictSet][5],tex_size=>200), -"\( F(x/3) \)", -image($pictID[$pictSet][6],tex_size=>200), -"\( F(x^2) \)", -image($pictID[$pictSet][7],tex_size=>200), -); - -$ml->choose(4); -sub format_graphs { - my $self = shift; - my @in = @_; - my $out = ""; - while(@in) { - $out .= shift(@in). "#" ; - } - $out; # The output has to be a string in order to conform to the - # specs for the match list object, but I've put some - # markers in (#) so that - # I can break the string up into a list for use - # as an input into row. -} - -# We need to change the output, since the normal -# output routine will put the pictures one above another. -$ml->rf_print_a(~~&format_graphs); - -BEGIN_TEXT -This is a graph of the function \( F(x) \): -($BBOLD Click on image for a larger view $EBOLD) -$PAR -\{ image($pictSetname, tex_size => 200) \} -$PAR -Enter the letter of the graph below which corresponds to the transformation -of the function. -\{ $ml -> print_q \} -END_TEXT - -# Place the output into a table -TEXT( - begintable(4), - row( split("#",$ml->print_a() ) ), - row('A', 'B', 'C', 'D' ), - endtable(), -); - -ANS( str_cmp( $ml ->ra_correct_ans() ) ) ; - -ENDDOCUMENT(); diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/tmp b/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/tmp deleted file mode 100644 index 3090a9405f..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/staticgraphicsexample/tmp +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/csh - -cat $1 | giftopnm | pnmtopng >$2 diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/truefalseexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/truefalseexample.pg deleted file mode 100644 index 42f6abe70b..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/truefalseexample.pg +++ /dev/null @@ -1,63 +0,0 @@ -DOCUMENT(); -loadMacros("PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", - -); -TEXT($BBOLD, "True False Example", $EBOLD, $BR,$BR); - -# Since this is a true questions, we do not usually wish to tell students which -# parts of the matching question have been answered correctly and which are -# incorrect. That is too easy. To accomplish this we set the following flag to -# zero. -$showPartialCorrectAnswers = 0; - -# True false questions are a special case of a "select list" -# Make a new select list -$tf = new_select_list(); -# $tf now "contains" the select list object. -# Insert some questions and whether or not they are true. - -$tf -> qa ( # each entry has to end with a comma -"All continuous functions are differentiable.", -"F", -"All differentiable functions are continuous.", -"T", -"All polynomials are differentiable.", -"T", -"All functions with positive derivatives are increasing.", -"T", -"All compact sets are closed", -"T", -"All closed sets are compact", -"F", -"All increasing functions have positive deriviatives", -"F", -"All differentiable strictly increasing functions have non-negative derivatives - at every point", -"T", -); - -# Choose four of the question and answer pairs at random. -$tf ->choose(4); - -# Now print the text using $ml->print_q for the questions -# and $ml->print_a to print the answers. - -BEGIN_TEXT -$PAR - -Enter T or F depending on whether the statement is true or false. -(You must enter T or F -- True and False will not work.)$BR - -\{ $tf-> print_q \} - -$PAR - -END_TEXT - -# Enter the correct answers to be checked against the answers to the students. - -ANS( str_cmp( $tf->ra_correct_ans ) ) ; - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setMAAtutorial/vectorfieldexample.pg b/courses.dist/modelCourse/templates/setMAAtutorial/vectorfieldexample.pg deleted file mode 100644 index 309c092ced..0000000000 --- a/courses.dist/modelCourse/templates/setMAAtutorial/vectorfieldexample.pg +++ /dev/null @@ -1,90 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros("PG.pl", - "PGbasicmacros.pl", - "PGchoicemacros.pl", - "PGanswermacros.pl", - "PGgraphmacros.pl", -); - -# Since this is a true questions, we do not usually wish to tell students which -# parts of the matching question have been answered correctly and which are -# incorrect. That is too easy. To accomplish this we set the following flag to zero. -$showPartialCorrectAnswers = 0; - - -# Make a new select list - -$tf = new_match_list(); - -$numberOfQuestions = 4; -$tf -> qa ( -"\(y'= 2y + x^2e^{2x} \)", -sub{my ($x,$y) = @_; 2*$y+($x**2)*exp(2*$x); }, -"\( y'= -2 + x - y \)", -sub{my ($x,$y) = @_; -2 + $x - $y;}, -"\(y'= e^{-x} + 2y\)", -sub{my ($x,$y) = @_; exp(-$x) + 2*$y;}, -"\(y'= 2\sin(x) + 1 + y\)", -sub{my ($x,$y) = @_; 2*sin($x) + 1 + $y;}, -"\(y'= -\frac{2x+y)}{(2y)} \)", -sub{my ($x,$y) = @_; ($y==0)? -2*$x/0.001 : -(2*$x+$y)/(2*$y);}, -"\(y'= y + 2\)", -sub{my ($x,$y) = @_; $y + 2 ;}, -); - -$tf ->choose($numberOfQuestions); -BEGIN_TEXT - -Match the following equations with their direction field. -Clicking on each picture will give you an -enlarged view. While you can probably solve this problem by guessing, -it is useful to try to predict characteristics of the direction field -and then match them to the picture. -$PAR -Here are some handy characteristics to start with -- -you will develop more as you practice. -$PAR - -\{OL( - "Set y equal to zero and look at how the derivative behaves along the x axis.", - "Do the same for the y axis by setting x equal to 0", - "Consider the curve in the plane defined by setting y'=0 - -- this should correspond to the points in the picture where the - slope is zero.", - "Setting y' equal to a constant other than zero gives the curve of points - where the slope is that - constant. These are called isoclines, and can be used to construct the - direction field picture by hand." -)\} - - - \{ $tf->print_q \} - -END_TEXT -$dx_rule = sub{my ($x,$y) = @_; 1; }; -$dy_rule = sub{my ($x,$y) = @_; $y; }; -# prepare graphs: -@dy_rules = @{ $tf->{selected_a} }; - -for my $i (0..$numberOfQuestions-1) { - $graph[$i] = init_graph(-4,-4,4,4,'axes'=>[0,0],'grid'=>[8,8]); - $vectorfield[$i] = new VectorField($dx_rule, $dy_rules[$i], $graph[$i]); - $vectorfield[$i]->dot_radius(2); - $graphURL[$i] = insertGraph($graph[$i]); -} -#### -BEGIN_TEXT -$PAR - \{ imageRow( [@graphURL[0..$numberOfQuestions/2-1]], - [@ALPHABET[0..$numberOfQuestions/2-1]], height => 200, - width => 200,tex_size=>300 ) \} - \{ imageRow( [@graphURL[$numberOfQuestions/2..$numberOfQuestions-1]], - [@ALPHABET[$numberOfQuestions/2..$numberOfQuestions-1]], - height => 200, width => 200,tex_size=>300 ) \} - -END_TEXT - -ANS( str_cmp( $tf->ra_correct_ans ) ) ; - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation.def b/courses.dist/modelCourse/templates/setOrientation.def deleted file mode 100644 index 50970d0cb5..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation.def +++ /dev/null @@ -1,149 +0,0 @@ -assignmentType = default -openDate = 06/26/2004 at 11:30am EDT -reducedScoringDate = 04/04/2015 at 12:20pm EDT -dueDate = 04/04/2015 at 12:20pm EDT -answerDate = 04/05/2015 at 12:00pm EDT -enableReducedScoring = N -paperHeaderFile = setOrientation/setHeader.pg -screenHeaderFile = setOrientation/setHeader.pg -description = -restrictProbProgression = 0 -emailInstructor = 0 - -problemListV2 -problem_start -problem_id = 1 -source_file = setOrientation/prob01.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 2 -source_file = setOrientation/prob02.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 3 -source_file = setOrientation/prob03.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 4 -source_file = setOrientation/prob04.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 5 -source_file = setOrientation/prob05/prob05.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 6 -source_file = setOrientation/prob06.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 7 -source_file = setOrientation/prob07.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 8 -source_file = setOrientation/prob08.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 9 -source_file = setOrientation/prob09.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 10 -source_file = setOrientation/prob10.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 11 -source_file = setOrientation/prob11.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 12 -source_file = setOrientation/prob12.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 13 -source_file = setOrientation/prob13.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 14 -source_file = setOrientation/prob14/prob14.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end -problem_start -problem_id = 15 -source_file = setOrientation/prob15.pg -value = 1 -max_attempts = -1 -showMeAnother = -1 -counts_parent_grade = 0 -att_to_open_children = 1 -problem_end - diff --git a/courses.dist/modelCourse/templates/setOrientation/course_info.txt b/courses.dist/modelCourse/templates/setOrientation/course_info.txt deleted file mode 100644 index e8a5db2e2b..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/course_info.txt +++ /dev/null @@ -1,21 +0,0 @@ -You have logged into WeBWorK! - -

- -If you haven't already done so, you should change your password to -something other than your student ID or whatever your instructor -used as your initial password. To do this, click on the -User Settings link in the sidebar navigation at the far left, -and follow the directions on that page. Note you can collapse and -uncollapse the sidebar navigation by clicking on the three bars "hamburger" -icon at the top left. So if you don't see the sidebar navigation, click -on the three bars to make it reappear. - -

- -Once you have changed your password, click on the "Orientation" link -in the white area at the left. This will take you to the the first -homework assignment, which will help you to learn how to use WeBWorK -effectively. - - diff --git a/courses.dist/modelCourse/templates/setOrientation/login_info.txt b/courses.dist/modelCourse/templates/setOrientation/login_info.txt deleted file mode 100644 index 3ff81bcd37..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/login_info.txt +++ /dev/null @@ -1,20 +0,0 @@ -Welcome to WeBWorK! - -

- -Your username is the same as the one used for your Union College -email, and your initial password is your Union College Student ID -number (you will change that after you log in). - -

- -Log in and follow the instructions that will appear in this panel once -you have done so. For the first assignment, do not log in as a -guest user or using a friend's account. If you do, you will not -receive credit for doing the asignment! - -

- -On future assignments, you can use the guest accounts to get -additional practice problems that are similar to the ones in your -homework set, but with different numbers. diff --git a/courses.dist/modelCourse/templates/setOrientation/options_info.txt b/courses.dist/modelCourse/templates/setOrientation/options_info.txt deleted file mode 100644 index f6aa8eadf3..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/options_info.txt +++ /dev/null @@ -1,31 +0,0 @@ -Change Your Password - -

- -If you haven't already changed your password, you should do so now. -To do this, type your OLD password in the top box at the left and -your NEW password in the two lower boxes at the left, -then press the "Change User Options" button. - -

- -Your password should be at least 6 characters long, and should include -something other than just letters. Don't make it something that is -easily guessed, and don't make it the same as the password for your -e-mail account! - -

- -If you prefer to receive your e-mail at a location other than your -Union College account, you should change your e-mail address in the box -at the left. Be aware, however, that your professor (and others on -the Union campus) may still send mail to your Union College address, -so you should check that account regularly anyway. Note that you can -have your Union college e-mail forwarded to another address -automatically if you wish. - -

- -Finally, when you have made the changes that you want to make, select -the "Homework Sets" link at the top of the red panel at the far left -to get back to the list of homework sets. diff --git a/courses.dist/modelCourse/templates/setOrientation/parserOrientation.pl b/courses.dist/modelCourse/templates/setOrientation/parserOrientation.pl deleted file mode 100644 index 1530e2ea32..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/parserOrientation.pl +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/local/bin/perl - -###################################################################### -# -# Macros used by the orientation problem set -# -###################################################################### - -#loadMacros("PGcourse.pl"); - -# -# Special use of CARET to have it work in non-math mode -# -$CARET = MODES( - TeX => '\hbox{\texttt{\char94}}', - Latex2HTML => '^', - HTML => '^' -); - -# -# Functions to display student input and computer output -# (written as functions so that we change the style without -# recoding the problems themselves). -# -sub student { - my $message = shift; - MODES( - TeX => '\leavevmode\hbox{\texttt{' . $message . '}}', - Latex2HTML => $bHTML . '' . $eHTML . $message . $bHTML . '' . $eHTML, - HTML => '' . $message . '' - ); -} - -sub computer { - my $message = shift; - MODES( - TeX => '\hbox{\texttt{' . $message . '}}', - Latex2HTML => $bHTML . '' . $eHTML . $message . $bHTML . '' . $eHTML, - HTML => '' . $message . '' - ); -} - -# -# This prints things we need to fill in yet in red -# -sub moreWork { - my $message = shift; - MODES( - TeX => '{\sl ' . $message . '}', - Latex2HTML => $bHTML . '' . $eHTML . $message . $bHTML . '' . $eHTML, - HTML => '' . $message . '' - ); -} - -# -# Temporary macros to mark comments in areas that we are -# working on. -# -$BCOMMENT = MODES( - TeX => '{\footnotesize\it', - Latex2HTML => $bHTML . '

' . $eHTML, - HTML => '
' -); - -$ECOMMENT = MODES( - TeX => '}', - Latex2HTML => $bHTML . '
' . $eHTML, - HTML => '
' -); - -# $BCOMMENT = MODES( -# TeX => '\iffalse', -# Latex2HTML => $bHTML.''.$eHTML, -# HTML => ' -->' -# ); - -# -# Hack to get better spacing in HTML_tth math mode but without -# messing up the spacing in other modes. -# -$SP = MODES( - TeX => ' ', - Latex2HTML => ' ', - HTML => ' ', - HTML_tth => '\ ', - HTML_jsMath => ' ', - HTML_dpng => ' ', -); - -# -# Special table macros for questions that have -# displayed math expressions equal to an answer rule, -# with an accompanying explanation on a separate line. -# - -sub BeginExamples { - return "" if ($displayMode eq "TeX"); - BeginTable(@_); -} - -sub EndExamples { - return "" if ($displayMode eq "TeX"); - EndTable(); -} - -@ExampleDefaults = (ans_rule_len => 40, ans_rule_height => 1); - -sub BeginExample { - my $math = shift; - my $ans = shift; - my %options = (@ExampleDefaults, @_); - my ($cols, $rows) = ($options{ans_rule_len}, $options{ans_rule_height}); - my $rule; - - if ($rows == 1) { $rule = ans_rule($cols) } - else { $rule = ans_box($rows, $cols) } - ANS($ans); - - # - # HTML_tth puts an unwanted
at the beginning, - # and uses a centered table. Remove the
and - # align the table to the right. - # - if ($displayMode eq "HTML_tth") { - $math = trimString(EV2('\[' . $math . '\]')); - $math =~ s!
!!; - $math =~ s!table align="center"!table align="right"!; - } elsif ($displayMode eq "HTML") { - $math = '\(' . $math . '\)'; - } elsif ($displayMode =~ m/^HTML/) { - $math = '\(\displaystyle ' . $math . '\)'; - } - - MODES( - TeX => "\n" . '\[' . $math . '=\hbox to 8em{' . $rule . '}\]', - Latex2HTML => $bHTML - . '' - . $eHTML - . '\(\displaystyle ' - . $math . '\)' - . $bHTML - . ' =  ' . '' - . $eHTML - . $rule - . $bHTML - . '' - . '' - . $eHTML, - HTML => '' - . $math - . ' =  ' . '' - . $rule - . '' - ); -} - -sub EndExample { - MODES( - TeX => "\n", - Latex2HTML => $bHTML . '

' . $eHTML, - HTML => '

' - ); -} - -sub ExampleRule { - MODES( - TeX => '\par', - Latex2HTML => $bHTML . '
' . $eHTML, - HTML => '
' - ); -} - -# -# Produce a TeX version and an answer checker for the formula -# -sub DisplayQA { my $f = shift; return (DMATH($f->TeX), $f->cmp) } -sub QA { my $f = shift; return ($f->TeX, $f->cmp) } - -################################################## -# -# Insert an image of an equation (but use the equation -# in TeX mode). -# - -sub MathIMG { - my ($img, $text, $tex) = @_; - my $useTeX = MODES(TeX => 1, Latex2HTML => 0, HTML => 0, HTML_tth => 0, HTML_dpng => 1); - return '\(' . $tex . '\)' if $useTeX; - $img = alias($img); - return qq{$text}; -} - -################################################## -# -# A simple grader that always returns a score of 1. -# This is used in the tutorial to give students -# credit for reading a problem (even if it doesn't -# ask any questions). -# -sub forgiving_grader { - my $rh_evaluated_answers = shift; - my $rh_problem_state = shift; - my %form_options = @_; - my %evaluated_answers = %{$rh_evaluated_answers}; - my %problem_state = %{$rh_problem_state}; - - my %problem_result = ( - score => 1, # always return 1 - errors => '', - type => 'forgiving_grader', - msg => '', - ); - - return (\%problem_result, \%problem_state) - if (!$form_options{answers_submitted}); - - $problem_state{recorded_score} = $problem_result{score}; - $problem_state{num_of_correct_ans}++; - - (\%problem_result, \%problem_state); -} - -################################################## -# -# Syntactic sugar to avoid ugly ~~& construct in PG. -# -sub install_forgiving_grader { install_problem_grader(\&forgiving_grader) } - -1; diff --git a/courses.dist/modelCourse/templates/setOrientation/prob01.pg b/courses.dist/modelCourse/templates/setOrientation/prob01.pg deleted file mode 100644 index f86f4bcff4..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob01.pg +++ /dev/null @@ -1,69 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "PGunion.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - - -Title("Understanding $WW Problem Pages"); - -############################################## -BEGIN_TEXT - -The $WW screen is divided into several areas, each used for a -different purpose. You will need to understand these -in order to use $WW effectively. -$PAR - -At the upper left are the navigation buttons that allow you to move -from problem to problem. The ${LQ}Next$RQ and ${LQ}Previous$RQ -buttons, naturally, send you to the next and previous problems. The -${LQ}Problem List$RQ button takes you back to the opening -page for the homework set (the one that lists all the problems and -gives the instructions for the homework set). -$PAR - -The area below the navigation buttons is where $WW tells you about -your score for the current problem. When you have submitted your -answers, this is where you will be given information about what -answers you got right and wrong. This -area also shows you how many points a problem is worth. -$PAR - -The main part of the page is the text of the problem you are trying to -answer, including blank boxes for you to enter your answers. (There -aren't any such boxes on this page, because you are not being asked -any questions here, but usually there will be one or more answer blanks -on a page.) -$PAR - -Below the problem text is a message area where you may be informed -about how partial credit is handled in multi-part problems. Other -information also may appear there, such as a message indicating that -the due date is passed, or that answers are available. -$PAR - -In the panel at the left, instead of the list of homework sets, you -now have a list of the problems within this assignment. You can go to -any problem just by clicking on it. There is also a progress bar which gives you a visual indication of your progress on the set and, following each problem number, an icon indicating if the problem has been answered corrrectly or still needs to be worked on to get full credit. -$PAR - -The buttons at the bottom of the screen, including the ${LQ}Submit -Answers$RQ button, are discussed in the next problem. At this point, -you can get credit for Problem 1 by pressing the ${LQ}Submit -Answers$RQ button at the bottom of the page (even though there was no -answer to submit), and then pressing the ${LQ}Next$RQ button at the -top of the screen to go on to the next problem. - -END_TEXT - - -install_forgiving_grader(); -$showPartialCorrectAnswers = 1; - -############################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob02.pg b/courses.dist/modelCourse/templates/setOrientation/prob02.pg deleted file mode 100644 index eb10283bed..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob02.pg +++ /dev/null @@ -1,96 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "PGunion.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - - -Title("Controlling $WW"); - -############################################## - -BEGIN_TEXT - -The buttons at the bottom of the screen are what cause $WW to process -your answers. Nothing that you type will have any effect until you -press one of these buttons. -$PAR - -The ${LQ}Submit Answers$RQ button causes $WW to check your answers and -report your score for the problem. Usually you can continue to work on a -problem until you get it right, so don't be afraid to submit your -answer even if you have only finished parts of the problem or are -not sure of the correctness of your answer. Sometimes (e.g. for -multiple choice or true/false type questions) your instructor -may limit the number of attempts allowed on a problem. At the bottom of the -page you will see how many attempts you have remaining. If the due date is -passed and the answers are available, you can click the ${LQ}Show -Correct Answers$RQ button before pressing ${LQ}Submit$RQ. -If you do, the correct answer(s) will be displayed in the answer area -at the top of the screen along with the answers you have provided. -$PAR - -If you have typed in a complicated answer, or are being told your -answer is incorrect when you think it's right, you may want to use the -${LQ}Preview My Answers$RQ button. This will ask $WW to display at the -top of the page its interpretation of what you have entered. This -can be used to help spot errors in your typing, and verify that $WW -understands your answer the way you intend it to. And it does not count -as an attempt on the problem. (This is discussed further in a later problem.) -$PAR - -The ${LQ}User Settings$RQ link under the sidebar navigation at the left -has a ${LQ}Change Display Options$RQ section that allows you to -change how the problem is displayed. The equations within the problem -can be represented in two different ways: - -\{BeginParList("UL")\} - -$ITEM -${LQ}images${RQ} mode produces accurate mathematical notation, -with the disadvantages of being slightly slower, and not printing well -if you want to print out a single problem. -$ITEMSEP - -$ITEM -${LQ}MathJax${RQ} mode is the default choice (what $WW uses unless you tell -it otherwise). It uses Cascading Style -Sheets (CSS) with web fonts or SVG, instead of bitmap images or Flash, so -equations scale with surrounding text at all zoom levels. MathJax is -compatible with screenreaders and provides zoom for everyone. - -\{EndParList("UL")\} -$PAR - -Choose whichever mode is most comfortable for you. You can always -select a different mode if a particular problem needs it. Here is a -sample of some simple mathematics, \(x^2 + 3\), and a more complicated -expression, \(\frac{x(1-x)}{2x + 1}\). Try changing the display mode -by clicking the ${LQ}User Settings$RQ link, selecting the ${LQ}images${RQ} -radio button, pressing the ${LQ}Change User Settings${RQ} button and then -displaying problem 2 again. Then change the display mode to back to -${LQ}MathJax$RQ mode for the rest of the homework set. -$PAR - -The ${LQ}Show saved answers${RQ} checkboxes tell whether you -want $WW to fill in the answer blanks with your previous answers or -not. (If you like, you can test this out on the next problem, since -there are no answer blanks in this one.) -$PAR - -You are now ready to learn how to enter answers into $WW. Press the -${LQ}Submit Answers${RQ} button to get credit for this problem, and -then press the ${LQ}Next$RQ button at the top of the page to go on to -the next one. - -END_TEXT - -install_forgiving_grader(); -$showPartialCorrectAnswers = 1; - -############################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob03.pg b/courses.dist/modelCourse/templates/setOrientation/prob03.pg deleted file mode 100644 index 8692f83f1c..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob03.pg +++ /dev/null @@ -1,140 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "MathObjects.pl", - "contextLimitedNumeric.pl", - "PGunion.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - - -$showPartialCorrectAnswers = 1; - -Title("Typing in Your Answers"); - -############################################## - -BEGIN_TEXT - -Here are the standard symbols that $WW, along with most other -computer software, uses for arithmetic operations: -$PAR - -\{ - BeginTable(). - Row([$BBOLD.'Symbol'.$EBOLD, - $BBOLD.'Meaning'.$EBOLD, - $BBOLD.'Example'.$EBOLD]). - TableLine(). - Row([computer("+"),'Addition',computer("3+4 = 7")],align=>"CENTER"). - Row([computer("-"),'Subtraction',computer("3-4 = -1")],align=>"CENTER"). - Row([computer("*"),'Multiplication',computer("3*4 = 12")],align=>"CENTER"). - Row([computer("/"),'Division',computer("3/4 = .75")],align=>"CENTER"). - Row([computer($CARET)." or ".computer("**"),'Exponentiation', - computer("3${CARET}4 = 81")." or ". - computer("3**4 = 81")],align=>"CENTER"). - TableLine(). - EndTable() -\} -$PAR - -END_TEXT - -################################################## - -$a = non_zero_random(-5,5,1); -$b = non_zero_random(-5,5,1); -$c = non_zero_random(-3,3,1) * 2; -$d = non_zero_random(-5,5,1); - -BEGIN_TEXT - -Sometimes $WW will insist that you calculate the value of an -expression as a single number before you enter it. For example, -calculate the value of \($c($a - $b) - ($c - $d)\) and enter it in -the following blank. -(Here you have to enter a single integer; the question is testing -whether you can do the operations correctly.) - -$PAR -$BBLOCKQUOTE -\($c($a - $b) - ($c - $d)\) = \{ans_rule(10)\} -$EBLOCKQUOTE -$PAR -END_TEXT - -Context("LimitedNumeric"); -$ans = $c*($a - $b) - ($c - $d); -ANS(Real($ans)->cmp); - -################################################## - -BEGIN_TEXT - -Most often you will not have to simplify your answer, but can let -$WW do this for you. The following blanks are all expecting -the value 16. Try entering it several different ways, such as -\{student "7+9"\}, \{student "18-2"\}, \{student "8*2"\}, -\{student "32/2"\}, and \{student "4${CARET}2"\}. Note: pressing -the ${LQ}Tab$RQ key on your keyboard will move you from one answer -box to the next. - -$PAR -$BBLOCKQUOTE -16 = \{ans_rule(8)\} or -\{ans_rule(8)\} or -\{ans_rule(8)\} or -\{ans_rule(8)\} or -\{ans_rule(8)\} -$EBLOCKQUOTE -$PAR - -END_TEXT - -Context("Numeric"); - -ANS( - Real(16)->cmp, - Real(16)->cmp, - Real(16)->cmp, - Real(16)->cmp, - Real(16)->cmp, -); - -################################################## - -BEGIN_TEXT - -$WW also understands that quantities written next to each other are -supposed to be multiplied. For example, you can enter \{student -"(9)(7)"\} instead of \{student "63"\}. Most often this is used when -one quantity is a number and the other a variable or function. For -instance, \{computer "2x"\} means \{computer "2*x"\}, while \{computer -"3sin(5x)"\} means \{computer "3*sin(5*x)"\}. The following blank is -expecting the value 100; try entering it as -\{student("4(30-5)")\}. - -$PAR -$BBLOCKQUOTE -100 = \{ans_rule(10)\} -$EBLOCKQUOTE -$PAR -END_TEXT - -ANS(Real(100)->cmp); - -################################################## - -BEGIN_TEXT - -${BITALIC}When you are ready, don't forget to press the ${LQ}Submit Answers${RQ} -button to ask $WW to check your work. Once you get the answers -correct, press ${LQ}Next${RQ} to go on.${EITALIC} - -END_TEXT - -################################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob04.pg b/courses.dist/modelCourse/templates/setOrientation/prob04.pg deleted file mode 100644 index 4f7e93577c..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob04.pg +++ /dev/null @@ -1,94 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "PGchoicemacros.pl", - "MathObjects.pl", - "PGunion.pl", - "alignedChoice.pl", - "contextLimitedNumeric.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - -$showPartialCorrectAnswers = 1; - -Title("Rules of Precedence"); - -############################################## - -BEGIN_TEXT - -The rules of precedence determine the order in which the mathematical -operations are performed by $WW. It is essential for you to understand -these so that you know how $WW interprets what you type in. If there -are no parentheses and no functions (such as \{computer "sin"\} or -\{computer "log"\}), then $WW computes the value of your answer by -performing exponentiation first, followed by multiplication and division -(from left to right), and finally addition and subtraction -(from left to right). -$PAR - -If there are expressions within parentheses, those expressions are -simplified first. We'll talk about functions (and give a more -complete list of rules) in a later problem. - -$PAR -Examples: -\{BeginList("UL")\} -$ITEM -\{student "4*3/6 = 12/6 = 2"\} (multiplications and divisions are done -from left to right), and \{student "2*7 = 14"\}, so -\{student "4*3/6-2*7+10 = 2 - 14 + 10 = -2"\}. -$ITEM -\{student "12/3/2 = 4/2 = 2"\} (multiplications and divisions are done -from left to right). -$ITEM -\{student "12/(3/2) = 12/1.5 = 8"\} -(expressions inside parentheses are calculated before anything else). -$ITEM -\{student "2*4${CARET}2 = 2*16 = 32"\} (exponentiation is done before multiplication), -so \{student "2*4${CARET}2 - 3*4 = 2*16 - 3*4 = 32 - 12 = 20"\}. -\{EndList("UL")\} -$PAR - -To practice these rules, completely simplify the following -expressions. Because the point of this problem is for you to do the -numerical calculations correctly, $WW will only accept sufficiently -accurate decimal numbers as the answers to these problems. -It will not simplify any expressions, including fractions. -$PAR - -END_TEXT - -$a = random(1,6,1); -$b = random(2,6,1); -$c = random(3,6,1); -$d = random(2,25,1); -$al = new_aligned_list(equals => 1); - -$al->qa( - computer("$a+$b*$c"), Real($a+($b*$c))->cmp, -# computer("($a+$b)*$c"), Real(($a+$b)*$c)->cmp, -# computer("($a+$b)/$c"), Real(($a+$b)/$c)->cmp, -# computer("$a+$b/$c"), Real($a+($b/$c))->cmp, - computer("$a/$b*$c"), Real(($a/$b)*$c)->cmp, -# computer("$a/($b*$c)"), Real($a/($b*$c))->cmp, -# computer("$a/$b/$c"), Real(($a/$b)/$c)->cmp, - computer("3*$b-$a/5*$c+$d"), Real((3*$b)-(($a/5)*$c)+$d)->cmp, - computer("2${CARET}$b+1"), Real((2**$b)+1)->cmp, - computer("2${CARET}($b+1)"), Real(2**($b+1))->cmp, -); - -BEGIN_TEXT -$BBLOCKQUOTE -\{$al->print_q\} -$EBLOCKQUOTE - -END_TEXT - -ANS($al->correct_ans); - -############################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob05/prob05-a.gif b/courses.dist/modelCourse/templates/setOrientation/prob05/prob05-a.gif deleted file mode 100644 index d32ee11b3b..0000000000 Binary files a/courses.dist/modelCourse/templates/setOrientation/prob05/prob05-a.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setOrientation/prob05/prob05-b.gif b/courses.dist/modelCourse/templates/setOrientation/prob05/prob05-b.gif deleted file mode 100644 index b074b10281..0000000000 Binary files a/courses.dist/modelCourse/templates/setOrientation/prob05/prob05-b.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setOrientation/prob05/prob05-c.gif b/courses.dist/modelCourse/templates/setOrientation/prob05/prob05-c.gif deleted file mode 100644 index 71062c6f06..0000000000 Binary files a/courses.dist/modelCourse/templates/setOrientation/prob05/prob05-c.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setOrientation/prob05/prob05.pg b/courses.dist/modelCourse/templates/setOrientation/prob05/prob05.pg deleted file mode 100644 index 7eaffa7d5d..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob05/prob05.pg +++ /dev/null @@ -1,129 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "PGchoicemacros.pl", - "MathObjects.pl", - "PGunion.pl", - "alignedChoice.pl", - "../parserOrientation.pl", - "PGcourse.pl", -); - -$showPartialCorrectAnswers = 1; - -Title("Common Errors to Avoid"); - -############################################## - -BEGIN_TEXT - -Many of the answers you enter into $WW will be expressions -that involve variables. Here are some important things to know. - -$PAR - -\{BeginParList("UL")\} - -$ITEM -It matters what letter you use. For example, if you are asked for a -function using the variable \(x\), then it won't work to enter the -function with the variable \(t\). Also, $WW considers upper- and -lower-case letters to be different, so don't use the capital letter -\{student "X"\} in place of the lower-case letter \{student "x"\}. -The following blank is expecting the -function \(x^3\), which you would enter as \{student "x${CARET}3"\} or -\{student "x**3"\}. Instead, try entering \{student "t${CARET}3"\} and -submitting your answer. - -$PAR -$BBLOCKQUOTE -\{ans_rule(10)\} -$EBLOCKQUOTE -$PAR - -You should get an error message informing you that \{computer "t"\} -is not defined in this context. This tells you that $WW did not receive the -correct variable and doesn't know how to check your answer. Now enter -\{student "x${CARET}3"\} and resubmit to get credit for this part of -the problem. - -END_TEXT - -ANS(Formula("x^3")->cmp); - -################################################## - -$IMGA = MathIMG("prob05-a.gif","1/x+1","1/x+1"); -$IMGB = MathIMG("prob05-b.gif","1/(x+1)","\frac{1}{x+1}"); -$IMGC = MathIMG("prob05-c.gif","(1/x)+1","\frac{1}{x} + 1"); - -BEGIN_TEXT - -$ITEM -$WW requires that you be precise in how you think about and present -your answer. We have just seen that you need to be careful about the -variables that you use. You must be equally careful about how the -rules of precedence apply to your answers. Often, this involves using -parentheses appropriately. - -$PAR - -For example, you might write $IMGA on your paper when you meant $IMGB, -but that is actually incorrect. The expression $IMGA means $IMGC, -according to the rules of precedence. $WW will force you to be exact -in what you are thinking and in what you are writing, because it must -interpret your answers according to the standard rules. If you want -to enter something that means $IMGB, you must write \{student -"1/(x+1)"\}. This also is true in written work, so making a habit of -being precise about this will improve your written mathematics as well -as your ability to enter answers quickly and correctly in $WW. - -$PAR -END_TEXT - -################################################## - -BEGIN_TEXT - -\{EndParList("UL")\} - -$PAR -$HR -$PAR - -Now enter the following functions: -$PAR -END_TEXT - -$al = new_aligned_list( - equals => 1, ans_rule_len => 30, - tex_spacing => "5pt", spacing => 10 -); - -Context("Numeric")->variables->are(t=>'Real'); $t = Formula("t"); -Context("Numeric")->variables->are(y=>'Real'); $y = Formula("y"); -Context("Numeric")->variables->are(x=>'Real'); $x = Formula("x"); - -$al->qa( - DisplayQA($t/(2*$t+6)), -# DisplayQA(2*$y*($y**2-$y+1)), -# DisplayQA(1/$x**2 - 3*(1/$x)), - DisplayQA(1/(2*($x-5))), - DisplayQA((2*$x-3)**4), -); - -BEGIN_TEXT - -$BBLOCKQUOTE -\{$al->print_q\} -$EBLOCKQUOTE - -END_TEXT - -ANS($al->correct_ans); - - -############################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob06.pg b/courses.dist/modelCourse/templates/setOrientation/prob06.pg deleted file mode 100644 index 14da12db6a..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob06.pg +++ /dev/null @@ -1,128 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "MathObjects.pl", - "PGunion.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - - -$showPartialCorrectAnswers = 1; - -Context("Numeric")->variables->are(y=>'Real'); $y = Formula('y'); -Context("Numeric")->variables->are(x=>'Real'); $x = Formula('x'); -Context()->flags->set(limits=>[0,2]); - -Title("Using Parentheses Effectively"); - -############################################## - -BEGIN_TEXT - -One of the hardest parts about using parentheses is making sure that -they match up correctly. Here are a couple of hints to help you with -this: - -$PAR -END_TEXT - -$BRACES = HTML('{}','\char123\char125'); - -BEGIN_TEXT - -\{BeginParList("UL")\} - -$ITEM -Several types of parentheses are allowed: \{student "()"\}, -\{student "[]"\}, and \{student $BRACES\}. When you need to nest -parentheses inside other parentheses, try using a different type for -each so that you can see more easily which ones match up. -$ITEMSEP - -$ITEM -When you type a left parenthesis, type the corresponding right -parenthesis at the same time, then position your cursor between them and -type the expression that goes inside. This can save you a -lot of time hunting for mismatched parentheses. -$ITEMSEP - -$ITEM -When you have a complicated answer, type a template for -the structure of your result first. For example, suppose that you are -planning to enter the fraction -\[\frac{2x^2-5}{(x+1)(3x^{3x} - 22)}.\] -A good way to start would be to type in \{student "()/[()*()]"\}. -This shows a template of one number divided by the product of two -other numbers. (Note that \{student "()/()*()"\} would not be a good -way to start; do you see why?) Now when you fill in the expressions, you -will be sure your parentheses balance correctly. -$PAR - -Although $WW understands that numbers written next to each other are -meant to be multiplied (so you do not have to use \{student "*"\} to -indicate multiplication if you don't want to), it is often useful for -you to include the \{student "*"\} anyway, as it helps you keep track -of the structure of your answer. -$PAR - -$ITEM -To see how $WW is interpreting what you type, enter your answer and -then click the ${LQ}Preview My Answers$RQ button, which is next to the -${LQ}Submit Answers$RQ button below. $WW will show you what it thinks -you entered (the preview appears in your answer area at the top of the -page). Previewing your answer does not count as an attempt on the problem and does not submit it for credit; that only -happens when you press the ${LQ}Submit Answers$RQ button. -$ITEMSEP - -$ITEM -When division or exponentiation are involved, it is a good idea to -use parentheses even in simple situations, rather than relying on the -order of operations. For example, 1/2x and (1/2)x both mean the same -thing (first divide 1 by 2, then multiply the result by x), but the -second makes it easier to see what is going on. Likewise, use -parentheses to clarify expressions involving exponentiation. Type -\{student "(e${CARET}x)${CARET}2"\} if you mean \((e^x)^2\), and type -\{student "e${CARET}(x${CARET}2)"\} if you mean \(e^{(x^2)}\). - -\{EndParList("UL")\} - -$PAR -$HR -$PAR - -Now enter the following functions: - -$BBLOCKQUOTE - -\{@ExampleDefaults = (ans_rule_len => 50, ans_rule_height => 1); - BeginExamples\} - -\{BeginExample(QA(($x**(2*$x-1))/(($x**2-$x)*(3*$x+5))))\} -Start with the template \{student "[x${CARET}()]/[()*()]"\}. -\{EndExample\} -\{ExampleRule\} - -\{BeginExample(QA((($y+3)*($y**3+$y+1))/((2*$y**2-2)*(5*$y+4))))\} -Start by putting in an appropriate template. This means that you -should begin by looking at the function and thinking about how many -pieces are used to construct it and how those pieces are related. -Once you have entered your answer, try using the ${LQ}Preview My Answers$RQ button -to see how $WW is interpreting your answer. -\{EndExample\} -\{ExampleRule\} - -\{BeginExample(QA((($x+1)/($x-2))**4))\} -Start by putting in an appropriate template. -\{EndExample\} - -\{EndExamples\} - -$EBLOCKQUOTE - -END_TEXT - -############################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob07.pg b/courses.dist/modelCourse/templates/setOrientation/prob07.pg deleted file mode 100644 index 31c041518b..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob07.pg +++ /dev/null @@ -1,146 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "PGchoicemacros.pl", - "MathObjects.pl", - "PGunion.pl", - "alignedChoice.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - - -$showPartialCorrectAnswers = 1; - -Title("Constants and Functions in $WW"); - -############################################## - -BEGIN_TEXT - -$WW knows the value of \(\pi\), which you can enter as \{student -"pi"\}, and the value of \(e\) (the base of the natural logarithm, -\(e\approx 2.71828\)), which you can enter simply as the letter -\{student "e"\}. -$PAR - -$WW also understands many standard functions. Here -is a partial list. Notice that all the function names start with a lower-case -letter. Capitalizing the function will lead to an error message. - -\{BeginParList("UL")\} - -$ITEM -$WW knows about \{student "sin(x)"\}, \{student "cos(x)"\}, \{student -"tan(x)"\}, \{student "arcsin(x)"\}, \{student "arccos(x)"\}, -\{student "arctan(x)"\} and the other trigonometric functions and their -inverses. $WW ${BITALIC}always$EITALIC uses radian mode for these -functions. -$PAR - -$WW will evaluate trigonometric functions for you in many situations. -For example, the following blank is expecting the value \(-1\). -Remember that \(\cos(\pi) = -1\), so enter \{student "cos(pi)"\} -and submit it. - -$PAR -$BBLOCKQUOTE -\{ans_rule(10)\} \(= -1\) -$EBLOCKQUOTE -$PAR - -END_TEXT - -ANS(Real(-1)->cmp); - -################################################## - -BEGIN_TEXT - -$ITEM -The square root \(\sqrt x\) is represented by the function \{student -"sqrt(x)"\} or by \{student "x${CARET}(1/2)"\}. -$ITEMSEP - -$ITEM -The function \{student "log(x)"\} means the ${BITALIC}natural$EITALIC -logarithm of \(x\) (the logarithm with base \(e\)), not the common -logarithm (the logarithm with base \(10\), sometimes written -\(\log_{10}\)). You can also write \{student "ln(x)"\} for the -natural logarithm of \(x\), so \{student "log(x)"\} and \{student "ln(x)"\} -mean the same thing. Use \{student "log10(x)"\} for the base 10 -logarithm of \(x\). Note that it is possible for your instructor to -change \{student "log(x)"\} to mean the common -logarithm (the logarithm with base \(10\)) but he or she should tell you if they do that. -$ITEMSEP - -$ITEM -The exponential function with base \(e\) can be entered as -\{student "e${CARET}x"\} or \{student "exp(x)"\}. The second notation -is convenient if you have a long, complicated exponent. -$ITEMSEP - -$ITEM -The absolute value function, \(|x|\), should be entered as -\{student "|x|"\} or \{student "abs(x)"\}. -$ITEMSEP - -$ITEM -The inverse sine function, \(\sin${CARET}{-1}(x)\), is written -\{student "arcsin(x)"\} or \{student "asin(x)"\} or \{student "sin${CARET}(-1)(x)"\} -in $WW. Note that this is ${BITALIC}not$EITALIC the same as -\{student "(sin(x))${CARET}(-1)"\}, which means \(\frac{1}{\sin(x)}\). -The other inverse functions are handled similarly. - -\{EndParList("UL")\} - -$PAR -$HR -$PAR - -Now enter the following functions: -$PAR -END_TEXT - -$al = new_aligned_list( - equals => 1, - ans_rule_len => 40, - tex_spacing => "5pt", - spacing => 10, -); - -Context("Numeric")->variables->are( - u => ['Real',limits=>[0.1,1.5]], - t => ['Real',limits=>[-1.9,-0.1]], - x => ['Real',limits=>[3.75,6]] -); -$u = Formula('u'); -$t = Formula('t'); -$x = Formula('x'); - -#Context("Numeric")->variables->are(y=>'Real'); $y = Formula('y'); -#Context("Numeric")->variables->are(x=>'Real'); $x = Formula('x'); -#Context()->flags->set(limits=>[-2,10]); - -$al->qa( -# DisplayQA(sqrt($y**2+1)), -# DisplayQA(sin(3*$x+1)), - DisplayQA(1/tan($u)), - DisplayQA(asin($t+1)), - DisplayQA((sin($x)-cos($x))/sqrt(2*$x-7)) -); - -BEGIN_TEXT - -$BBLOCKQUOTE -\{$al->print_q\} -$EBLOCKQUOTE - -END_TEXT - -ANS($al->correct_ans); - -############################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob08.pg b/courses.dist/modelCourse/templates/setOrientation/prob08.pg deleted file mode 100644 index a8f06aba30..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob08.pg +++ /dev/null @@ -1,157 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "MathObjects.pl", - "PGunion.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - - -Context("Numeric")->variables->are(y=>'Real'); $y = Formula('y'); -Context("Numeric")->variables->are(x=>'Real'); $x = Formula('x'); - -$showPartialCorrectAnswers = 1; - -Title("Rules of Precedence (Again)"); - -############################################## - -$Explanation = "${BITALIC}Explanation${EITALIC}"; -$Moral = "${BITALIC}Moral${EITALIC}"; - -BEGIN_TEXT - -At this point, we can give the complete rules of precedence for -how $WW computes the value of a mathematical formula. The operations -are handled in the following order: -$PAR - -\{BeginList\} -$ITEM Evaluate expressions within parentheses. -$ITEM Evaluate functions such as \{student "sin(x)"\}, -\{student "cos(x)"\}, \{student "log(x)"\}, \{student "sqrt(x)"\}. -$ITEM Perform exponentiation (from right to left). -$ITEM Perform multiplication and division, (from left to right). -$ITEM Perform addition and subtraction, (from left to right). -\{EndList\} -$PAR - -This can get a little subtle, so be careful. The following are some -typical traps for $WW users. -$PAR - -\{BeginParList("UL")\} - -$ITEM -$WW interprets \{student "sin 2x"\} to mean \((\sin${SP}2)*x\) -$PAR - -$Explanation: Rule 2 tells you that $WW does evaluation of functions -(like \{student "sin"\}) before multiplication. Thus $WW first -computes \(\sin${SP}2\), and then multiplies the result by \(x\). -$PAR - -$Moral: You must type \{student "sin(2x)"\} for the sine of \(2x\), -even though we often write it as \(\sin${SP}2x\). -Get in the habit of using parentheses for all your trigonometric -functions. -$PAR - -Now enter the following function: -$PAR -$BBLOCKQUOTE -The cosine of \(5x\) is entered as \{ans_rule(15)\}. -$EBLOCKQUOTE -$PAR - -END_TEXT - -ANS(cos(5*$x)->cmp); - -BEGIN_TEXT - -$ITEM -$WW interprets \{student "cos t${CARET}3"\} to mean \((\cos${SP}t)^3\) -$PAR - -$Explanation: Rule 2 tells you that $WW does evaluation of functions -(like \{student "cos"\}) before exponentiation. Thus $WW first -computes \(\cos${SP}t\) and then raises the result to the power 3. -$PAR - -$Moral: You must type in \{student "cos(t${CARET}3)"\} if you mean the -cosine of \(t^3\), even though we sometimes write it as \(\cos${SP}t^3\). -$PAR - -Now enter the following function: -$PAR -$BBLOCKQUOTE -The tangent of \(y^4\) is entered as \{ans_rule(15)\}. -$EBLOCKQUOTE -$PAR - -END_TEXT - -ANS(tan($y**4)->cmp); - -BEGIN_TEXT - -$ITEM -In mathematics, we often write \(\sin^2${SP}x\) to mean \((\sin x)^2\). -$WW will let you write \{student "sin${CARET}2(x)"\} for this, though -it is probably better to type \{student "(sin(x))${CARET}2"\} instead, -as this makes your intention clearer. Note that a power of \(-1\), as -in \{student "sin${CARET}(-1)(x)"\}, is a special case; it indicates the -${BITALIC}inverse${EITALIC} function \{student "arcsin(x)"\} rather -than a power. -$PAR - -Now enter the following function: -$PAR -$BBLOCKQUOTE -\(\sin^2${SP}x + \cos^3${SP}x\) = \{ans_rule(30)\} -$EBLOCKQUOTE -$PAR - -END_TEXT - -ANS((sin($x)**2 + cos($x)**3)->cmp); - -BEGIN_TEXT - -$ITEM -\{student "e${CARET}3x"\} means \((e^3) x\) and not \(e^{(3x)}\) $PAR -$PAR - -$Explanation: Rule 3 says that $WW does exponentiation before multiplication. -Thus $WW first computes \{student "e${CARET}3"\}, with the result -\(e^3\), and then multiplies the result by \(x\). -$PAR - -$Moral: Always put parentheses around an exponent. -Type \{student "e${CARET}(3x)"\} if you want \(e^{3x}\). -$PAR - -Now enter the following function: -$PAR -$BBLOCKQUOTE -\(2^{4x^3}\) = \{ans_rule(30)\} -$EBLOCKQUOTE -$PAR - -END_TEXT - -ANS((2**(4*($x**3)))->cmp); - -BEGIN_TEXT - -\{EndParList("UL")\} - - -END_TEXT - -############################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob09.pg b/courses.dist/modelCourse/templates/setOrientation/prob09.pg deleted file mode 100644 index 7d65426f03..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob09.pg +++ /dev/null @@ -1,112 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "MathObjects.pl", - "PGunion.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - -$showPartialCorrectAnswers = 1; - -Title("Non-Numeric Answers"); - -############################################## - -BEGIN_TEXT - -Sometimes you will be asked to enter answers that are not numbers. -For example, if you are asked to determine a limit, the answer may be -that the limit does not exist, so you might have to type \{student -"DNE"\} to indicate this (the problem should tell you what word to -use). Note that upper- and lower-case letters are not the same to -$WW, so you will need to enter the answer exactly as indicated in the -problem. (Well written problems will allow the answer to be -entered either way.) -$PAR - -$BBLOCKQUOTE -Please enter ${LQ}\{student "DNE"\}${RQ} here: \{ans_rule(10)\}. -$EBLOCKQUOTE - -END_TEXT - -ANS(String('DNE')->cmp); - -################################################## - -BEGIN_TEXT - -Other problems may require you to enter \(\infty\), which you do using -the word ${LQ}\{student "INFINITY"\}${RQ} (in upper- or lower-case) or -${LQ}\{student "INF"\}${RQ} for short. The problem should remind you -of how to do this. Note that most operations are not defined on -infinity, so you can't add or multiply something by infinity. You -can, however, indicate \(-\infty\) by ${LQ}\{student "-INFINITY"\}${RQ}, -or ${LQ}\{student "-INF"\}${RQ}. -$PAR - -$BBLOCKQUOTE -Try entering \(-\infty\) here: \{ans_rule(10)\}. -$EBLOCKQUOTE - -END_TEXT - -ANS((-(Infinity))->cmp); - -################################################## - -Context("Interval"); - -$a = random(-5,5,1); -$I = Compute("(-infinity,$a)"); - -BEGIN_TEXT - -One common place where you use \(\infty\) is as an endpoint of -an interval. $WW allows you to enter intervals using standard -interval notation, including infinite endpoints. For example, -\{student "[-2,5)"\} represents an interval that is closed on the -left and open on the right, while \{student "[2,inf)"\} is an interval -that extends infinitely to the right. -$PAR - -$BBLOCKQUOTE -Write the interval of points that are less than \($a\): \{ans_rule(20)\}. -$EBLOCKQUOTE - -END_TEXT - -ANS($I->cmp); - -################################################## - -Context("Interval"); - -$a = random(-8,-2,1); -$b = random($a+1,$a+5,1); -$c = random($b+1,$b+5,1); -$I = Compute("[$a,$b) U ($b,$c)"); - -BEGIN_TEXT - -Several intervals can be combined into one region using the ${LQ}set -union${RQ} operation, \(\cup\), which is represented as ${LQ}\{student -"U"\}${RQ} in $WW. For example, \{student "[-2,0] U (8,inf)"\} -represents the points from \(-2\) to \(0\) together with everything -bigger than 8. -$PAR - -$BBLOCKQUOTE -Write the set of points from \($a\) to \($c\) but excluding \($b\) and \($c\) -as a union of intervals: \{ans_rule(20)\}. -$EBLOCKQUOTE - -END_TEXT - -ANS($I->cmp); - -################################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob10.pg b/courses.dist/modelCourse/templates/setOrientation/prob10.pg deleted file mode 100644 index d0c0d6d24a..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob10.pg +++ /dev/null @@ -1,129 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "MathObjects.pl", - "PGunion.pl", - "parserVectorUtils.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - -$showPartialCorrectAnswers = 1; - -Title("Points and Vectors"); - -############################################## - -Context("Vector"); - -$p0 = non_zero_point2D(); -$p1 = $p0 + 2*non_zero_point2D(2,2,1); - -Context()->texStrings; -BEGIN_TEXT - -Some problems will ask you to enter an answer that is a point rather -than a number. You enter points in $WW just as you would expect: by -separating the coordinates by commas and enclosing them all in -parentheses. So \{student "(2,-3)"\} represents the point in the -plane that has an \(x\)-coordinate of \(2\) and \(y\)-coordinate of -\(-3\). -$PAR - -$BBLOCKQUOTE -What point is halfway between \($p0\) and \($p1\)? \{ans_rule(20)\}. -$EBLOCKQUOTE - -END_TEXT -Context()->normalStrings; - -ANS((($p0+$p1)/2)->cmp); - -################################################## - -$P = non_zero_point3D(); - -$LANGLE = HTML('<',"\char60 "); -$RANGLE = HTML('>',"\char62 "); - -Context()->flags->set(ijk=>1); -Context()->texStrings; -BEGIN_TEXT - -Other problems require you to provide a vector as your answer. $WW -allows you to enter vectors either as a list of coordinates enclosed -in angle braces, \{student $LANGLE\} and \{student $RANGLE\}, or as a -sum of multiples of the coordinate unit vectors, \(\{i\}\), \(\{j\}\) -and \(\{k\}\), which you enter as \{student "i"\}, \{student "j"\} and -\{student "k"\}. For example, \{student "${LANGLE}1,3,-2${RANGLE}"\} -represents the same vector as \{student "i+3j-2k"\}. -$PAR - -$BBLOCKQUOTE -What vector points from the origin to the point \($P\)? \{ans_rule(20)\}. -$EBLOCKQUOTE - -END_TEXT -Context()->normalStrings; -Context()->flags->set(ijk=>0); - -ANS(Vector($P)->cmp); - -################################################## - -$v0 = non_zero_vector3D(); -$v1 = non_zero_vector3D(); - -$SPACING = HTML('  '); -$BNOBR = HTML(''); -$ENOBR = HTML(''); - -Context()->texStrings; -BEGIN_TEXT - -Just as you can enter a number by giving an equation that reduces to it, -$WW allows you to enter points and vectors by giving equations for the -individual coordinates, or by using a vector-valued equation that -reduces to your answer. For example, -$PAR -$BCENTER -$BNOBR\{student "${LANGLE}1-(-3),2-sqrt(4),6/2${RANGLE}"\}$ENOBR -${SPACING} and ${SPACING} -$BNOBR\{student "[1-(-3)]i + [2-sqrt(4)]j + (6/2)k"\}$ENOBR -$ECENTER -$PAR -both represent the vector \(\{Vector(4,0,3)\}\), while -$BNOBR\{student "${LANGLE}1,0,-1${RANGLE} + ${LANGLE}2,-2,3${RANGLE}"\}$ENOBR -could be used to answer a question that asks for the vector \(\{Vector(3,-2,2)\}\). -$PAR - -$BBLOCKQUOTE -Write \(\{$v0+$v1\}\) as a sum of two vectors: \{ans_rule(30)\}. -$EBLOCKQUOTE - -END_TEXT -Context()->normalStrings; - -# -# Check that the result actually IS a sum (or difference). -# -sub checkAdd { - my $ans = shift; - if ($ans->{score} == 1 && !$ans->{isPreview}) { - my $item = $ans->{student_formula}->{tree}; - $ans->{correct_value}->cmp_Error - ($ans,"Your answer is not a sum of vectors") - unless $item->class eq 'BOP' && - ($item->{bop} eq '+' || $item->{bop} eq '-'); - } - return $ans; -} - -my $check = ($v0+$v1)->cmp; -$check->install_post_filter(~~&checkAdd); -ANS($check); - -################################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob11.pg b/courses.dist/modelCourse/templates/setOrientation/prob11.pg deleted file mode 100644 index 9703f9b6d1..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob11.pg +++ /dev/null @@ -1,69 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "MathObjects.pl", - "PGunion.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - -$showPartialCorrectAnswers = 1; - -Title("Multiple Answers in One Blank"); - -############################################## - -Context("Numeric"); - -$a = random(1,5,1); -$f = Formula("1/(x^2-$a^2)")->reduce; - -Context()->texStrings; -BEGIN_TEXT - -You may sometimes be asked to provide more than one answer in a single -answer blank. For example, you may need to enter all the values where -a function is not defined. In this case, you should separate your -answers by commas. Such an answer is called a -${BITALIC}list${EITALIC} in $WW. Note that you need not enter -multiple answers for a list; a single number is a legal answer (there -might only be one point where the function is undefined, for -instance). -$PAR - -$BBLOCKQUOTE -The function \(\displaystyle f(x)=$f\) is not defined at these \(x\) values: \{ans_rule(20)\}. -$EBLOCKQUOTE - -END_TEXT -Context()->normalStrings; - -ANS(List($a,-$a)->cmp); - -################################################## - -$a = random(1,5,1); -$f = Formula("1/(x^2+$a^2)")->reduce; - -Context()->texStrings; -BEGIN_TEXT - -When you are asked for a list of numbers, another possible answer is -that there are ${BITALIC}no${EITALIC} numbers that satisfy the -requirements. In that case, you should enter ${LQ}\{student -"NONE"\}${RQ} as your answer. -$PAR - -$BBLOCKQUOTE -The function \(\displaystyle f(x)=$f\) is not defined at these \(x\) values: \{ans_rule(20)\}. -$EBLOCKQUOTE - -END_TEXT -Context()->normalStrings; - -ANS(String('NONE')->cmp); - -################################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob12.pg b/courses.dist/modelCourse/templates/setOrientation/prob12.pg deleted file mode 100644 index 13c2574f1d..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob12.pg +++ /dev/null @@ -1,68 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "PGchoicemacros.pl", - "PGunion.pl", - "choiceUtils.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - -Title("True/False Questions in $WW"); - -############################################## - -$a = random(1,5,1); -$b = random(6,10,1); -$c = random(-10,-1,1); -$d = random(-10,-1,1); -$e = random(1,10,1); - -$sl = new_select_list(); -$sl->{rf_print_q} = ~~&alt_print_q; -$sl->{separation} = 5; - -$sl->qa( - "\(-$a $LT -$b\)", "F", - "\($c $LE $c\)", "T", - "\($d $LT $d\)", "F", - "\(\pi $GE 3.2\)", "F", - "\($e-1 $LE $e\)", "T" -); - -$sl->choose(4); - -################################################## - -BEGIN_TEXT - -Enter a \{student "T"\} or an \{student "F"\} in each -answer space below to indicate whether the corresponding -statement is true or false. -$PAR - -$BBLOCKQUOTE -\{$sl->print_q\} -$EBLOCKQUOTE -$PAR - -END_TEXT - -ANS(str_cmp($sl->ra_correct_ans)); -install_problem_grader(~~&std_problem_grader); -$showPartialCorrectAnswers = 0; - -BEGIN_TEXT - -In most multipart problems, if one or more of your answers is wrong, -then $WW tells you which ones they are. For True/False or -multiple-choice questions, however, $WW usually only tells you whether -${BITALIC}all$EITALIC the answers are correct. It won't tell you -which ones are right or wrong. - -END_TEXT - -################################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob13.pg b/courses.dist/modelCourse/templates/setOrientation/prob13.pg deleted file mode 100644 index 261adc2840..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob13.pg +++ /dev/null @@ -1,66 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "PGchoicemacros.pl", - "PGunion.pl", - "parserOrientation.pl", - "choiceUtils.pl", - "PGcourse.pl", -); - -Title("Matching Lists in $WW"); - -############################################## - -$a = random(-10,10,1); -$b = random(1,3,1); - -$ml = new_match_list(); -$ml->rf_print_q(~~&alt_print_q); -$ml->rf_print_a(~~&alt_print_a); -$ml->{separation} = 5; - -$ml->qa( - "\(x\) is less than \($a\)", "\(x $LT $a\)", - "\(x\) is any real number", "\(-\infty $LT x $LT \infty\)", - "\(x\) is greater than \($a\)", "\($a $LT x\)", - "\(x\) is less than or equal to \($a\)", "\(x $LE $a\)", - "\(x\) is greater than or equal to \($a\)", "\(x $GE $a\)", - "The distance from \(x\) to \($a\) is at most $b", - "\(|x - $a| $LE $b\)", - "The distance from \(x\) to \($a\) is more than $b", - "\(|x - $a| $GT $b\)" -); - -$ml->choose(5); - -################################################## - -BEGIN_TEXT - -Match the statements defined below with the letters labeling their -equivalent expressions. -$PAR - -\{ColumnMatchTable($ml,indent => 30)\} -$PAR - -END_TEXT - -ANS(str_cmp($ml->ra_correct_ans)); -install_problem_grader(~~&std_problem_grader); -$showPartialCorrectAnswers = 0; - -BEGIN_TEXT - -Usually with matching problems like this, -$WW only tells you whether ${BITALIC}all$EITALIC -your answers are correct or not. If they are not all -correct, $WW will not tell you which ones are right -and which are wrong. -$PAR - -END_TEXT - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob14/prob14-hint.html b/courses.dist/modelCourse/templates/setOrientation/prob14/prob14-hint.html deleted file mode 100644 index 9ad160d2da..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob14/prob14-hint.html +++ /dev/null @@ -1,35 +0,0 @@ - -WeBWork Set 0 Problem 11 Hint - - - -
- - -
- -
- -

Graphs in WeBWorK

- -Often in WeBWorK, the graphs are displayed as small thumbnail images. -These can be difficult to read, so in these cases, WeBWorK provides -you with a link to a larger copy of the graph. You can click on the -small version of the image to get the larger one. For example, click -on the diagram below to enlarge it. It will be displayed in a -separate window; close that window when you are done looking at the -larger graph. -

- -

- -
-

- -After you are done, press the "Back" button to go back to the problem page. - -

-
- - - diff --git a/courses.dist/modelCourse/templates/setOrientation/prob14/prob14.gif b/courses.dist/modelCourse/templates/setOrientation/prob14/prob14.gif deleted file mode 100644 index 16461e6334..0000000000 Binary files a/courses.dist/modelCourse/templates/setOrientation/prob14/prob14.gif and /dev/null differ diff --git a/courses.dist/modelCourse/templates/setOrientation/prob14/prob14.pg b/courses.dist/modelCourse/templates/setOrientation/prob14/prob14.pg deleted file mode 100644 index 41a89857e5..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob14/prob14.pg +++ /dev/null @@ -1,114 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "PGchoicemacros.pl", - "PGgraphmacros.pl", - "PGunion.pl", - "imageChoice.pl", - "../parserOrientation.pl", - "PGcourse.pl" -); - -# -# You need to change this to point to where you have stored the hint -# and graphic files. -# -$htmlWebworkURL = "http://omega.math.union.edu/webwork2_files/local"; -$hintURL = "${htmlWebworkURL}/parserOrientation/prob14-hint.html"; - -Title("Matching Graphs in $WW"); - -############################################## - -$ml = new_image_match_list(link => 0, border => 0); -$ml->{separation} = 3; - -@Goptions = (-6,-6,6,6, axes => [0,0], grid => [6,6], size => [150,150]); -$G1 = init_graph(@Goptions); -$G2 = init_graph(@Goptions); -$G3 = init_graph(@Goptions); -$G4 = init_graph(@Goptions); - -$a1 = random(-6,2,.1); $b1 = random($a1+1,6,.1); $m1 = ($b1-$a1)/12; -$a2 = random(-2,6,.1); $b2 = random($a2-1,-6,.1); $m2 = ($b2-$a2)/12; -$a3 = non_zero_random(.5,5,.1)*non_zero_random(-1,1,1); -$a4 = non_zero_random(.5,5,.1)*non_zero_random(-1,1,1); - -$plotoptions = "using color:red and weight=2"; -plot_functions($G1,"$m1(x+6)+$a1 for x in <-5.8,5.8> $plotoptions"); -plot_functions($G2,"$m2(x+6)+$a2 for x in <-5.8,5.8> $plotoptions"); -plot_functions($G3,"$a3 for x in <-5.8,5.8> $plotoptions"); -plot_functions($G4,"10000(x-$a4) for x in <-5.8,5.8> $plotoptions"); - -$ml->qa( - "The line is the graph of an increasing function", $G1, - "The line is the graph of a decreasing function", $G2, - "The line is the graph of a constant function", $G3, - "The line is not the graph of a function", $G4 -); - -$ml->choose(4); - -#BEGIN_TEXT -# -#The simplest functions are the ${BITALIC}linear$EITALIC ones --- -#the functions whose graphs are straight lines. They are important -#because many functions locally look like straight lines. (Looking -#like a line ${BITALIC}locally$EITALIC means that if we zoom in on the -#function and look at it at a very powerful magnification, it will look -#like a straight line.) -#$PAR - -BEGIN_TEXT - -Enter the letter of the graph that corresponds to each statement: -$PAR - -$BCENTER -$PAR -\{$ml->print_q\} -$PAR -$ECENTER - -\{$ml->print_a\} -$PAR - -END_TEXT - -ANS(str_cmp($ml->ra_correct_ans)); -install_problem_grader(~~&std_problem_grader); -$showPartialCorrectAnswers = 0; - -################################################## - -BEGIN_TEXT - -As with the previous matching problems, you will not be told which of -your answers are correct when you submit your answers to this problem. -$WW will only tell you if ${BITALIC}all${EITALIC} your answers are -correct or not. -$PAR - -Some $WW problems display a link to additional information or a -\{htmlLink($hintURL,"hint")\}. Follow this link for a hint about -graphs in $WW. -$PAR - -END_TEXT - -#Occasionally, a problem includes a hint that will not be available -#immediately. Once you have submitted incorrect answers a certain -#number of times (determined by the problem), you will see a ${LQ}Show -#Hint$RQ button above the submit buttons at the bottom of the screen. -#Check the box and press ${LQ}Submit$RQ in order to get the hint. For -#this problem, the hint will be available after one wrong answer. -# -#END_TEXT -# -#$showHint = 1; -#HINT("$HINT Usually the hints are more helpful than this."); - -################################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/prob15.pg b/courses.dist/modelCourse/templates/setOrientation/prob15.pg deleted file mode 100644 index b8d710bd55..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/prob15.pg +++ /dev/null @@ -1,88 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "PGunion.pl", - "parserOrientation.pl", - "PGcourse.pl", -); - - -Title("When You're Stuck..."); - -############################################## - -BEGIN_TEXT -The goal of the $WW software is to help you learn mathematics by giving -you immediate feedback on the correctness of your answer to a problem. -It is not designed to be a tutorial or to replace humans in -explaining the material to you. As with any learning tool, it is -up to you to make efficient and effective use of the software. - -Here are some things you can try when you are stuck on a problem. -$PAR - -\{BeginParList("UL")\} -$ITEM -Reread the problem carefully to see if there are any instructions -that you did not notice. -$ITEMSEP - -$ITEM -Check carefully for directions on the Problem List page. -You can get to this page by pressing the ${LQ}Problem List$RQ button at the -top of any problem page. -$ITEMSEP - -$ITEM -Look in the textbook for similar problems or relevant methods. -$ITEMSEP - -$ITEM -Talk to your instructor during office hours. -$ITEMSEP - -$ITEM -Ask a fellow student for help. -$ITEMSEP - -$ITEM -Use the Calculus Help Center. (Be sure to take a printout of -the problem with you. The tutors will need the ${BITALIC}exact$EITALIC -wording of the problem.) -$ITEMSEP - -$ITEM -Use the ${LQ}Email instructor$RQ button at the bottom of the problem page -to send e-mail to your instructor. Include in your message the details of -what you have tried so far. If you are having a software problem, -include details about the error messages you are getting. - -\{EndParList("UL")\} - -$PAR - -When you are truly stuck on a $WW problem, you should turn to other -sources (humans or books) for help, because it is not in your best -interest to guess repeatedly instead of thinking about what you might -be doing wrong. -$PAR - -To get credit for this problem, you must click the ${LQ}Submit -Answers$RQ button. Then you can use the ${LQ}Problem List$RQ button at -the top of the page to return to the problem list page. You will see -that the problems you have done have been labeled as correct or -incorrect, and you can go back and do problems you skipped or couldn't -get right the first time. Once you have done a problem correctly, it -is ${BITALIC}always$EITALIC listed as correct even if you go back and -do it incorrectly later. This means you can use WeBWorK to review -course material without any danger of changing your grade. $PAR - -END_TEXT - -install_forgiving_grader(); -$showPartialCorrectAnswers = 1; - -############################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/courses.dist/modelCourse/templates/setOrientation/setHeader.pg b/courses.dist/modelCourse/templates/setOrientation/setHeader.pg deleted file mode 100644 index ff9221ab30..0000000000 --- a/courses.dist/modelCourse/templates/setOrientation/setHeader.pg +++ /dev/null @@ -1,127 +0,0 @@ -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGstandard.pl", - "PGunion.pl", - "PGcourse.pl", -); - -$WW = "WeBWorK"; - -if ($displayMode eq 'TeX') { - -TEXT( - '\noindent{\large\bf '.$studentName.'}\hfill{\large\bf '.$course.'}', - '\par\noindent', -" -This set of $WW problems is designed to orient you to the -$WW system and to help you learn how to communicate with the -software. You will be learning about how to understand what -you see on the screen and about how to enter your answers when you do -the problems. You will practice entering numerical and functional -expressions and look at ways to find and correct errors in your -entries. - -", - "WeBWorK assignment $setNumber closes on $formattedDueDate.", -); - -} else { - -BEGIN_TEXT - -${BBOLD}Orientation to WeBWorK${EBOLD} -$PAR - -This set of $WW problems is designed to orient you to the $WW system -and to help you learn how to communicate with the software. You will -be learning about how to understand what you see on the screen and -about how to enter your answers when you do the problems. You will -practice entering numerical and functional expressions and look at -ways to find and correct errors in your entries. -$PAR - -Start by examining the features of this page. The panels at the -left and top help you to navigate to the different -pages available in WeBWorK. Note you can collapse and -uncollapse the sidebar navigation by clicking on the three bars -"hamburger" icon in the top left, so if you don't see the sidebar -menu, click the three bars to make it reappear. - -$PAR -On the left, in the sidebar navigation, you have already -seen the ${LQ}Homework Sets${RQ} page, -which lists all the homework assignments that have -been assigned to you. Links allow you to go to any assignment -you want to work on. -$PAR -The ${LQ}User Settings${RQ} page lets you change your password, -email address and display options. Display Options gives you control -over how you want math equations displayed. For example with -${LQ}MathJax${RQ} you can control the size of equations, have access -to accessibility options, etc. The ${LQ}Grades${RQ} page -shows your scores on the various assignments (but there is nothing -much to show at this point). -$PAR - -Links to your homework assignments are also listed in the ${LQ}Sets${RQ} -panel so you can quickly switch to another assignment. -$PAR - -\{ -#The ${LQ}Report Bugs${RQ} button is for reporting bugs in the WeBWorK -#System itself to the developers WeBWorK. It is unlikely that -#you will need to use that yourself. If you are having difficulty with -#WeBWorK, you should contact your professor using the ${LQ}Email -#Instructor${RQ} button instead. This should be available on nearly -#every page, so you always have a quick way to reach your professor. -#$PAR -\} - -The data in the panel at the top of the page tells you where you -are in WeBWorK's hierarchy of pages, but you can ignore that for the -most part. There is an additional logout button at the far right, -however, that you will need to use when you are done using $WW (don't -press it yet). -$PAR - -\{ -#The yellow question mark icon is a ${LQ}Help${RQ} -#button for system documentation, but there is not much of that -#available at the moment. -#$PAR -\} - -The main information on this page is located in the large panel -to the left. It shows the status of the problems in this assignment, -and your score on the set so far. Since you haven't yet tried any of -the problems, your score is zero, but after you work on some of them, -the number of attempts and the score will reflect the work you have -done. -$PAR -Near the bottom is a link that allows you to download a pdf ${LQ}hardcopy${RQ} -version of your -whole assignment which you can print out. This allows you to work on -the assignment without having to be connected to $WW. -$PAR -Also near the bottom of this and every $WW page there is an -${LQ}Email instructor${RQ} link that you can use to send email to -your instructor. That email, in addition to what ever you write, will -contain information on whatever page you are on so, e.g. if you are -working on a problem, your instructor will know what problem it is. - -$PAR -Each problem number is a link to that problem in the homework set. -You can click on one to get to any specific problem. To begin the -orientation assignment, click on the link for Problem 1. When you are -done working, be sure to dismiss your connection to the server by -clicking on ${LQ}Log Out${RQ}, so that no one else can gain access to -your account. -$PAR - - -END_TEXT - -} - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/devel/DBglue.notes b/doc/devel/DBglue.notes deleted file mode 100644 index 3402a13a8d..0000000000 --- a/doc/devel/DBglue.notes +++ /dev/null @@ -1,75 +0,0 @@ -############## -# webwork_DB # -############## - -PSVN => - => - - (Generated during problem set build process:) - - stlg StudentLogin the login name of the student who owns this PSVN - stnm SetNumber the "number" (name) of the problem set associated with this PSVN - pse# ProblemSeed(#) the problem seed, a random integer between 0 and 5000 - - (Taken from set definition file:) - - shfn SetHeaderFileName the file name of the set header file (shown when selecting problem?) - phfn ProbHeaderFileName the file name of the problem header file (shown when viewing a problem) - opdt OpenDate the date that the problem set "opens" - dudt DueDate the date after which no credit can be recieved - andt AnswerDate the date when answers can be shown - pfn# ProblemFileName(#) the file name of the problem - pva# ProblemValue(#) the number of points that the problem is worth - pmia# ProblemMaxNumOfIncorrectAttempts(#) the number of times a student is allowed to answer incorrectly - - (Added when student works on problem set:) - - pst# ProblemStatus(#) the "correctness" of the problem: [0,1] - pat# ProblemAttempted(#) boolean, whether the problem has been attempted (answer has been submitted) - pan# ProblemStudentAnswer(#) the student's last answer (attempt) for a problem - pca# ProblemNumOfCorrectAns(#) number of correct answers (there can be more than one ANS per problem) - pia# ProblemNumOfIncorrectAns(#) number of incorrect answers - -LOGIN => - SetNumber => PSVN - ... - -SET_NUMBER => - StudentLogin => PSVN - ... - -################ -# classlist_DB # -################ - -LOGIN => - stln StudentLastName - stfn StudentFirstName - stea StudentEmailAddress - stid StudentID - stst StudentStatus - clsn ClassSection - clrc ClassRecitation - comt Comment - -######## -# keys # -######## - -LOGIN => - KEY the last key associated with the user - TIMESTAMP the time that this key was last used - -############### -# password_DB # -############### - -LOGIN => - PASSWORD the password set for this user - -################## -# permissions_DB # -################## - -LOGIN => - PERMISSIONS an integer representing the permissions allowed to the user (i.e. 10=prof, 5=ta) diff --git a/doc/devel/URL-notes b/doc/devel/URL-notes deleted file mode 100644 index 61c5cc672a..0000000000 --- a/doc/devel/URL-notes +++ /dev/null @@ -1,92 +0,0 @@ -THE CURRENT STRATEGY - -The URL strategy we've been basically using is to put the nouns in the path info and the verbs in the query string. The heirarchy of the path info mirrors the heirarchy of objects in the system. It works like this in the Problem.pm content generator (I think): - - /webwork/$courseID/$setID/$problemID?submitAnswer=Submit+Answer - -WeBWorK contains courses, courses contain sets, sets contain problems. This is reflected in the path info. The presence of a parameter named submitAnswer in the query string causes Problem.pm to record the answers given later in the query string. The set and problem to which the answers are recorded is given in the path info. Each content generator has a default action that is performed if no verbs are given in the query string. These default actions are non-destructive, i.e. "view" or "list". - -ADVANTAGES - -This scheme has the advantage of allowing a user (usually a professor) to easily reference a "location" within the system, without needing to worry about the relativly complicated syntax of the query string. For example, a professor could, after making a change to a problem, instruct students via email to "visit http://courses.webwork.rochester.edu/mth161/4/7/ to view the updated problem". This is certainly more manageable than a scheme in which the verb is in the path info and the nouns are in the query string (as in "http://courses.webwork.edu/viewProblem?course=mth161&set=4&problem=7"). - -It also allows us to, as we strive to reduce the amount of state that is passed through each request, pare the URL down so that very little query string remains. For example, the session key could be kept in a cookie, and the rest of the session data could be kept in the key table of the database. The only thing that would be left in the query string would be the verb and (I suppose) various "adverbs" and "adjectives". - -PROBLEMS - -Currently the dispatch() function uses only the path info to decide which content generator to invoke. This means that unless we wish to have the problem viewer and the problem editor be implemented in the same content generator, it would be impossible to have these two URIs: - - /webwork/$courseID/$setID/$problemID/?action=view - /webwork/$courseID/$setID/$problemID/?action=edit - -This, I think, can be solved by allowing the dispatch() function to inspect the query string in a limited fashion. If it were allowed to check for an "action" parameter, and a standard set of actions were defined, the combination of an action (in the query string) and a type of object (in the path info) could be used to select a content generator. - -A table would be needed to noun/action pairs to content generators and vice versa. For example: - - Problem + view <=> WeBWorK::ContentGenerator::Problem - Problem + edit <=> WeBWorK::ContentGenerator::Instructor::PGProblemEditor - -However, there are some tricky implementation problems with this approach. The value of a parameter triggered by a submit button (generated with the INPUT element) is the same as the text that is displayed on the button. This puts the desire for good UI at odds with having easily manageable action identifiers. We'd like to have a button named "Download Hardcopy for Selected Sets", but have the action identifier be "makeHardcopy" or something. The BUTTON element promises to solve this, but browser support is very broken for both the presentation and behavior of this element. - -Another problem with this scheme is that sometimes the nouns needed are not known when the form action is decided at page generation time. For example, the URI for creating a new set might be something like: - - /webwork/$courseID/instructor/sets/$setID?action=create - -The set name ($setID) would have be supplied by a text field on the form, and therefore would not be known at the time that the form was generated. One solution to this would be to name the "slots" in the path info, and have the dispatcher substitute in values from the query string before handing the request off to the content generator. A redirect to the URI with the values substituted in could fix the user experience somewhat. For example, the dispatcher could convert the URI: - - /webwork/mth161/instructor/sets/?action=create&setID=newIntegrals - -to: - - /webwork/mth161/instructor/sets/newIntegrals/?action=create - -In the example, the value of the parameter named "setID" was placed in the "setID" slot in the path info, and the parameter was deleted. - --------------------------------------------------------------------------------- - -(+) = list - -THE CURRENT URI HEIRARCHY - -$webworkURIRoot - $courseID - test - $setID - $problemID - hardcopy - $setID - login - logout - options - feedback - instructor - [some crap] - -PROPOSED URI HEIRARCHY - -$webworkURIRoot - $courseID -- lists sets - test -- prints debugging information - $setID -- lists problems in a set - $problemID -- displays problem (interactive mode) - hardcopy -- prompts user for which sets/users to generate - $setID (+) -- generates hardcopy for specified sets - login -- prompts for login information - logout -- deletes session information, displays logout message - options -- allows user to change email address and password - feedback -- allows user to send feedback to professor - instructor -- gives user a choice between user list and set list - sets -- lists/edits (global) sets - $setID -- displays/edits (global) set data - problems -- lists (global) problems for a set - $problemID -- displays/edits (global) set data - users -- lists/edits assigned users - $userID (+) -- displays/edits user-specific set data - problems -- lists/edits assigned problems - $problemID -- displays/edits user-specific problem data - users -- lists/edits users - $userID -- displays/edits user data - sets -- lists/edits assigned sets - $setID -- displays/edits user-specific set data - problems -- lists/edits assigned problems - $problemID -- displays/edits user-specific problem data diff --git a/doc/devel/cg-refactor-notes b/doc/devel/cg-refactor-notes deleted file mode 100644 index b9e0f4e35c..0000000000 --- a/doc/devel/cg-refactor-notes +++ /dev/null @@ -1,300 +0,0 @@ --------------------------------------------------------------------------------- -currently, ContentGenerators do several things: --------------------------------------------------------------------------------- - -display lists of objects and object details ("displays") -prompt for list filtering ("display filters") -prompt for creating, editing, and deleting objects ("action triggers") -create, edit, and delete objects ("actions") - --------------------------------------------------------------------------------- -goals: --------------------------------------------------------------------------------- - -have a chunk of code that takes care of each "action" -avoid duplication of code -preserve and "purify" noun-based URL path-info -have the noun-based path refer to the object or list being viewed - --------------------------------------------------------------------------------- -changes to request format: --------------------------------------------------------------------------------- - -Define some special parameters that are recognized by the dispatcher: - - "u" specifies the current user (i.e. the actual human using the system) - "p" specifies the password of the current user - "k" specifies the session key of the current user - "a" specifies an action to perform (like "a=deleteUser") - "o" specifies objects on which to perform an action (like "o=sh002i&o=dl001i") - -We should reserve all single-letter parameter names. -(UPDATE: I changed my mind. Single-letter param names are unneccesarily -obfuscated and annnoying.) - -We can't use - -An action trigger that would be put onto a user detail page: - - - --------------------------------------------------------------------------------- -development plan --------------------------------------------------------------------------------- - -1. write a "new" dispatcher -2. code display modules using display code from existing content generators -3. add actions and action triggers as needed, using code from existing content generators -4. ? -5. profit diff --git a/doc/devel/converted-cgs b/doc/devel/converted-cgs deleted file mode 100644 index 89d98aebcf..0000000000 --- a/doc/devel/converted-cgs +++ /dev/null @@ -1,81 +0,0 @@ -4 => 3 + GENERATE PATHS WITH URLPath INSTEAD OF HARD CODING -(this has to be done before we can change the virtual heirarchy.) - lib/WeBWorK/ContentGenerator.pm - lib/WeBWorK/ContentGenerator/EquationDisplay.pm - lib/WeBWorK/ContentGenerator/Error.pm - lib/WeBWorK/ContentGenerator/Feedback.pm - lib/WeBWorK/ContentGenerator/Grades.pm - lib/WeBWorK/ContentGenerator/Home.pm - lib/WeBWorK/ContentGenerator/Instructor.pm - lib/WeBWorK/ContentGenerator/Instructor/AddUsers.pm - lib/WeBWorK/ContentGenerator/Instructor/Assigner.pm - lib/WeBWorK/ContentGenerator/Instructor/FileXfer.pm - lib/WeBWorK/ContentGenerator/Instructor/Index.pm - lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm - lib/WeBWorK/ContentGenerator/Login.pm - lib/WeBWorK/ContentGenerator/Logout.pm - lib/WeBWorK/ContentGenerator/Options.pm - lib/WeBWorK/ContentGenerator/Problem.pm - lib/WeBWorK/ContentGenerator/ProblemSet.pm - lib/WeBWorK/ContentGenerator/ProblemSets.pm - - lib/WeBWorK/ContentGenerator/Instructor/ProblemList.pm - lib/WeBWorK/ContentGenerator/Instructor/ProblemSetEditor.pm - lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm - lib/WeBWorK/ContentGenerator/Instructor/Scoring.pm - lib/WeBWorK/ContentGenerator/Instructor/ScoringDownload.pm - lib/WeBWorK/ContentGenerator/Instructor/SendMail.pm - lib/WeBWorK/ContentGenerator/Instructor/SetsAssignedToUser.pm - lib/WeBWorK/ContentGenerator/Instructor/ShowAnswers.pm - lib/WeBWorK/ContentGenerator/Instructor/Stats.pm - lib/WeBWorK/ContentGenerator/Instructor/UserList.pm - -3 => 2 + GET PATH DATA FROM URLPath INSTEAD OF FROM $self->r -(this has to be done before we can take advantage of path/param munging.) - lib/WeBWorK/ContentGenerator/GatewayQuiz.pm - - delaying path generation changes until major cleanup - lib/WeBWorK/ContentGenerator/Hardcopy.pm - - delaying path generation changes until major cleanup - - - - - - - - - - - - lib/WeBWorK/ContentGenerator/Instructor/UsersAssignedToSet.pm - -2 => 1 + GET $ce, $db, $authz FROM $self->r INSTEAD OF FROM $self -(this has to be done before we can remove ce/db/authz from $self.) - - -1 => REMOVE DEPENDANCY ON DATA FROM @_ (get from URLPath instead) -(this has to be done before things will work.) - - - - - -0 => NONE OF THE ABOVE DONE - ------ Code that needs cleaning ----- - -- lots of code needs to be factored out of Problem and GatewayQuiz: - - problem logic (recording answers, checking permissions, etc.) - - display idioms (attemptResults, etc.) -- Hardcopy: - - move actual PDF generation into a Utils::* module - - clean up error handling -- code can be factored out of Grades, Stats, and SendMail - - a widget for displaying the "student progress" chart - - util code for doing mail merge from scoring files (whatever it does) -- Instructor needs work -- there's a lot of cut-n-paste going on -- factor info box formatting code out of Login, ProblemSets, ProblemSet - and into WeBWorK::HTML::InfoBox -- some modules should probably go under Utils: - - Compatability.pm - - Timing.pm diff --git a/doc/devel/daemon-problem-environment b/doc/devel/daemon-problem-environment deleted file mode 100644 index fd9a98e296..0000000000 --- a/doc/devel/daemon-problem-environment +++ /dev/null @@ -1,70 +0,0 @@ -- = not in daemon problem environment from frontier - -- ALLOW_MAIL_TO - answerDate - CAPA_GraphicsDirectory - CAPA_Graphics_URL - CAPA_MCTools - CAPA_Tools - courseName - courseScriptsDirectory -- showHints - displayMode -- showSolutions - dueDate -- externalDvipngPath - externalGif2EpsPath -- externalGif2PngPath -- externalLaTeXPath - externalPng2EpsPath - externalTTHPath - fileName - formattedAnswerDate - formattedDueDate - formattedOpenDate - functAbsTolDefault - functLLimitDefault - functMaxConstantOfIntegration - functNumOfPoints - functRelPercentTolDefault - functULimitDefault - functVarDefault - functZeroLevelDefault - functZeroLevelTolDefault - htmlDirectory - htmlURL - inputs_ref -- languageMode - macroDirectory -- mailSmtpSender -- mailSmtpServer - numAbsTolDefault - numFormatDefault - numOfAttempts - numRelPercentTolDefault - numZeroLevelDefault - numZeroLevelTolDefault - openDate -- outputMode - PRINT_FILE_NAMES_FOR - probFileName - problemSeed - problemValue -- PROBLEM_GRADER_TO_USE - probNum - psvn - questionNumber -- QUIZ_PREFIX -- recitationName -- recitationNumber - sectionName - sectionNumber - sessionKey - setNumber -- studentID - studentLogin - studentName - tempDirectory - templateDirectory - tempURL - webworkDocsURL diff --git a/doc/devel/dont-forget b/doc/devel/dont-forget deleted file mode 100644 index feea0c1e9f..0000000000 --- a/doc/devel/dont-forget +++ /dev/null @@ -1,24 +0,0 @@ -Barbara from UNH had some ideas: - - " [x] destroy existing versions " on build problem sets page. - - others? - -Dan from UNH wants to be able to "name" math expressions so that images can be made conditional (i.e. if( blah ) { TEXT(\[ ... \]) } else { TEXT(\[ ... ]\) } (see email) - -Image generation alternatives: - - math2img, with some generalization - - math2img, as a function in dangerousMacros - - something out of http://preview-latex.sourceforge.net/ - - TeXd -- http://www.activetex.org/ - - - - -defaults.config: - - the "root" of the modperl handler should be sent in from outside - - right now, it's in $webworkURLs{root} - -fun Apache::* modules: - Apache::PerlVINC - Apache::SubProcess - output of subprocesses goes to client - -also: http://perl.apache.org/docs/general/perl_reference/perl_reference.html#Exception_Handling_for_mod_perl diff --git a/doc/devel/hardcopy-notes b/doc/devel/hardcopy-notes deleted file mode 100644 index 8a47286378..0000000000 --- a/doc/devel/hardcopy-notes +++ /dev/null @@ -1,22 +0,0 @@ -students should be able to: - - generate hardcopy for a single problem set for themselves -professors should be able to: - - generate hardcopy of a single problem set for any user - - generate hardcopy of multiple problem sets for any user - - generate hardcopy of a single problem set for multiple users - -inputs: - - $singleSet (from PATH_INFO) - added to @sets list - @sets - lists sets to generate - @users - lists users to generate for - $generateHardcopy - true if "Generate Hardcopy" button has been clicked - -hardcopy generated is (sets x users), if permissions permit - -subroutines: - &generateHardcopy - - compiles tex, translates, converts to pdf - - throws exceptions on PG errors, returns PG warnings - &displayForm - - displays options form diff --git a/doc/devel/new-DB-API b/doc/devel/new-DB-API deleted file mode 100644 index 30c3e319cf..0000000000 --- a/doc/devel/new-DB-API +++ /dev/null @@ -1,91 +0,0 @@ --------------------------------------------------------------------------------- -DB API --------------------------------------------------------------------------------- - -The DB API defines the following methods. These methods are grouped according to which tables they access. A method followed by a list of "=>"-prefixed methods depends on the listed methods. - -password - - listPasswords() - addPassword($Password) - getPassword($userID) - putPassword($Password) - deletePassword($userID) - -permission - - listPermissionLevels() - addPermissionLevel($PermissionLevel) - getPermissionLevel($userID) - putPermissionLevel($PermissionLevel) - deletePermissionLevel($userID) - -key - - listKeys() - addKey($Key) - getKey($userID) - putKey($Key) - deleteKey($userID) - -user - - listUsers() - addUser($User) - getUser($userID) - putUser($User) - deleteUser($userID) - => deletePassword($userID) - => deletePermissionLevel($userID) - => deleteKey($userID) - => deleteUserSet($userID, *) - -set - - listGlobalSets() - addGlobalSet($GlobalSet) - getGlobalSet($setID) - putGlobalSet($GlobalSet) - deleteGlobalSet($setID) - => deleteGlobalProblem($setID, *) - => deleteUserSet(*, $setID) - -set_user - - listSetUsers($setID) - listUserSets($userID) - addUserSet($UserSet) - getUserSet($userID, $setID) - putUserSet($UserSet) - deleteUserSet($userID, $setID) - => deleteUserProblem($userID, $setID, *) - -problem - - listGlobalProblems($setID) - addGlobalProblem($GlobalProblem) - getGlobalProblem($setID, $problemID) - putGlobalProblem($GlobalProblem) - deleteGlobalProblem($setID, $problemID) - => deleteUserProblem(*, $setID, $problemID) - -problem_user - - listProblemUsers($setID, $problemID) - listUserProblems($userID, $setID) - addUserProblem($UserProblem) - getUserProblem($userID, $setID, $problemID) - putUserProblem($UserProblem) - deleteUserProblem($userID, $setID, $problemID) - -set+set_user - - getGlobalUserSet($userID, $setID) - => getGlobalSet($setID) - => getUserSet($userID, $setID) - -problem+problem_user - - getGlobalUserProblem($userID, $setID, $problemID) - => getGlobalProblem($setID, $problemID) - => getUserProblem($userID, $setID, $problemID) diff --git a/doc/devel/new-DB-architecture b/doc/devel/new-DB-architecture deleted file mode 100644 index 3bc4d72110..0000000000 --- a/doc/devel/new-DB-architecture +++ /dev/null @@ -1,165 +0,0 @@ --------------------------------------------------------------------------------- -Architecture --------------------------------------------------------------------------------- - -The new database system uses a three-tier architecture to insulate each layer from the adjacent layers. - -TOP LAYER: DB -------------- - -The top layer of the architecture is the DB module. It provides the methods -listed in doc/new-DB-API, and uses schema modules (via tables) to implement those methods. - - / list* exists* add* get* put* delete* \ <- api -+------------------------------------------------------------------+ -| DB | -+------------------------------------------------------------------+ - \ password permission key user set set_user problem problem_user / <- tables - - -MIDDLE LAYER: SCHEMAS ---------------------- - -The middle layer of the architecture is provided by one or more schema modules. They are called "schema" modules because they control the structure of the data for a table. This includes odd things like the way multiple tables are encoded in a single hash in the WWHash schema, and the encoding scheme used. - -The schema modules provide an API that matches the requirements of the DB layer, on a per-table basis. Each schema module has a style that determines which drivers it can interface with. For example, WWHash is a "hash" style schema. SQL is a "dbi" style schema. - -Both WeBWorK 1.x and 2.x courses use: - - / password permission key \ / user \ <- tables -+-----------------------------+ +----------------+ -| Auth1Hash | | Classlist1Hash | -+-----------------------------+ +----------------+ - \ hash / \ hash / <- style - -WeBWorK 1.x courses also use: - - / set_user problem_user \ / set problem \ -+-------------------------+ +---------------------+ -| WW1Hash | | GlobalTableEmulator | -+-------------------------+ +---------------------+ - \ hash / \ null / - -The GlobalTableEmulator schema emulates the global set and problem tables using data from the set_user and problem_user tables. - -WeBWorK 2.x courses also use: - - / set set_user problem problem_user \ -+-------------------------------------+ -| WW2Hash | -+-------------------------------------+ - \ hash / - -Other drop-in schema modules could be: - - / * \ / password \ -+-------+ +--------------+ -| SQL | | PasswordLDAP | -+-------+ +--------------+ - \ dbi / \ ldap / - - -BOTTOM LAYER: DRIVERS ---------------------- - -Driver modules implement a style for a schema. They provide physical access to a data source containing the data for a table. Some driver modules are as follows: - - / hash \ / hash \ / hash \ <- style -+--------+ +--------+ +--------+ -| DB | | GDBM | | DB3 | -+--------+ +--------+ +--------+ - - / dbi \ / ldap \ -+-------+ +--------+ -| DBI | | LDAP | -+-------+ +--------+ - --------------------------------------------------------------------------------- -Schema API --------------------------------------------------------------------------------- - -$record - an object representing a record in the table -@keyparts - values for fields that make up the table's key - -@tables = tables() - returns list of tables supported. - -$style = style() - returns the required driver style. - -$handle = new($db, $driver, $table, $record, $params) - creates a schema interface for $table, using the driver interface - provided by $driver and using the record class named in $record. dies - if the $driver does not support the driver style needed by the schema. - $params contains extra information needed by the schema. $db is provided - so that schemas can query other schemas. (This is used by the - GlobalTableEmulator schema.) - -@keys = $handle->list(@keyparts) - returns a list containing the key of each record in the table that - matches the values in @keyparts. (i.e. [$userID, undef] will return all - of the records with the specified user_id.) the elements of @keys are - \@keyparts. if no matching records exist, an empty list is returned. - -$result = $handle->exists(@keyparts) - returns whether a record matching @keyparts exists in the table. - -$result = $handle->add($record) - attempts to add $record to the table. die if a record with the same key - exists. - -$record = $handle->get(@keyparts) - attempts to retrieve the record matching @keyparts from the table. - returns undef if no record matches. - -$result = $handle->put($record) - attempts to replace the record in the table that matches the key of - $record. dies if no such record exists. - -$result = $handle->delete(@keyparts) - attempts to delete the record matching @keyparts from the table. returns - true if the record was successfully deleted, or false if it did not - exist. - --------------------------------------------------------------------------------- -Driver API --------------------------------------------------------------------------------- - -COMMON ------- - -$style = style() - returns the supported driver style. - -$handle = new($source, $params) - creates a new interface to the data contained in $source. $params - contains extra information needed by the schema. - -$result = $handle->connect($mode) - connects to the data source with access mode $mode. dies if connection - fails. - -$result = $handle->disconnect() - disconnects from the data source. dies if disconnection fails. - -STYLE: hash ------------ - -$ref = $handle->hash() - returns a reference to the underlying tied hash. dies if the hash is - not available (i.e. not connected). - --------------------------------------------------------------------------------- -@keyparts key order --------------------------------------------------------------------------------- - -table keyparts ------ -------- -password user_id -permission user_id -key user_id -user user_id -set set_id -set_user user_id, set_id -problem set_id, problem_id -problem_user user_id, set_id, problem_id diff --git a/doc/devel/new-DB-notes b/doc/devel/new-DB-notes deleted file mode 100644 index fb2baa0634..0000000000 --- a/doc/devel/new-DB-notes +++ /dev/null @@ -1,67 +0,0 @@ --------------------------------------------------------------------------------- -Notes on the new database system --------------------------------------------------------------------------------- - -CHANGES IN THE ARCHITECTURE ---------------------------- - -The architecture is now three-tier. For more information, consult the file doc/new-DB-architecture. - -CHANGES IN THE TABLE STRUCTURE ------------------------------- - -PSVNs have been added to the set_user table, eliminating the need for a separate PSVN table. - -set_user and problem_user tables have been added, containing student-specific data. In most cases, the override fields (marked `!' above) will be empty. The dynamic fields (marked `~' above) will be populated as the student works through problems. problem_seed (in problem_user) and psvn(in set_user) are neither dynamic or override fields -- they are set at assignment time. - -a problem_order field has been added to the set and set_user tables. It contains a definition of how the problems in each set will be ordered. - -RECORD CREATION DEPENDANCIES ----------------------------- - - password -> user - permission -> user - key -> user - user -> - set -> - set_user -> user, set - problem -> set -problem_user -> set_user, problem - -RECORD DELETION DEPENDANCIES ----------------------------- - - password -> - permission -> - key -> - user -> password permission key set_user - set -> set_user problem - set_user -> problem_user - problem -> problem_user -problem_user -> - -TABLE STRUCTURE IMPLEMENTATION IN HASH-BASED DATABASES ------------------------------------------------------- - -The GeneralHash schema module will implement a new table structure implementation for use with WeBWorK 2. Classlist1Hash, Auth1Hash, and WW1Hash will use the old implementation. - -Each table will be stored in a separate database file. Each table has one or more fields that make up a unique identifier for each record. In the case of a one field identifier, the value of that field will be used as the record's key in the hash. In the case of a two-field identifier, the string "FIELD1:FIELD2" will be used. Literal colons will be escaped as `\:', and literal backslashes as `\\'. - -Rather than use a custom encoding scheme for the hash data, as is done in the 1.x implementation, a simple table-based scheme will be used, in which each field is separated by a colon (`:'). Literal colons (and literal backslashes) will be dealt with as above. This sort of scheme is common in the UNIX world. For example, consider the UNIX password file. - -COMPATABILITY WITH 1.X DATABASES --------------------------------- - -By specifying the WW1Hash schema module for the appropriate tables, 1.x databases can be handled. - -Conversion of 1.x databases to 2.x databases can be achieved by using the most popular value for each field in each user-specific record as the global value, and merging PSVNs from the separate PSVN table. Conversion from 2.x databases to 1.x databases is trivial, if somewhat lossy (i.e. problem_order). - -TREATMENT OF THE CURRENT API ----------------------------- - -The current API (implemented by Auth.pm, Classlist.pm, and WW.pm) will be removed. The code base is currently small enough that it will be easy to migrate existing code to the new API. - -NEW API FUNCTIONS ------------------ - -The new API is outlined in the file doc/new-DB-API. diff --git a/doc/devel/new-DB-sql b/doc/devel/new-DB-sql deleted file mode 100644 index cdd3877fed..0000000000 --- a/doc/devel/new-DB-sql +++ /dev/null @@ -1,79 +0,0 @@ -# Feed this file to the mysql client to create a course database. Replace the -# string !!COURSENAME!! with the name of your course. - -CREATE DATABASE webwork_!!COURSENAME!!; -USE webwork_!!COURSENAME!!; - -CREATE TABLE user ( - user_id VARCHAR(255) NOT NULL PRIMARY KEY, - first_name TEXT, - last_name TEXT, - email_address TEXT, - student_id TEXT, - status TEXT, - section TEXT, - recitation TEXT, - comment TEXT -); - -CREATE TABLE password ( - user_id VARCHAR(255) NOT NULL PRIMARY KEY, - password TEXT -); - -CREATE TABLE permission ( - user_id VARCHAR(255) NOT NULL PRIMARY KEY, - permission INT -); - -CREATE TABLE key_not_a_keyword ( - user_id VARCHAR(255) NOT NULL PRIMARY KEY, - key_not_a_keyword TEXT, - timestamp INT -); - -CREATE TABLE set_not_a_keyword ( - set_id VARCHAR(255) NOT NULL PRIMARY KEY, - set_header TEXT, - problem_header TEXT, - open_date INT, - due_date INT, - answer_date INT -); - -CREATE TABLE set_user ( - user_id VARCHAR(255) NOT NULL, - set_id VARCHAR(255) NOT NULL, - psvn INT NOT NULL PRIMARY KEY AUTO_INCREMENT, - set_header TEXT, - problem_header TEXT, - open_date INT, - due_date INT, - answer_date INT -); - -CREATE TABLE problem ( - set_id VARCHAR(255) NOT NULL, - problem_id VARCHAR(255) NOT NULL, - source_file TEXT, - value INT, - max_attempts INT -); - -CREATE TABLE problem_user ( - user_id VARCHAR(255) NOT NULL, - set_id VARCHAR(255) NOT NULL, - problem_id VARCHAR(255) NOT NULL, - source_file TEXT, - value INT, - max_attempts INT, - problem_seed INT, - status FLOAT, - attempted INT, - last_answer TEXT, - num_correct INT, - num_incorrect INT -); - -GRANT select ON webwork_!!COURSENAME!!.* TO webworkRead@localhost IDENTIFIED BY 'zaqwsxcderfv'; -GRANT select, insert, update, delete ON webwork_!!COURSENAME!!.* TO webworkWrite@localhost IDENTIFIED BY 'qwerfdsazxcv'; diff --git a/doc/devel/new-DB-structure b/doc/devel/new-DB-structure deleted file mode 100644 index 4e42f958b0..0000000000 --- a/doc/devel/new-DB-structure +++ /dev/null @@ -1,69 +0,0 @@ --------------------------------------------------------------------------------- -DB table structure --------------------------------------------------------------------------------- - -The DB API models an underlying table structure that may or may not literally exist, depending on what transformations the schema modules make. However, this is the table structure that is presented to users of the DB module. - -The order of key fields in this list is the order that arguments should be passed to the exists(), get(), and delete() schema functions, and the order of the values in the return value of the list() schema function. - -* indicates a key -=> indicates a relation -! indicates an override field -~ indicates a "dynamic" field (modified by the problem processor) - -password - user_id *,=> user.id - password -permission - user_id *,=> user.id - permission -key - user_id *,=> user.id - key -user - id * - first_name - last_name - email_address - student_id - status - section - recitation - comment -set - id * - set_header - problem_header - open_date - due_date - answer_date - problem_order (not implemented) -set_user - user_id *,=> user.id - set_id *,=> set.id - psvn - set_header ! - problem_header ! - open_date ! - due_date ! - answer_date ! - problem_order ! (not implemented) -problem - id * - set_id *,=> set.id - source_file - value - max_attempts -problem_user - user_id *,=> user.id - set_id *,=> set.id - problem_id *,=> problem.id - source_file ! - value ! - max_attempts ! - problem_seed - status ~ - attempted ~ - last_answer ~ - num_correct ~ - num_incorrect ~ diff --git a/doc/devel/pre-bugzilla-TODO-file b/doc/devel/pre-bugzilla-TODO-file deleted file mode 100644 index eaf799a3ba..0000000000 --- a/doc/devel/pre-bugzilla-TODO-file +++ /dev/null @@ -1,186 +0,0 @@ -################################################################################ -# WeBWorK mod_perl (c) 2000-2002 WeBWorK Project -# $Id: pre-bugzilla-TODO-file,v 1.1 2003-06-03 19:23:03 sh002i Exp $ -################################################################################ - -Son of WeBWorK - TODO list - -DONE write template file from what we drew on the board -DONE normalize files: - - (c) header on all files - - standard order of preamble lines: - 1. (c) header - - 2. package PACKAGENAME; - - 3. short summary of the package (pod's NAME section) - - 4. use - pragmatic modules - 5. use - standard perl modules - 6. use - CPAN modules - 7. use - webwork modules - - ALWAYS use strict and use warnings - - use "use base" rather than "our @ISA" - - no warnings or errors! - - (later on) POD documentation for all files -DONE fix templating code in ContentGenerator, add new escape functions -DONE implement the new template -DONE finish ProblemSets and ProblemSet - --------------------------------------------------------------------------------- - -DONE New interface to PG.pm - DONE - instead of it accessing the database directly, accept $user, - $set, and $problem as arguments (instead of $setName, etc.) - DONE - UPDATE PG.pm'S DOCS!!!!!!!!!!!!! - DONE - Fix Problem.pm and Hardcopy.pm to work with the new interface - - Hardcopy generation - DONE - ad-hoc version of &latex2png in Hardcopy.pm (move later) - DONE - fix code (heh heh) - DONE - fix hardcopySetHeader - - Integration of dvipng method of image generation - N/A - choice between writing to a temporary TeX file from within - dangerousMacros, or queueing the equations up in RAM and - passing them out to the caller (via PG_flags?) who will then - call &latex2png - DONE - write tempfile from dangerousMacros - - PRO: gets it out of RAM - - PRO: no dependancies between equations - - CON: have to coordinate between latex2png and - dangerous macros for tempfile locations - FORGET - queue equations in RAM - - PRO: all file access and external calls happen - outside of the safe compartment. - - PRO: latex2png gets to decide where to put files - - CON: stuff sits in RAM - - CON: uses a package array to queue equations :p - -DONE display of screen set header in ProblemSet.pm (easy) -DONE add -DONE fix alignment of displaymath images -DONE print $pg->{header_text} in head of Problem.pm -DONE remove "set" and "prob" from URL generation, remove s/^(set|prob)//; -DONE remove webwork-dvipng-xxxxx temp directories when finished with them -DONE make "enter" on the problem form trigger "submit answer", not "redisplay" -DONE handling of PG warnings (?!?!?!?!) -DONE time logging -DONE test transaction logging -DONE make static images work -DONE make GD-generated images work -DONE make HTML links work -DONE make images in PDFs work -DONE remove dependancies on Global:: from send_mail_to -DONE preview button in Problem -DONE handle PG errors (and warnings) in Hardcopy -DONE have logout button invalidate key -DONE Options - email address and password - --------------------------------------------------------------------------------- - ->>>>> FOR JANUARY PREVIEW RELEASE <<<<< - -DONE Feedback - need nice modular way of sending email -DONE customize reciever, make object dumps prettier, link to context -DONE Professor - redirect to the old system - pretty die messages (from outside of the translator) -DONE make sure students can't look at not-yet-open problem sets -DONE make answer previews use $displayMode -DONE make preview-on-submit optional - Increase border size on images by a couple pixels -DONE effectiveUser for at least Problem.pm -DONE add a "check answers" button if the user has the apropriate permissions -DONE write a template escape for printing $user, $effectiveUser, &c. nicely - calling logout requires valid key -- doesn't make sense. -DONE hardcopy - allow hardcopy with correct answers (BRANCH) -DONE sort problems by due date -DONE disable "show hint/solution" when there's no hint/solution -DONE results table -DONE part(or blank)/entered/preview/result/messages -DONE don't show messages unless there are some -DONE make displayMode sticky (for nav and siblings) -DONE check TTH mode character encodings on mac - grep for "***" in source, address all issues (hah!) - -mike wants: - problem credit indicator (graphical?) -DONE answers/solutions in hardcopy -DONE turn off verbose debugging in feedback mail -DONE URL on feedback from any module - email address change notification by email -arnie wants: - parse errors in student answers hilited more strongly - ->>>>> FOR SPRING RELEASE <<<<< - - write TeXImage module to take care of TeX image caching and generation - replace direct access of $permissionLevel with calls to Authz - replace hardcoded URL construction with some other method - (unify dispatcher URL parsing and module URL generation) - implement professor pages - have the prof modules be subclasses of the single Professor - ContentGenerator module? more structured? less structured? - THIS WILL TAKE LONGER THAN I THINK IT WILL - implement better (and more unified) file editor - ->>>>> AFTER SPRING RELEASE <<<<< - - MySQL and PostgreSQL database backend - problem library work (?) - PG language work (?) - renderer/frontend/database uncoupling (?) - --------------------------------------------------------------------------------- - -change notes - --------------------------------------------------------------------------------- - -IO functions (dependancies on other IO functions are not listed) - -all variables in the global namespace should be replaced with items in the -%envir hash. all functions that access the envir hash need to be evaluated -from within the safe compartment (i.e. &unrestricted_eval'd). - -includePGtext - $envir{probFileName} -send_mail_to - $REMOTE_HOST - $REMOTE_ADDR - #$Global::smtpServer - #$Global::webmaster - (&Global::wwerror) -read_whole_problem_file -read_whole_file -convertPath stub -getDirDelim stub -getCourseTempDirectory - $envir{tempDirectory} -surePathToTmpFile - #$Global::tmp_directory_permission - #$Global::numericalGroupID - (&Global::wwerror) -fileFromPath -directoryFromPath -createFile -createDirectory -getImageDimmensions - --------------------------------------------------------------------------------- - - Calling LaTeX/dvipng and PDFLaTeX in a nice way - - create LaTeX.pm (?) - - &latex2pdf - - create secure tempdir - - write hardcopy to tex file - - call pdflatex - - move resulting pdf file to tmp/hardcopy/whatever.pdf - - remove tempdir - - &latex2png (dvipng method) - - create secure tempdir - - write equations to tex file - - call latex - - run dvipng (as per ImageGenerator) on dvi file - - move resulting images to tmp/dvipng/whatever.png - - remove tempdir diff --git a/doc/devel/schema-2002 b/doc/devel/schema-2002 deleted file mode 100644 index e449eac1b8..0000000000 --- a/doc/devel/schema-2002 +++ /dev/null @@ -1,77 +0,0 @@ -=> denotes a field which corresponds to the key of another table - --------------------------------------------------------------------------------- -Database: COURSENAME_classlist (i.e. MTH161Q_classlist) --------------------------------------------------------------------------------- - -Table: user -Read by: scoring tools, classlist tools -Written by: classlist tools - - login - last_name - first_name - email_address - student_id - status - section - recitation - comment - -Table: access -Read by: authorization system -Written by: classlist tools - - login - password - permissions - --------------------------------------------------------------------------------- -Database: COURSENAME_webwork --------------------------------------------------------------------------------- - -Table: set -Read by: set lister, problem lister, harcopy generator, problem processor -Written by: set generator - - name - set_header - problem_header - open_date - due_date - answer_date - -Table: problem_SETNAME -Read by: problem lister, hardcopy generator, problem processor, scoring tools -Written by: set generator - - number - source_file - value - max_attempts - -Table: set_user -Read by: same as set table -Written by: set generator - - login => user.login - set => set.name - psvn - problem_order - open_date - due_date - answer_date - -Table: ww_USERNAME -Read by: same as problem_SETNAME table -Written by: set generator, problem processor - - set => set.name - problem => problem.number - max_attempts - problem_seed - status - attempted - last_answer - num_correct - num_incorrect diff --git a/doc/devel/template-escapes b/doc/devel/template-escapes deleted file mode 100644 index 9ddb4f7756..0000000000 --- a/doc/devel/template-escapes +++ /dev/null @@ -1,14 +0,0 @@ -head -path - style = text|image - image = URL of image - text = text separator -links -siblings -nav - style = text|image - imageprefix = prefix to image URL - imagesuffix = suffix to image URL - separator = HTML to place in between links -title -body diff --git a/doc/devel/unified-prof-page-form-notes b/doc/devel/unified-prof-page-form-notes deleted file mode 100644 index b147503bfc..0000000000 --- a/doc/devel/unified-prof-page-form-notes +++ /dev/null @@ -1,32 +0,0 @@ -{ - field_name => { - type => /number|text|date|password|enumerable/, - size => /\d*/, - specificity => /global|user/, - access => /readonly|writeonly|readwrite/, - items => { # Only for enumerable - value => "label", - value => "label", - } - synonyms => { - qr/pattern/ => "value", - qr/pattern/ => "value", - "*" => "value", - } - } -} - -type number, text, longtext, date -label -value -specificity global user - Indicates whether it is a global setting that is overridden for a user, - or a user-only setting that is only overwritten for multiUser editing. -synonyms - Regex-s indicating other values that could mean the same thing as the - keys in the "items" hash. This is for backwards compatibility with the - days when even multi-choice fields were given freeform frontends. - the special value "*" (an illegal regular expression) points to a value - that should be substituted for unrecognized values. The order that the - values are checked against the regular expressions is unspecified, and - probably won't be the order they are given in the source code. diff --git a/doc/parser/README b/doc/parser/README deleted file mode 100644 index 8c62267d3f..0000000000 --- a/doc/parser/README +++ /dev/null @@ -1,145 +0,0 @@ -OVERVIEW: - -This directory contains the documentation for a new -mathematical-expression parser written in perl. It was developed for -use with the WeBWorK on-line homework system, but it can be used in -any perl program. - -The goal was to process vector-valued expressions, but the parser was -designed to be extensible, so that you could add your own functions, -operators, and data types. It is still a work in progress, but should -provide a framework for building more sophisticated expression handling. - -Currenlty, the parser understands: - - - real and complex numbers, - - points, vectors, and matrices (with real or complex entries) - - arbitrary lists of elements - - intervals and unions of intervals - - predefined strings like 'infinity' - -Some other useful features are that you can write sin^2 x for (sin(x))^2 -and sin^-1 x for arcsin(x), and so on. - -Most of the documentation still needs to be written, but you can get some -ideas from the samples in the problems and extensions directories, and by -reading the files in the docs directory. - - -INSTALLATION: - -The parser should already be installed as part of the WeBWorK 2.1 -distribution, so you should not need to install it separately. If you -don't seem to have it installed, then it can be obtained from the -Union CVS repository at - - http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/WeBWorKCVS - -The README file in that directory contains the installation instructions. - - -SAMPLE FILES: - -Sample problems are given in the problems and extensions directories. Move -these to the templates directory of a course where you want to test the -Parser, and move the contents of the macros directory to that course's -macros directory. - -Now try looking at these problems using the Library Browser. Edit the -source to see how they work, and to read the comments within the code -itself. - - -EXAMPLE FILES: - -The 'problems' directory contains several examples that show how to use -Parser within your problem files. - - sample01.pg - Uses the parser to make a string into a formula that you can - evaluate and print in TeX form - - sample02.pg - Shows how to create formulas using perl's usual mathematical - expressions rather than character strings. - - sample03.pg - Shows how to use the parser's differentiation abilities. - - sample04.pg and sample05.pg - Use the parser in conjunction with the graphics macros to generate - function graphs on the fly. These also show how to create a - perl function to evaluate an expression. - - sample06.pg - Shows some simple use of vectors in a problem. - - sample07.pg - Example if using the build-in Real object and its answer - checker - - sample08.pg - Uses complex numbers and the built-in checker - - sample09.pg and sample10.pg - Demonstrates points and vectors and their answer checkers - - sample11.pg and sample12.pg - Shows the answer checkers for intervals and unions. - - sample13.pg, sample14.pg, sample15.pg - Demonstrate various list checkers, including a check for the - word 'NONE', which is a predefined string. - - sample16.pg, sample17.pg, sample18.pg - These show the multi-variable function checker in use (for - functions of the form R->R, R^2->R and R->R^3). - - sample19.pg - Uses the function checker to implement a "constant" that can - be used in formulas. - - sample20.pg - Shows how to use the parser's substitution abilities. - - sample21.pg - Checks for a list of points. - - sample22.pg - Shows how to provide named constants that the student can - use in his answer. - -The 'examples' directory contains samples that show how to extend the -parser to include your own functions, operators, and so on. There are also -some samples of how to call the methods available for Formula objects -generated by the parser, and what some error messages look like. - - 1-function.pg - Adds a single-variable function to the parsers list of functions. - - 2-function.pg - Adds a two-variable function to the parser. - - 3-operator.pg - Adds a binary operator to the parser. (Unary operators are similar.) - - 4-list.pg - Adds a new "list type" object. In this case, it's really an - operation [n,r] that returns n choose r. - - 5-list.pg - Add a new "equality" operator that you can use to handle answers - like "x+y=0". - - 6-precedence.pg - Shows an experimental precedence setting that can be used to make - sin 2x return sin(2x) rather than (sin(2))x. - - 7-context.pg - Shows how to switch contexts (in this case, to complex and to vector - contexts), and how this affects the parsing. - - 8-answer.pg - Implements a simple vector-valued answer checker using the - parser's computation and comparison ability. - diff --git a/doc/parser/docs/ParserAnswerCheckers.pod b/doc/parser/docs/ParserAnswerCheckers.pod deleted file mode 100644 index 10d3f7ad69..0000000000 --- a/doc/parser/docs/ParserAnswerCheckers.pod +++ /dev/null @@ -1,423 +0,0 @@ -=head1 MathObjects-based Answer Checkers - -MathObjects is designed to be used in two ways. First, you can use -it within your perl code when writing problems as a means of making it -easier to handle formulas, and in particular, to genarate be able to -use a single object to produce numeric values, TeX output and answer -strings. This avoids having to type a function three different ways -(which makes maintaining a problem much harder). Since MathObjects -also included vector and complex arthimatic, it is easier to work with -these types of values as well. - -The second reason for MathObjects is to use it to process student -input. This is accomplished through special answer checkers that are -part of the Parser package (rather than the traditional WeBWorK answer -checkers). Checkers are available for all the types of values that -the parser can produce (numbers, complex numbers, infinities, points, -vectors, intervals, unions, formulas, lists of numbers, lists of -points, lists of intervals, lists of formulas returning numbers, lists -of formulas returning points, and so on). - -To use one of these checkers, simply call the ->cmp method of the -object that represents the correct answer. For example: - - $n = Real(sqrt(2)); - ANS($n->cmp); - -will produce an answer checker that matches the square root of two. -Similarly, - - ANS(Vector(1,2,3)->cmp); - -matches the vector <1,2,3> (or any computation that produces it, e.g., -i+2j+3k, or <4,4,4>-<3,2,1>), while - - ANS(Interval("(-inf,3]")->cmp); - -matches the given interval. Other examples include: - - ANS(Infinity->cmp); - ANS(String('NONE')->cmp); - ANS(Union("(-inf,$a) U ($a,inf)")->cmp); - -and so on. - -Formulas are handled in the same way: - - ANS(Formula("x+1")->cmp); - - $a = random(-5,5,1); $b = random(-5,5,1); $x = random(-5,5,1); - $f = Formula("x^2 + $a x + $b")->reduce; - ANS($f->cmp); - ANS($f->eval(x=>$x)->cmp); - - $x = Formula('x'); - ANS((1+$a*$x)->cmp); - - Context("Vector")->variables->are(t=>'Real'); - $v = Formula(""); $t = random(-5,5,1); - ANS($v->cmp); - ANS($v->eval(t=>$t)->cmp); - -and so on. - -Lists of items can be checked as easily: - - ANS(List(1,-1,0)->cmp); - ANS(List(Point($a,$b),Point($a,-$b))->cmp); - ANS(List(Vector(1,0,0),Vector(0,1,1))->cmp); - ANS(Compute("(-inf,2),(4,5)")->cmp); # easy way to get list of intervals - ANS(Formula("x, x+1, x^2-1")->cmp); - ANS(Formula(",,<0,x>")->cmp); - ANS(List('NONE')->cmp); - -and so on. The last example may seem strange, as you could have used -ANS(String('NONE')->cmp), but there is a reason for using this type -of construction. You might be asking for one or more numbers (or -points, or whatever) or the word 'NONE' of there are no numbers (or -points). If you used String('NONE')->cmp, the student would get an -error message about a type mismatch if he entered a list of numbers, -but with List('NONE')->cmp, he will get appropriate error messages for -the wrong entries in the list. - -It is often appropriate to use the list checker in this way even when -the correct answer is a single value, if the student might type a list -of answers. - -On the other hand, using the list checker has its disadvantages. For -example, if you use - - ANS(Interval("(-inf,3]")->cmp); - -and the student enters (-inf,3), she will get a message indicating -that the type of interval is incorrect, while that would not be the -case if - - ANS(List(Interval("(-inf,3]"))->cmp); - -were used. (This is because the student doesn't know how many -intervals there are, so saying that the type of interval is wrong -would inform her that there is only one.) - -The rule of thumb is: the individual checkers can give more detailed -information about what is wrong with the student's answer; the list -checker allows a wider range of answers to be given without giving -away how many answers there are. If the student knows there's only -one, use the individual checker; if there may or may not be more than -one, use the list checker. - -Note that you can form lists of formulas as well. The following all -produce the same answer checker: - - ANS(List(Formula("x+1"),Formula("x-1"))->cmp); - - ANS(Formula("x+1,x-1")->cmp); # easier - - $f = Formula("x+1"); $g = Formula("x-1"); - ANS(List($f,$g)->cmp); - - $x = Formula('x'); - ANS(List($x+1,$x-1)->cmp); - -See the files in webwork2/doc/parser/problems for more -examples of using the parser's answer checkers. - -=head2 Controlling the Details of the Answer Checkers - -The action of the answer checkers can be modified by passing flags to -the cmp() method. For example: - - ANS(Real(pi)->cmp(showTypeWarnings=>0)); - -will prevent the answer checker from reporting errors due to the -student entering in the wrong type of answer (say a vector rather than -a number). - -=head3 Flags common to all answer checkers - -There are a number of flags common to all the checkers: - -=over - -=item S1 or 0 >>> - -show/don't show messages about student -answers not being of the right type. -(default: 1) - -=item S1 or 0 >>> - -show/don't show messages produced by -trying to compare the professor and -student values for equality, e.g., -conversion errors between types. -(default: 1) - -=item S1 or 0 >>> - -show/don't show type mismatch errors -produced by strings (so that 'NONE' will -not cause a type mismatch in a checker -looking for a list of numbers, for example). -(default: 1) - -=back - -In addition to these, the individual types have their own flags: - -=head3 Flags for Real()->cmp - -=over - -=item S1 or 0 >>> - -Don't report type mismatches if the -student enters an infinity. -(default: 1) - -=back - -=head3 Flags for String()->cmp - -=over - -=item Svalue >>> - -Specifies the type of object that -the student should be allowed to enter -(in addition the string). -(default: 'Value::Real') - -=back - -=head3 Flags for Point()->cmp - -=over - -=item S1 or 0 >>> - -show/don't show messages about the -wrong number of coordinates. -(default: 1) - -=item S1 or 0 >>> - -show/don't show message about -which coordinates are right. -(default: 1) - -=back - -=head3 Flags for Vector()->cmp - -=over - -=item S1 or 0 >>> - -show/don't show messages about the -wrong number of coordinates. -(default: 1) - -=item S1 or 0 >>> - -show/don't show message about -which coordinates are right. -(default: 1) - -=item S1 or 0 >>> - -do/don't allow the student to -enter a point rather than a vector. -(default: 1) - -=item S1 or 0 >>> - -Mark the answer as correct if it -is parallel to the professor's answer. -Note that a value of 1 forces -showCoordinateHints to be 0. -(default: 0) - -=item S1 or 0 >>> - -During a parallel check, mark the -answer as correct only if it is in -the same (not the opposite) -direction as the professor's answer. -(default: 0) - -=back - -=head3 Flags for Matrix()->cmp - -=over - -=item S1 or 0 >>> - -show/don't show messages about the -wrong number of coordinates. -(default: 1) - -=back - -The default for showEqualErrors is set to 0 for Matrices, since -these errors usually are dimension errors, and that is handled -separately (and after the equality check). - -=head3 Flags for Interval()->cmp - -=over - -=item S1 or 0 >>> - -do/don't show messages about which -endpoints are correct. -(default: 1) - -=item S1 or 0 >>> - -do/don't show messages about -whether the open/closed status of -the enpoints are correct (only -shown when the endpoints themselves -are correct). -(default: 1) - -=back - -=head3 Flags for Union()->cmp and List()->cmp - -all the flags from the Real()->cmp, plus: - -=over - -=item S1 or 0 >>> - -do/don't show messages about which -entries are incorrect. -(default: $showPartialCorrectAnswers) - -=item S1 or 0 >>> - -do/don't show messages about having the -correct number of entries (only shown -when all the student answers are -correct but there are more needed, or -all the correct answsers are among the -ones given, but some extras were given). -(default: $showPartialCorrectAnswers) - -=item S1 or 0 >>> - -do/don't give partial credit for when -some answers are right, but not all. -(default: $showPartialCorrectAnswers) -(currently the default is 0 since WW -can't handle partial credit properly). - -=item S1 or 0 >>> - -give credit only if the student answers -are in the same order as the -professor's answers. -(default: 0) - -=item S'a (name)' >>> - -The string to use in error messages -about type mismatches. -(default: dynamically determined from list) - -=item S'a (name)' >>> - -The string to use in error messages -about numbers of entries in the list. -(default: dynamically determined from list) - -=item Svalue >>> - -Specifies the type of object that -the student should be allowed to enter -in the list (determines what -constitutes a type mismatch error). -(default: dynamically determined from list) - -=item S1 or 0 >>> - -Do/don't require the parentheses in the -student's answer to match those in the -professor's answer exactly. -(default: 1) - -=item S1 or 0 >>> - -Do/don't remove the parentheses from the -professor's list as part of the correct -answer string. This is so that if you -use List() to create the list (which -doesn't allow you to control the parens -directly), you can still get a list -with no parentheses. -(default: 0 for List() and 1 for Formula()) - -=back - -=head3 Flags for Formula()->cmp - -The flags for formulas are dependent on the type of the result of -the formula. If the result is a list or union, it gets the flags -for that type above, otherwise it gets that flags of the Real -type above. - -More flags need to be added in order to allow more control over the -answer checkers to give the full flexibility of the traditional -WeBWorK answer checkers. Note that some things, like whether trig -functions are allowed in the answer, are controlled through the -Context() rather than the answer checker itself. For example, - - Context()->functions->undefine('sin','cos','tan'); - -would remove those three functions from use. (One would need to remove -cot, sec, csc, arcsin, asin, etc., to do this properly; there could be -a function call to do this.) - -Similarly, which arithmetic operations are available is controlled -through Context()->operations. - -The tolerances used in comparing numbers are part of the Context as -well. You can set these via: - - Context()->flags->set( - tolerance => .0001, # the relative or absolute tolerance - tolType => 'relative', # or 'absolute' - zeroLevel => 1E-14, # when to use zeroLevelTol - zeroLevelTol => 1E-12, # smaller than this matches zero - # when one of the two is less - # than zeroLevel - limits => [-2,2], # limits for variables in formulas - num_points => 5, # the number of test points - ); - -[These need to be handled better.] - -Note that for testing formulas, you can override the limits and -num_points settings by setting these fields of the formula itself: - - $f = Formula("sqrt(x-10)"); - $f->{limits} = [10,12]; - - $f = Formula("log(xy)"); - $f->{limits} = [[.1,2],[.1,2]]; # x and y limits - -You can also specify the test points explicitly: - - $f = Formula("sqrt(x-10)"); - $f->{test_points} = [[11],[11.5],[12]]; - - $f = Formula("log(xy)"); - $f->{test_points} = [[.1,.1],[.1,.5],[.1,.75], - [.5,.1],[.5,.5],[.5,.75]]; - -[There still needs to be a means of handling the tolerances similarly, -and through the ->cmp() call itself.] - diff --git a/doc/parser/docs/UsingParser.pod b/doc/parser/docs/UsingParser.pod deleted file mode 100644 index 4ece7c5b90..0000000000 --- a/doc/parser/docs/UsingParser.pod +++ /dev/null @@ -1,401 +0,0 @@ -=head1 USING MATHOBJECTS - -To use MathObjects in your own problems, you need to load the -"MathObjects.pl" macro file: - - loadMacros("Parser.pl"); - -which defines the commands you need to interact with MathObjects. -Once you have done that, you can call the MathObjects functions to create -formulas for you. The main call is Formula(), which takes a string and -returns a parsed version of the string. For example: - - $f = Formula("x^2 + 3x + 1"); - -will set $f to a reference to the parsed version of the formula. - -=head2 Working With Formulas - -A formula has a number of methods that you can call. These include: - -=over - -=item $f->eval(x=>5) - -Evaluate the formula when x is 5. -If $f has more variables than that, then -you must provide additional values, as in -$f->eval(x=>3,y=>1/2); - -=item $f->reduce - -Tries to remove redundent items from your -formula. For example, Formula("1x+0") returns "x". -Reduce tries to factor out negatives and do -some other adjustments as well. (There still -needs to be more work done on this. What it does -is correct, but not always smart, and there need -to be many more situations covered.) All the -reduction rules can be individually enabled -or disabled using the Context()->reduction->set() -method, but the documentation for the various -rules is not yet ready. - -=item $f->substitute(x=>5) - -Replace x by the value 5 throughout (you may want -to reduce the result afterword, as this is not -done automatically). Note that you can replace a -variable by another formula, if you wish. To make -this easier, substitute will apply Formula() to -any string values automatically. E.g., - Formula("x-1")->substitute(x=>"y") - -returns "y-1" as a formula. - -=item $f->string - -returns a string representation of the formula -(should be equivalent to the original, though not -necessarily equal to it). - -=item $f->TeX - -returns a LaTeX representation of the formula. -You can use this in BEGIN_TEXT...END_TEXT blocks -as follows: - - BEGIN_TEXT - Suppose \(f(x) = \{$f->TeX}\). ... - END_TEXT - -=item $f->perl - -returns a representation of the formula that could -be evaluated by perl's eval() function. - -=item $f->perlFunction - -returns a perl code block that can be called to -evaluate the function. For example: - - $f = Formula('x^2 + 3')->perlFunction; - $y = &$f(5); - -will assign the value 28 to $y. -You can also pass a function name to perlFunction -to get a named function to call: - - Formula('x^2 + 3')->perlFunction('f'); - $y = f(5); - -If the formula involves more than one variable, -then the paramaters should be given in -alphabetical order. - - Formula('x^2 + y')->perlFunction('f'); - $z = f(5,3); # $z is 28. - -Alternatively, you can tell the order for the -parameters: - - Formula('x^2 + y')->perlFunction('f',['y','x']); - $z = f(5,3); $ now $z is 14. - -=back - -=head2 Combining Formulas - -There is a second way to create formulas. Once you have a formula, you can -create additional formulas simply by using perls' built-in operations and -functions, which have been overloaded to handle formulas. For example, - - $x = Formula('x'); - $f = 3*x**2 + 2*$x - 1; - -makes $f be a formula, and is equivalent to having done - - $f = Formula("3x^2 + 2x - 1"); - -This can be very convenient, but also has some pitfalls. First, you -need to include '*' for multiplication, since perl doesn't do implied -multiplication, and you must remember to use '**' not '^'. (If you use '^' -on a formula, the parser will remind you to use '**'.) Second, the -precedences of the operators in perl are fixed, and so changes you make to -the precedence table for the parser are not reflected in formulas produced -in this way. (The reason '^' is not overloaded to do exponentiation is -that the precedence of '^' is wrong for that in perl, and can't be -changed.) As long as you leave the default precedences, however, things -should work as you expect. - -Note that the standard functions, like sin, cos, etc, are overloaded to -generate appropriate formulas when their values are formulas. For example, - - $x = Formula('x'); - $f = cos(3*$x + 1); - -produces the same result as $f = Formula("cos(3x+1)"); and you can then go -on to output its TeX form, etc. - -=head2 Special Syntax - -This parser has support for some things that are missing from the current -one, like absolute values. You can say |1+x| rather than abs(1+x) -(though both are allowed), and even |1 - |x|| works. - -Also, you can use sin^2(x) (or even sin^2 x) to get (sin(x))^2. - -Finally, you can use sin^-1(x) to get arcsin(x). - -There is an experimental set of operator precedences that make it possible -to write sin 2x + 3 and get sin(2x) + 3. See examples/7-precedence.pg -for some details. - -=head2 The Formula Types - -The parser understands a wide range of data types, including real and -complex numbers, points, vectors, matrices, arbitrary lists, intervals, -unions of intervals, and predefined words. Each has a syntax for use -within formulas, as described below: - - numbers the usual form: 153, 233.5, -2.456E-3, etc. - - complex a + b i where a and b are numbers: 1+i, -5i, 6-7i, etc. - - infinitites the words 'infinity' or '-infinity' (or several - equivalents). - - point (a,b,c) where a, b and c are real or complex numbers. - any number of coordinates are allowed. Eg, (1,2), - (1,0,0,0), (-1,2,-3). Points are promoted to vectors - automatically, when necessary. - - vector or a i + b j + c k (when used in vector context). - As with points, vectors can have any number of - coordinates. For example, <1,0,0>, <-1,3>, , etc. - - matrix [[a11,...,a1n],...[am1,...amn]], i.e., use [..] around - each row, and around the matrix itself. The elements - are separated by commas (not spaces). e.g, - [[1,2],[3,4]] (a 2x2 matrix) - [1,2] (a 1x2 matrix, really a vector) - [[1],[2]] (a 2x1 matrix, ie. column vector) - Points and vectors are promoted to matrices when - appropriate. Vectors are converted to column vectors - when needed for matrix-vector multiplication. Matrices - can be 3-dimensional or higher by repeated nesting of - matrices. (In this way, a 2-dimensional matrix is really - thought of as a vector of vectors, and n-dimensional - ones as vectors of (n-1)-dimensional ones.) - - list (a,b,c) where a,b,c are arbitrary elements. - For example, (1+i, -3, <1,2,3>, Infinity). - The empty list () is allowed, and the parentheses are - optional if there is only one list. (This makes it - possible to make list-based answer checkers that - really know where the separations occur.) - - interval (a,b), (a,b], [a,b), [a,b], or [a,a] where a and b are - numbers or appropriate forms of infinity. - For example, (-INF,3], [4,4], [2,INF), (-INF,INF). - - union represented by 'U'. For example [-1,0) U (0,1]. - - string special predefined strings like NONE and DNE. - -These forms are what are used in the strings passed to Formula(). -If you want to create versions of these in perl, there are several -ways to do it. One way is to use the Compute() command, which takes a -string parses it and then evaluates the result (it is equivalent to -Formula(...)->eval). If the formula produces a vector, the result -will be a Vector constant that you can use in perl formulas by hand. - -For example: - - $v = Compute("<1,1,0> >< <-1,4,-2>"); - -would compute the dot product of the two vectors and assign the -resulting vector object to $v. - -Another way to generate constants of the various types is to use the -following routines. If their inputs are constant, they produce a -constant of the appropriate type. If an input is a formula, they -produce corresponding formula objects. - - Real(a) create a real number with "fuzzy" - comparisons (so that 1.0000001 == Real(1) is true). - - Complex(a,b) create a complex number a + b i - - Infinity creates the +infinity object - -(Infinity) creates -infinity - - Point(x1,...xn) or Point([x1,...,xn]) produces (x1,...,xn) - - Vector(x1,...,xn) or Vector([x1,...,xn]) produces - - Matrix([a11,...,a1m],...,[am1,...,amn]) or - Matrix([[a11,...,a1m],...,[am1,...,amn]]) produces an n x m matrix - - List(a,...,b) produces a list with the given elements - - Interval('(',a,b,']') produces (a,b], (the other endpoints work as - expected. Use 'INF' and '-INF' for infinities.) - - Union(I1,...,In) takes the union of the n intervals. (where I1 to In - are intervals.) - - String(word) Produces a string object for the given word (if it - is a known word). This is mostly to be able to - call the ->cmp and ->TeX methods. - -For example, - - $a = random(-5,5,1) - $V = Vector($a,1-$a,$a**2+1); - -produces a vector with some random coordinates. - -Objects of these types also have TeX, string and perl methods, so you can -use: - - Vector(1,2,3)->TeX - -to produce a TeX version of the vector, just as you can with formulas. - -There are several "constant" functions that generate common constant -values. These include pi, i, j, k and Infininty. you can use these -in perl expressions as though they were their actual values: - - $z = $a + $b * i; - $v = $a*i + $b*j + $c*k; - $I = Infinity; - -Note that because of a peculiarity of perl, you need to use -(pi) -or - pi (with a space) rather than -pi, and similary for the other -functions. Without this, you will get an error message about an -ambiguity being resolved. (This is not a problem if you process your -expressions through the parser itself, only if you are writing -expressions in perl directly. Note that since student answers are -processed by the parser, not perl directly, they can write -pi without -problems.) - -Another useful command is Compute(), which evaluates a formula and -returns its value. This is one way to create point or vector-valued -constants, but there is an easier way discussed below. - -=head2 Specifying the Context - -You may have noticed that "i" was used in two different ways in the -examples above. In the first example, it was treated as a complex -number and the second as a coordinate unit vector. To control which -interpretation is used, you specify a parser "context". - -The context controls what operations and functions are defined in the -parser, what variables and constants to allow, how to interpret -various paretheses, and so on. Changing the context can completely -change the way a formula is interpreted. - -There are several predefined contexts: Numeric, Complex, Vector, -Interval and Full. (You can also define your own contexts, but that -will be described elsewhere.) To select a context, use the Context() -function, e.g. - - Context("Numeric"); - -selects the numeric context, where i, j and k have no special meaning, -points and vectors can't be used, and the only predefined variable is -'x'. - -On the other hand, Context("Vector") makes i, j and k represent the -unit coordinate vectors, and defines variables 'x', 'y' and 'z'. - -Context("Interval") is like numeric context, but it also defines the -parentheses so that they will form intervals (rather than points or -lists). - -Once you have selected a context, you can modify it to suit the -particular needs of your problem. The command - - $context = Context(); - -gets you a reference to the current context object (you can also use -something like - - $context = Context("Numeric"); - -to set the context and get its reference at the same time). Once you -have this reference, you can call the Context methods to change values -in the context. These are discussed in more detail in the -documentation of the Context object [not yet written], but some of the -more common actions are described here. - -To add a variable, use, for example, - - $context->variables->add(y=>'Real'); - -To delete any existing variables and replace them with new ones, use - - $context->variables->are(t=>'Real'); - -To remove a variable, use - - $context->variables->remove('t'); - -To get the names of the defind variables, use - - @names = $context->variables->names; - - -Similarly, you can add a named constant via - - $context->constants->add(M=>1/log(10)); - -and can change, remove or list the constants via methods like those -used for variables above. The command - - $M = $context->constants->get('M'); - -will return the value of the consant M. (See the -pg/lib/Value/Context/Data.pm file for more information on the methods -you can call for the various types of context data.) - -To add new predefined words (like 'NONE' and 'DNE'), use something -like - - $context->strings->add(TRUE=>{},FALSE=>{}); - -Strings are case-insensitive, unless you say otherwise. To mark a -string as being case-senstive, use - - $context->strings->add(TRUE => {caseSensitive=>1}); - -You may want to privide several forms for the same word; to do so, -make the additional words into aliases: - - $context->strings->add( - T => {alias=>'TRUE'}, - F => {alias=>'FALSE'}, - ); - -so that either "TRUE" or "T" will be interpreted as TRUE, and -similarly for "FALSE" and "F"; - -There are a number of values stored in the context that control things -like the tolerance used when comparing numbers, and so on. You -control these via commands like: - - $context->flags->set(tolerance=>.00001); - -For example, - - $context->flags->set(ijk=>1); - -will cause the output of all vectors to be written in ijk format -rather than <...> format. - -Finally, you can add or modify the operators and functions that are -available in the parser via calls to $context->operators and -$context->functions. See the files in webwork2/docs/parser/extensions -for examples of how to do this. - diff --git a/doc/parser/extensions/1-function.pg b/doc/parser/extensions/1-function.pg deleted file mode 100644 index 1a4179becf..0000000000 --- a/doc/parser/extensions/1-function.pg +++ /dev/null @@ -1,79 +0,0 @@ -########################################################## -# -# Example showing how to add a new single-variable function to the Parser -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserTables.pl", -); - -########################################################### -# -# Use standard numeric mode -# -Context('Numeric'); - -############################################# -# -# Create a 'log2' function to the Parser for log base 2 -# - -package MyFunction1; -our @ISA = qw(Parser::Function::numeric); # this is what makes it R -> R - -sub log2 { - shift; my $x = shift; - return CORE::log($x)/CORE::log(2); -} - -package main; - -# -# Make it work on formulas as well as numbers -# -sub log2 {Parser::Function->call('log2',@_)} - -# -# Add the new functions into the Parser -# - -Context()->functions->add( - log2 => {class => 'MyFunction1', TeX => '\log_2'}, # fancier TeX output -); - -$x = Formula('x'); - -########################################################### -# -# The problem text -# -BEGIN_TEXT - -In this problem, we have added a new function to the Parser: ${BTT}log2(x)${ETT}. -(Edit the code to see how this is done.) -$PAR -Assuming that ${BTT}${DOLLAR}x = Formula('x')${ETT}, it can be used as follows: -$PAR - -\{ParserTable( - 'Formula("log2(x)")', - 'log2(8)', - 'log2($x+1)', - 'Formula("log2(x)")->eval(x=>16)', - '(log2($x))->eval(x=>16)', - 'Formula("log2()")', - 'Formula("log2(1,x)")', - 'log2()', - 'log2(1,3)', - )\} - -END_TEXT - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/extensions/2-function.pg b/doc/parser/extensions/2-function.pg deleted file mode 100644 index 2c68409c74..0000000000 --- a/doc/parser/extensions/2-function.pg +++ /dev/null @@ -1,79 +0,0 @@ -########################################################## -# -# Example showing how to add a new two-variable function to the Parser -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserTables.pl", -); - -########################################################### -# -# Use standard numeric mode -# -Context('Numeric'); - -############################################# -# -# Create a "Combinations" function -# - -package MyFunction2; -our @ISA = qw(Parser::Function::numeric2); # this is what makes it R^2 -> R - -sub C { - shift; my ($n,$r) = @_; my $C = 1; - $r = $n-$r if ($r > $n-$r); # find the smaller of the two - for (1..$r) {$C = $C*($n-$_+1)/$_} - return $C -} - -package main; - -# -# Make it work on formulas as well as numbers -# -sub C {Parser::Function->call('C',@_)} - -# -# Add the new functions into the Parser -# - -Context()->functions->add(C => {class => 'MyFunction2'}); - -$x = Formula('x'); - -########################################################### -# -# The problem text -# -BEGIN_TEXT - -In this problem, we have added a new function to the Parser: ${BTT}C(n,r)${ETT}. -(Edit the code to see how this is done). -$PAR -Assuming that ${BTT}${DOLLAR}x = Formula('x')${ETT}, it can be used as follows: -$PAR - -\{ParserTable( - 'Formula("C(x,3)")', - 'C(6,2)', - 'C($x,3)', - 'Formula("C(x,3)")->eval(x=>6)', - '(C($x,2))->eval(x=>6)', - 'Formula("C(x)")', - 'Formula("C(1,2,3)")', - 'C(1)', - 'C(1,2,3)', - )\} - -END_TEXT - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/extensions/3-operator.pg b/doc/parser/extensions/3-operator.pg deleted file mode 100644 index 1fa295c30a..0000000000 --- a/doc/parser/extensions/3-operator.pg +++ /dev/null @@ -1,109 +0,0 @@ -########################################################## -# -# Example showing how to add new operators to the Parser -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserTables.pl", -); - -########################################################## -# -# Define our own binary operator -# - -package MyOperator; -our @ISA = qw(Parser::BOP); # subclass of Binary OPerator - -# -# Check that the operand types are numbers. -# -sub _check { - my $self = shift; my $name = $self->{bop}; - return if $self->checkNumbers(); - $self->Error("Operands of '$name' must be Numbers"); -} - -# -# Compute the value of n choose r. -# -sub _eval { - shift; my ($n,$r) = @_; my $C = 1; - $r = $n-$r if ($r > $n-$r); # find the smaller of the two - for (1..$r) {$C = $C*($n-$_+1)/$_} - return $C -} - -# -# Non-standard TeX output -# -sub TeX { - my $self = shift; - return '{'.$self->{lop}->TeX.' \choose '.$self->{rop}->TeX.'}'; -} - -# -# Non-standard perl output -# -sub perl { - my $self = shift; - return '(MyOperator->_eval('.$self->{lop}->perl.','.$self->{rop}->perl.'))'; -} - -package main; - -########################################################## -# -# Add the operator into the current context -# - -$prec = Context()->operators->get('+')->{precedence} - .25; - -Context()->operators->add( - '#' => { - class => 'MyOperator', - precedence => $prec, # just below addition - associativity => 'left', # computed left to right - type => 'bin', # binary operator - string => ' # ', # output string for it - TeX => '\mathop{\#}', # TeX version (overridden above, but just an example) - } -); - - -$CHOOSE = MODES(TeX => '\#', HTML => '#'); - - -########################################################### -# -# The problem text -# -BEGIN_TEXT - -In this problem, we have added a new operator to the Parser: ${BTT}n $CHOOSE r${ETT}, -which returns \(n\choose r\). -$PAR - -\{ParserTable( - 'Formula("x # y")', - 'Formula("x+1 # 5")', - 'Formula("x # 5")->eval(x=>7)', - 'Formula("(x#5)+(x#4)")', - 'Formula("x#5+x#4")', - 'Formula("x # y")', - 'Formula("x # y")->substitute(x=>5)', - 'Formula("x # y")->eval(x=>5,y=>2)', - 'Formula("x # y")->perlFunction(~~'C~~'); C(5,2)', - 'Formula("1 # ")', - )\} - -END_TEXT - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/extensions/4-list.pg b/doc/parser/extensions/4-list.pg deleted file mode 100644 index 0184117b22..0000000000 --- a/doc/parser/extensions/4-list.pg +++ /dev/null @@ -1,102 +0,0 @@ -########################################################## -# -# Example showing how to add a new list-type object -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserTables.pl", -); - -########################################################## -# -# Define our own [n,r] notation for n choose r -# - -package MyChoose; -our @ISA = qw(Parser::List); # subclass of List - -# -# Check that two numbers are given -# -sub _check { - my $self = shift; - $self->{type}{list} = 0; # our result is a single number, not really a list - $self->Error("You need two numbers within '[' and ']'") - if ($self->{type}{length} < 2); - $self->Error("Only two numbers can appear within '[' and ']'") - if ($self->{type}{length} > 2); - my ($n,$r) = @{$self->{coords}}; - $self->Error("The arguments for '[n,r]' must be numbers") - unless ($n->type eq 'Number' && $r->type eq 'Number'); - $self->{type} = $Value::Type{number}; -} - -# -# Compute n choose r -# -sub _eval { - shift; my ($n,$r) = @_; my $C = 1; - $r = $n-$r if ($r > $n-$r); # find the smaller of the two - for (1..$r) {$C = $C*($n-$_+1)/$_} - return $C -} - -# -# Non-standard TeX output -# -sub TeX { - my $self = shift; - return '{'.$self->{coords}[0]->TeX.' \choose '.$self->{coords}[1]->TeX.'}'; -} - -# -# Non-standard perl output -# -sub perl { - my $self = shift; - return '(MyChoose->_eval('.$self->{coords}[0]->perl.','.$self->{coords}[1]->perl.'))'; -} - - -package main; - -########################################################## -# -# Add the new list to the context -# - -Context()->lists->add(Choose => {class => 'MyChoose'}); -Context()->parens->replace('[' => {close => ']', type => 'Choose'}); - -########################################################### -# -# The problem text -# -BEGIN_TEXT - -In this problem, we have added a new list to the Parser: ${BTT}[n,r]${ETT}, -which returns \(n\choose r\). -$PAR - -\{ParserTable( - 'Formula("[x,3]")', - 'Formula("[5,3]")', - 'Formula("[x,3]")->eval(x=>5)', - '$C = Formula("[x,y]"); $C->substitute(x=>5)', - 'Formula("[x,y]")->perlFunction("C"); C(5,3)', - 'Formula("[x,y,3]")', - 'Formula("[x]")', - 'Formula("[x,[y,2]]")', - 'Formula("[x,<1,2>]")', - )\} - -END_TEXT - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/extensions/5-operator.pg b/doc/parser/extensions/5-operator.pg deleted file mode 100644 index 60dea5eb2b..0000000000 --- a/doc/parser/extensions/5-operator.pg +++ /dev/null @@ -1,85 +0,0 @@ -########################################################## -# -# Example of how to implement equalities in the Parser -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserTables.pl", -); - -########################################################## -# -# Define our own operator for equality -# - -package Equality; -our @ISA = qw(Parser::BOP); # subclass of Binary OPerator - -# -# Check that the operand types are numbers. -# -sub _check { - my $self = shift; my $name = $self->{bop}; - $self->Error("Only one equality is allowed in an equation") - if ($self->{lop}->class eq 'Equality' || $self->{rop}->class eq 'Equality') ; - $self->Error("Operands of '$name' must be Numbers") unless $self->checkNumbers(); - $self->{type} = Value::Type('Equality',1); # Make it not a number, to get errors with other operations. -} - -# -# Determine if the two sides are equal -# -sub _eval {return ($_[1] == $_[2])? 1: 0} - -package main; - -# -# Add the operator into the current context -# - -$prec = Context()->operators->get(',')->{precedence} + .25; - -Context()->operators->add( - '=' => { - class => 'Equality', - precedence => $prec, # just above comma - associativity => 'left', # computed left to right - type => 'bin', # binary operator - string => '=', # output string for it - perl => '==', # perl string - } -); - - -########################################################### -# -# The problem text -# -BEGIN_TEXT - -In this problem, we have added a new operator to the Parser: ${BTT} a -= b${ETT}, for equality. -$PAR - -\{ParserTable( - 'Formula("x + y = 0")', - 'Formula("x + y = 0")->{tree}->class', - 'Formula("x + y = 0")->{tree}{lop}', - 'Formula("x + y = 0")->{tree}{rop}', - 'Formula("x + y = 0")->eval(x=>2,y=>3)', - 'Formula("x + y = 0")->eval(x=>2,y=>-2)', - 'Formula("x + y = 0 = z")', - 'Formula("(x + y = 0) + 5")', - 'Formula("x + y = 0, 3x-y = 4")', # you CAN get a list of equalities - )\} - -END_TEXT - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/extensions/6-precedence.pg b/doc/parser/extensions/6-precedence.pg deleted file mode 100644 index 5997a2a838..0000000000 --- a/doc/parser/extensions/6-precedence.pg +++ /dev/null @@ -1,80 +0,0 @@ -########################################################## -# -# Example of the non-standard precedences as a possible alternative -# that makes it possible to write "sin 2x" and get "sin(2x)" -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserTables.pl", -); - -########################################################## -# -# Use standard precedences for multiplication -# - -Context()->usePrecedence("Standard"); - -$standard = ParserTable( - 'Formula("sin 2xy/3")', - 'Formula("sin 2x y/3")', - 'Formula("sin 2x y / 3")', - 'Formula("sin 2x+5")', - 'Formula("sin x(x+1)")', - 'Formula("sin x (x+1)")', - 'Formula("1/2xy")', - 'Formula("1/2 xy")', - 'Formula("1/2x y")', - 'Formula("sin^2 x")', - 'Formula("sin^(-1) x")', - 'Formula("x^2x")', -); - -Context()->usePrecedence("Non-Standard"); - -$nonstandard = ParserTable( - 'Formula("sin 2xy/3")', - 'Formula("sin 2x y/3")', - 'Formula("sin 2x y / 3")', - 'Formula("sin 2x+5")', - 'Formula("sin x(x+1)")', - 'Formula("sin x (x+1)")', - 'Formula("1/2xy")', - 'Formula("1/2 xy")', - 'Formula("1/2x y")', - 'Formula("sin^2 x")', - 'Formula("sin^(-1) x")', - 'Formula("x^2x")', -); - - - -########################################################### -# -# The problem text -# -BEGIN_TEXT - -In this problem, we compare the standard and non-standard precedences for -multiplication. -$PAR - -\{Title("The Non-Standard precedences:")\} -$PAR -$nonstandard -$PAR$BR - -\{Title("The Standard precedences:")\} -$PAR -$standard - -END_TEXT - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/extensions/7-context.pg b/doc/parser/extensions/7-context.pg deleted file mode 100644 index 191c22d2c7..0000000000 --- a/doc/parser/extensions/7-context.pg +++ /dev/null @@ -1,82 +0,0 @@ -########################################################## -# -# Example showing how to switch different contexts -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserTables.pl", -); - -BEGIN_TEXT - -In this problem, we compare formulas in complex and vector contexts. -Note the difference between how ${BTT}i${ETT} is treated in the two -contexts. Note that 'Number' comprises both real and complex numbers. -$PAR - -Assuming that ${BTT}${DOLLAR}x = Formula('x')${ETT}, it can be used as follows: -$PAR - -END_TEXT - -$x = Formula('x'); - -########################################################## -# -# Use Complex context -# - -Context('Complex'); - -BEGIN_TEXT -\{Title("The Complex context:")\} -$PAR -\{ParserTable( - 'i', - 'Formula("1+3i")', - 'Formula("x+3i")', - '1 + 3*i', - '$x + 3*i', - '$z = tan(2*i)', - 'Formula("sinh(zi)")', - 'Formula("3i+4j-k")', - 'Formula("3i+4j-k")->eval', - '3*i + 4*j - k', -)\} -$PAR$BR -END_TEXT - - -########################################################## -# -# Use Vector context -# - -Context('Vector'); - -BEGIN_TEXT -\{Title("The Vector context:")\} -$PAR -\{ParserTable( - 'i', - 'Formula("1+3i")', - 'Formula("x+3i")', - '1 + 3*i', - '$x + 3*i', - '$z = tan(2*i)', - 'Formula("sinh(zi)")', - 'Formula("3i+4j-k")', - 'Formula("3i+4j-k")->eval', - '3*i + 4*j - k', -)\} - -END_TEXT - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/extensions/8-answer.pg b/doc/parser/extensions/8-answer.pg deleted file mode 100644 index 9a45e3b720..0000000000 --- a/doc/parser/extensions/8-answer.pg +++ /dev/null @@ -1,110 +0,0 @@ -########################################################## -# -# Example showing an answer checker that uses the parser -# to evaluate the student (and professor's) answers. -# -# This is now obsolete, as the paser's ->cmp method -# can be used to produce an answer checker for any -# of the parser types. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserUtils.pl", -); - -########################################################## -# -# Use Vector context -# - -Context('Vector'); - -########################################################## -# -# Make the answer checker -# -sub vector_cmp { - my $v = shift; - die "vector_cmp requires a vector argument" unless defined $v; - my $v = Vector($v); # covert to vector if it isn't already - my $ans = new AnswerEvaluator; - $ans->ans_hash(type => "vector",correct_ans => $v->string, vector=>$v); - $ans->install_evaluator(~~&vector_cmp_check); - return $ans; -} - -sub vector_cmp_check { - my $ans = shift; my $v = $ans->{vector}, - $ans->score(0); # assume failure - my $f = Parser::Formula($ans->{student_ans}); - my $V = Parser::Evaluate($f); - if (defined $V) { - $V = Formula($V) unless Value::isValue($V); # make sure we can call Value methods - $ans->{preview_latex_string} = $f->TeX; - $ans->{preview_text_string} = $f->string; - $ans->{student_ans} = $V->string; - if ($V->type eq 'Vector') { - $ans->score(1) if ($V == $v); # Let the overloaded == do the check - } else { - $ans->{ans_message} = $ans->{error_message} = - "Your answer doesn't seem to be a Vector (it looks like ".Value::showClass($V).")" - unless $inputs_ref->{previewAnswers}; - } - } else { - # - # Student answer evaluation failed. - # Report the error, with formatting, if possible. - # - my $context = Context(); - my $message = $context->{error}{message}; - if ($context->{error}{pos}) { - my $string = $context->{error}{string}; - my ($s,$e) = @{$context->{error}{pos}}; - $message =~ s/; see.*//; # remove the position from the message - $ans->{student_ans} = protectHTML(substr($string,0,$s)) . - '' . - protectHTML(substr($string,$s,$e-$s)) . - '' . - protectHTML(substr($string,$e)); - } - $ans->{ans_message} = $ans->{error_message} = $message; - } - return $ans; -} - -########################################################## -# -# The problem text -# - -$V = Vector(1,2,3); - -Context()->flags->set(ijk=>0); -Context()->constants->add(a=>1,b=>1,c=>1); - -$ABC = Formula(""); - -BEGIN_TEXT -Enter the vector \(\{$V->TeX\}\) in any way you like: \{ans_rule(20)\}. -$PAR -You can use either \(\{$ABC->TeX\}\) or \(\{$ABC->ijk\}\) notation,$BR -and can perform vector operations to produce your answer. -$PAR -${BBOLD}Note:${EBOLD} This problem is obsolete. -END_TEXT - -########################################################### -# -# The answer -# - -ANS(vector_cmp($V)); - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample01.pg b/doc/parser/problems/sample01.pg deleted file mode 100644 index 2d810de877..0000000000 --- a/doc/parser/problems/sample01.pg +++ /dev/null @@ -1,64 +0,0 @@ -########################################################### -# -# Example showing how to use the Parser to make -# a formula that you can evaluate and print in TeX form. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", -); - -########################################################### -# -# Use standard numeric mode -# -Context('Numeric'); - -# -# Define some functions -# -$a = non_zero_random(-8,8,1); -$b = random(1,8,1); - -@f = ( - "1 + $a*x + $b x^2", - "$a / (1 + $b x)", - "$a x^3 + $b", - "($a - x) / ($b + x^2)" -); - -# -# Pick one at random -# -$k = random(0,$#f,1); -$f = Formula($f[$k])->reduce; - -# -# Where to evaluate it -# -$x = random(-5,5,1); - -########################################################### -# -# The problem text -# -BEGIN_TEXT - -If \(\displaystyle f(x) = \{$f->TeX\}\) then \(f($x)=\) \{ans_rule(10)\}. - -END_TEXT - -########################################################### -# -# The answer -# -ANS(num_cmp($f->eval(x=>$x))); -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample02.pg b/doc/parser/problems/sample02.pg deleted file mode 100644 index b57c1fadb2..0000000000 --- a/doc/parser/problems/sample02.pg +++ /dev/null @@ -1,57 +0,0 @@ -########################################################### -# -# Example showing how you can use perl expressions (not -# just character strings) to generate formulas. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", -); - -########################################################### -# -# Use standard numeric mode -# -Context('Numeric'); -$x = Formula('x'); # used to construct formulas below. - -# -# Define a function and its derivative and make them pretty -# -$a = random(1,8,1); -$b = random(-8,8,1); -$c = random(-8,8,1); - -$f = ($a*$x**2 + $b*$x + $c) -> reduce; -$df = (2*$a*$x + $b) -> reduce; - -$x = random(-8,8,1); - -########################################################### -# -# The problem text -# -BEGIN_TEXT - -Suppose \(f(x) = \{$f->TeX\}\). -$PAR -Then \(f'(x)=\) \{ans_rule(20)\},$BR -and \(f'($x)=\) \{ans_rule(20)\}. - -END_TEXT - -########################################################### -# -# The answers -# -ANS(fun_cmp($df->string)); -ANS(num_cmp($df->eval(x=>$x))); -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample03.pg b/doc/parser/problems/sample03.pg deleted file mode 100644 index d473412c87..0000000000 --- a/doc/parser/problems/sample03.pg +++ /dev/null @@ -1,61 +0,0 @@ -########################################################### -# -# Example showing how to use the Parser's differentiation -# capabilities. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "Differentiation.pl", -); - -########################################################### -# -# Use standard numeric mode -# -Context('Numeric'); -$x = Formula('x'); # used to construct formulas below. - -# -# Define a function and its derivative and make them pretty -# -$a = random(1,8,1); -$b = random(-8,8,1); -$c = random(-8,8,1); - -$f = ($a*$x**2 + $b*$x + $c) -> reduce; -$df = $f->D('x'); - -$x = random(-8,8,1); - -########################################################### -# -# The problem text -# -BEGIN_TEXT - -Suppose \(f(x) = \{$f->TeX\}\). -$PAR -Then \(f'(x)=\) \{ans_rule(20)\},$BR -and \(f'($x)=\) \{ans_rule(20)\}. -$PAR -(Same as previous problem, but using the formal differentiation package. -Note that automatic differentiation does not always produce the simples form.) - -END_TEXT - -########################################################### -# -# The answers -# -ANS(fun_cmp($df->string)); -ANS(num_cmp($df->eval(x=>$x))); -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample04.pg b/doc/parser/problems/sample04.pg deleted file mode 100644 index ea39ec7192..0000000000 --- a/doc/parser/problems/sample04.pg +++ /dev/null @@ -1,113 +0,0 @@ -################################################################ -# -# Example showing how to use the Parser to create functions you -# can call from perl, to substitute values into a formula, and to -# convert a formula to a form that can be used in graphics generated -# on the fly. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PG.pl", - "PGbasicmacros.pl", - "PGanswermacros.pl", - "PGgraphmacros.pl", - "Parser.pl", - "parserUtils.pl", -); - -############################################## -# -# The setup -# - -Context('Vector'); -Context()->variables->add(a => 'Real', b => 'Real'); -$a = non_zero_random(-4,-1,1); -$b = non_zero_random(-3,3,1); - -# -# The function to plot -# -$f = Formula("ax^2 + by"); # the function to display - -# -# Traces to show -# -$x = non_zero_random(-1,1,1); -$y = non_zero_random(-1,1,1); - -# -# Graph domain and size -# -($xm,$xM) = (-2,2); -($ym,$yM) = (-2,2); -($zm,$zM) = (-5,5); -$size = [200,300]; -$tex_size = 350; - -############################################## - -# -# The plot defaults -# -@Goptions = ( - $ym,$zm,$yM,$zM, # dimensions of graph - axes => [0,0], grid => [$yM-$ym,$zM-$zm], # number of grid lines - size => $size # pixel dimension -); -@imageoptions = (size=>$size, tex_size=>$tex_size); - -$xdomain = "x in <$xm,$xM>"; -#$ydomain = "y in <$ym,$yM>"; # plot_functions only handles variable x -$ydomain = "x in <$ym,$yM>"; -$plotoptions = "using color:red and weight:2"; - -# -# Make the traces -# -$fx = $f->substitute(x=>$x, a=>$a, b=>$b, y=>'x')->reduce; # must have variable x -$Gx = init_graph(@Goptions); -plot_functions($Gx,"$fx for $ydomain $plotoptions"); -$Xtrace = Image($Gx,@imageoptions); - -$fy = $f->substitute(y=>$y, a=>$a, b=>$b)->reduce; -$Gy = init_graph(@Goptions); -plot_functions($Gy,"$fy for $xdomain $plotoptions"); -$Ytrace = Image($Gy,@imageoptions); - -# -# Make the table of images -# -@rowopts = (indent=>0, separation=>30); -$Images = - BeginTable(). - AlignedRow([$Xtrace,$Ytrace], @rowopts). - AlignedRow(["Trace for \(x=$x\)","Trace for \(y=$y\)"], @rowopts). - EndTable(); - -############################################## - -BEGIN_TEXT - -The graphs below are traces for a function \(f(x,y)\) at \(x=$x\) and -\(y=$y\). -$PAR - -$Images -$PAR - -If \(f(x,y) = \{$f->TeX\}\) then -\(a\) = \{ans_rule(6)\} and \(b\) = \{ans_rule(6)\}. - -END_TEXT - -################################################## - -ANS(std_num_cmp($a)); -ANS(std_num_cmp($b)); - -################################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample05.pg b/doc/parser/problems/sample05.pg deleted file mode 100644 index 35ba15f09f..0000000000 --- a/doc/parser/problems/sample05.pg +++ /dev/null @@ -1,137 +0,0 @@ -################################################################ -# -# A more complex example showing how to use the Parser to create -# functions you can call from perl, to substitute values into a -# formula, and to convert a formula to a form that can be used in -# graphics generated on the fly. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "PGgraphmacros.pl", - "PGauxiliaryFunctions.pl", - "Parser.pl", - "parserUtils.pl", -); - -############################################## -# -# The setup -# - -Context('Vector'); -Context()->variables->add(a => 'Real', b => 'Real'); - -$c = non_zero_random(-1,1,1); -$a = $c*random(2,5,1)/2; -$b = -$c*random(2,5,1)/2; - -# -# The function to plot -# -$f = Formula("a x^2 y + b x y^2"); -$f->substitute(a=>$a,b=>$b)->perlFunction('f'); - -# -# Traces to show -# -$x1 = non_zero_random(-2,2,1); $x1 /= 2 if (abs($b) >= 2 && abs($x1) == 2); -$x2 = non_zero_random(-2,2,1); $x2 /= 2 if (abs($a) >= 2 && abs($x2) == 2); - -$x = max(.5,min(3,round(-2*$b*$x2/$a)/2)); -$y = max(.5,min(3,round(-2*$a*$x1/$b)/2)); - -# -# Points to show -# -$xv = round(-$b*$y/$a/2); $xv = 1 if ($xv == 0); -$fxv = f($xv,$y); if (abs($fxv) < .75) {$xv = -$xv; $fxv = f($xv,$y)} - -$yv = round(-$a*$x/$b/2); $yv = -1 if ($yv == 0); -$fyv = f($x,$yv); if (abs($fyv) < .75) {$yv = -$yv; $fyv = f($x,$yv)} - -$M = int(max(abs($fxv),abs($fyv),4))+1; -# -# Graph size -# -($xm,$xM) = (-3,3); -($ym,$yM) = (-3,3); -($zm,$zM) = (-$M,$M); -$size = [200,250]; -$tex_size = 350; - -############################################## - -# -# The plot defaults -# -@Goptions = ( - $ym,$zm,$yM,$zM, # dimensions of graph - axes => [0,0], grid => [$yM-$ym,$zM-$zm], # number of grid lines - size => $size # pixel dimension -); -@imageoptions = (size=>$size, tex_size=>$tex_size); - -$plotoptions = "using color:red and weight:2"; - -# -# Make the traces -# -$fx = $f->substitute(x => x, a => $a, b => $b, y => 'x')->reduce; -$Gx = init_graph(@Goptions); -plot_functions($Gx, - "$fx for x in <$ym,$yv] $plotoptions", - "$fx for x in <$yv,$yM> $plotoptions", -); -$Xtrace = Image($Gx,@imageoptions); - -$fy = $f->substitute(y => $y, a => $a, b => $b)->reduce; -$Gy = init_graph(@Goptions); -plot_functions($Gy, - "$fy for x in <$xm,$xv] $plotoptions", - "$fy for x in <$xv,$xM> $plotoptions", -); -$Ytrace = Image($Gy,@imageoptions); - -Context()->texStrings; - -# -# Make the table of images -# -@rowopts = (indent=>0, separation=>30); -$Images = - BeginTable(). - AlignedRow([$Xtrace,$Ytrace], @rowopts). - AlignedRow(["Trace for \(x=$x\) has","Trace for \(y=$y\) has"], @rowopts). - AlignedRow(["a point at \(($yv,$fyv)\).","a point at \(($xv,$fxv)\)."], @rowopts). - EndTable(); - -############################################## - -BEGIN_TEXT - -The graphs below are traces for a function \(f(x,y)\) at \(x=$x\) and -\(y=$y\). -$PAR - -$Images -$PAR - -If \(f(x,y) = \{$f->TeX\}\) then -\(a\) = \{ans_rule(6)\} and \(b\) = \{ans_rule(6)\}. - -END_TEXT - -Context()->normalStrings; - -################################################## - -ANS(std_num_cmp($a)); -ANS(std_num_cmp($b)); - -################################################## - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample06.pg b/doc/parser/problems/sample06.pg deleted file mode 100644 index e4d8102bee..0000000000 --- a/doc/parser/problems/sample06.pg +++ /dev/null @@ -1,53 +0,0 @@ -########################################################### -# -# Example showing how to use the Parser to make -# a formula that you can evaluate and print in TeX form. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", -); - -########################################################### -# -# The setup -# -Context('Vector'); - -# -# Define a vector -# -$a = non_zero_random(-8,8,1); -$b = non_zero_random(-8,8,1); -$c = non_zero_random(-8,8,1); - -$V = $a*i + $b*j + $c*k; # equivalently: $V = Vector($a,$b,$c); - -########################################################### -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -The length of the vector \($V\) is \{ans_rule(20)\}. - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answer -# - -ANS(num_cmp(norm($V)->value)); # easier: ANS($V->cmp) -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample07.pg b/doc/parser/problems/sample07.pg deleted file mode 100644 index 64a320dc06..0000000000 --- a/doc/parser/problems/sample07.pg +++ /dev/null @@ -1,49 +0,0 @@ -########################################################## -# -# Example showing how to use the built-in answer checker for parsed values. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", -); - -########################################################## -# -# The setup -# - -Context('Numeric'); - -$a = Real(random(2,6,1)); -$b = Real(random($a+1,$a+8,1)); - -$c = sqrt($a**2 + $b**2); # still a Real object - -########################################################## -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose the legs of a triangle are of length \($a\) and \($b\).$BR -Then the hypoteneuse is of length \{ans_rule(20)\}. - -END_TEXT -Context()->normalStrings(); - -########################################################### -# -# The answer -# - -ANS($c->cmp); - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample08.pg b/doc/parser/problems/sample08.pg deleted file mode 100644 index 6c7b6d73d1..0000000000 --- a/doc/parser/problems/sample08.pg +++ /dev/null @@ -1,49 +0,0 @@ -########################################################## -# -# Example showing how to use the built-in answer checker for parsed values. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", -); - -########################################################## -# -# The setup -# - -Context('Complex'); - -$z = random(-5,5,1) + non_zero_random(-5,5,1)*i; - -$f = Formula('z^2 + 2z - 1'); -$fz = $f->eval(z => $z); - -########################################################## -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(f(z) = $f\).$BR -Then \(f($z)\) = \{ans_rule(20)\}. - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answer -# - -ANS($fz->cmp); - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample09.pg b/doc/parser/problems/sample09.pg deleted file mode 100644 index b2a9776653..0000000000 --- a/doc/parser/problems/sample09.pg +++ /dev/null @@ -1,49 +0,0 @@ -########################################################## -# -# Example showing how to use the built-in answer checker for parsed values. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", -); - -########################################################## -# -# The setup -# - -Context('Vector'); - -$P1 = Point(-2,4,2); -$P2 = Point(2,-3,1); - -$M = ($P1+$P2)/2; - -########################################################## -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -The midpoint of the line segment from \($P1\) to \($P2\) -is \{ans_rule(20)\}. - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answer -# - -ANS($M->cmp); - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample10.pg b/doc/parser/problems/sample10.pg deleted file mode 100644 index 5a25b8e800..0000000000 --- a/doc/parser/problems/sample10.pg +++ /dev/null @@ -1,54 +0,0 @@ -########################################################## -# -# Example showing how to use the built-in answer checker for parsed values. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", -); - -########################################################## -# -# The setup -# - -Context('Vector'); - -$P1 = Point(1,random(-3,3,1),random(-3,3,1)); -$P2 = Point(random(-3,3,1),4,random(-3,3,1)); - -$V = Vector($P2-$P1); - -Context()->flags->set(ijk=>0); -Context()->constants->add(a=>1,b=>1,c=>1); - -$ABC = Formula(""); - -########################################################## -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT -The vector from \($P1\) to \($P2\) is \{ans_rule(20)\}. -$PAR -You can use either \($ABC\) or \(\{$ABC->ijk\}\) notation,$BR -and can perform vector operations to produce your answer. -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answer -# - -ANS($V->cmp(promotePoints=>1)); # allow answers to be points or vectors - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample11.pg b/doc/parser/problems/sample11.pg deleted file mode 100644 index 8a1ff5e41d..0000000000 --- a/doc/parser/problems/sample11.pg +++ /dev/null @@ -1,48 +0,0 @@ -########################################################## -# -# Example showing how to use the built-in answer checker for parsed values. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", -); - -########################################################## -# -# The setup -# - -Context('Interval'); - -$p1 = random(-5,2,1); -$p2 = random($p1+1,$p1+7,1); - -$f = Formula("x^2 - ($p1+$p2) x + $p1*$p2")->reduce; -$I = Interval("($p1,$p2)"); - -########################################################## -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT -The function \(f(x) = $f\) is negative for values of \(x\) in the interval -\{ans_rule(20)\}. -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answer -# - -ANS($I->cmp); - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample12.pg b/doc/parser/problems/sample12.pg deleted file mode 100644 index 36222469d9..0000000000 --- a/doc/parser/problems/sample12.pg +++ /dev/null @@ -1,60 +0,0 @@ -########################################################## -# -# Example showing how to use the built-in answer checker for parsed values. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserUtils.pl", -); - -########################################################## -# -# The setup -# - -Context("Interval"); - -$a = non_zero_random(-5,5,1); -$f = Formula("(x^2+1)/(x-$a)")->reduce; -$R = Union("(-inf,$a) U ($a,inf)"); - -########################################################## -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(\displaystyle f(x) = $f\). -$PAR -Then \(f\) is defined on the region \{ans_rule(30)\}. -$PAR -${BCENTER} -${BSMALL} -Several intervals can be combined using the -set union symbol, ${LQ}${BTT}U${ETT}${RQ}.$BR -Use ${LQ}${BTT}infinity${ETT}${RQ} for ${LQ}\(\infty\)${RQ} and -${LQ}${BTT}-infinity${ETT}${RQ} for ${LQ}\(-\infty\)${RQ}. -${ESMALL} -${ECENTER} - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answer -# - -ANS($R->cmp); -$showPartialCorrectAnswers=1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample13.pg b/doc/parser/problems/sample13.pg deleted file mode 100644 index 5ad161d529..0000000000 --- a/doc/parser/problems/sample13.pg +++ /dev/null @@ -1,59 +0,0 @@ -########################################################## -# -# Example showing how to use the built-in answer checker for parsed values. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserUtils.pl", -); - -########################################################## -# -# The setup -# - -Context("Interval"); - -$a = non_zero_random(-5,5,1); -$f = Formula("(x^2+1)/(x-$a)")->reduce; -$R = Compute("(-inf,$a),($a,inf)"); - -########################################################## -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(\displaystyle f(x) = $f\). -$PAR -Then \(f\) is defined on the intervals \{ans_rule(30)\}. -$PAR -${BCENTER} -${BSMALL} -To enter more than one interval, separate them by commas.$BR -Use ${LQ}${BTT}infinity${ETT}${RQ} for ${LQ}\(\infty\)${RQ} and -${LQ}${BTT}-infinity${ETT}${RQ} for ${LQ}\(-\infty\)${RQ}. -${ESMALL} -${ECENTER} - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answer -# - -ANS($R->cmp); -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample14.pg b/doc/parser/problems/sample14.pg deleted file mode 100644 index 5ffef3f161..0000000000 --- a/doc/parser/problems/sample14.pg +++ /dev/null @@ -1,57 +0,0 @@ -########################################################## -# -# Example showing how to use the built-in answer checker for parsed values. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserUtils.pl", -); - -########################################################## -# -# The setup -# - -Context("Numeric"); - -$a = random(1,5,1); -$f = Formula("(x^2+1)/(x^2-$a^2)")->reduce; - -########################################################## -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(\displaystyle f(x) = $f\). -$PAR -Then \(f\) is defined for all \(x\) except for \{ans_rule(30)\}. -$PAR -${BCENTER} -${BSMALL} -To enter more than one value, separate them by commas.$BR -Enter ${LQ}${BTT}NONE${ETT}${RQ} if there are no such values. -${ESMALL} -${ECENTER} - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answer -# - -ANS(List($a,-$a)->cmp); -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample15.pg b/doc/parser/problems/sample15.pg deleted file mode 100644 index de84abe8b1..0000000000 --- a/doc/parser/problems/sample15.pg +++ /dev/null @@ -1,57 +0,0 @@ -########################################################## -# -# Example showing how to use the built-in answer checker for parsed values. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserUtils.pl", -); - -########################################################## -# -# The setup -# - -Context("Numeric"); - -$a = random(1,5,1); -$f = Formula("(x^2-$a)/(x^2+$a)"); - -########################################################## -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(\displaystyle f(x) = $f\). -$PAR -Then \(f\) is defined for all \(x\) except for \{ans_rule(30)\}. -$PAR -${BCENTER} -${BSMALL} -To enter more than one value, separate them by commas.$BR -Enter ${LQ}${BTT}NONE${ETT}${RQ} if there are no such values. -${ESMALL} -${ECENTER} - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answer -# - -ANS(List("NONE")->cmp); -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample16.pg b/doc/parser/problems/sample16.pg deleted file mode 100644 index 4317142848..0000000000 --- a/doc/parser/problems/sample16.pg +++ /dev/null @@ -1,61 +0,0 @@ -########################################################### -# -# Example showing how to use the Parser's function -# answer checker. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "Differentiation.pl", -); - -########################################################### -# -# The setup -# -Context('Numeric'); -$x = Formula('x'); # used to construct formulas below. - -# -# Define a function and its derivative and make them pretty -# -$a = random(1,8,1); -$b = random(-8,8,1); -$c = random(-8,8,1); - -$f = ($a*$x**2 + $b*$x + $c) -> reduce; -$df = $f->D('x'); - -$x = random(-8,8,1); - -########################################################### -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(f(x) = $f\). -$PAR -Then \(f'(x)=\) \{ans_rule(20)\},$BR -and \(f'($x)=\) \{ans_rule(20)\}. - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answers -# -ANS($df->cmp); -ANS($df->eval(x=>$x)->cmp); -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample17.pg b/doc/parser/problems/sample17.pg deleted file mode 100644 index 4fea5b52da..0000000000 --- a/doc/parser/problems/sample17.pg +++ /dev/null @@ -1,62 +0,0 @@ -########################################################### -# -# Example showing how to use the Parser's function -# answer checker. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "Differentiation.pl", -); - -########################################################### -# -# The setup -# -Context('Numeric')->variables->add(y=>'Real'); -$x = Formula('x'); # used to construct formulas below. -$y = Formula('y'); - -# -# Define a function and its derivative and make them pretty -# -$a = random(1,8,1); -$b = random(-8,8,1); -$c = random(-8,8,1); - -$f = ($a*$x**2 + $b*$x*$y + $c*$y**2) -> reduce; -$fx = $f->D('x'); -$fy = $f->D('y'); - -########################################################### -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(f(x,y) = $f\). -$PAR -Then \(f_x(x,y) =\) \{ans_rule(30)\},$BR -and \(f_y(x,y) =\) \{ans_rule(30)\}. - - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answers -# -ANS($fx->cmp); -ANS($fy->cmp); -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample18.pg b/doc/parser/problems/sample18.pg deleted file mode 100644 index 8d6fa17afd..0000000000 --- a/doc/parser/problems/sample18.pg +++ /dev/null @@ -1,61 +0,0 @@ -########################################################### -# -# Example showing how to use the Parser's function -# answer checker. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "Differentiation.pl", -); - -########################################################### -# -# The setup -# -Context('Vector')->variables->are(t=>'Real'); - -# -# Define a function and its derivative and make them pretty -# -$a = random(1,8,1); -$b = random(-8,8,1); -$c = random(-8,8,1); - -$f = Formula("") -> reduce; -$df = $f->D('t'); - -$t = random(-5,5,1); - -########################################################### -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(f(t) = $f\). -$PAR -Then \(f'(t) =\) \{ans_rule(20)\},$BR -and \(f'($t) =\) \{ans_rule(20)\}. - - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answers -# -ANS($df->cmp); -ANS($df->eval(t=>$t)->cmp); -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample19.pg b/doc/parser/problems/sample19.pg deleted file mode 100644 index a0c0cf0e9a..0000000000 --- a/doc/parser/problems/sample19.pg +++ /dev/null @@ -1,65 +0,0 @@ -########################################################### -# -# Example showing how to use the Parser's function -# answer checker. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserUtils.pl", -); - -########################################################### -# -# The setup -# -Context('Interval')->variables->add(a=>'Real'); -$x = Formula('x'); $a = Formula('a'); - -$f = log($x-$a); -$I = Formula("(-infinity,a]"); - -########################################################### -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(f(x) = $f\). -$PAR -Then \(f\) is undefined for \(x\) in the interval(s) -\{ans_rule(20)\}. -$PAR -${BCENTER} -${BSMALL} -To enter more than one interval, separate them by commas.$BR -Use ${LQ}${BTT}infinity${ETT}${RQ} for ${LQ}\(\infty\)${RQ} and -${LQ}${BTT}-infinity${ETT}${RQ} for ${LQ}\(-\infty\)${RQ}.$BR -Enter ${LQ}${BTT}NONE${ETT}${RQ} if the function is always defined. -${ESMALL} -${ECENTER} - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answers -# -ANS(List($I)->cmp( - list_type => 'a list of intervals', # override these names to avoid - entry_type => "an interval", # 'formula returning ...' messages -)); -Context()->variables->remove('x'); # error if 'x' is used in answer - -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample20.pg b/doc/parser/problems/sample20.pg deleted file mode 100644 index ecde485d70..0000000000 --- a/doc/parser/problems/sample20.pg +++ /dev/null @@ -1,60 +0,0 @@ -########################################################### -# -# Example showing how to use the Parser's function -# answer checker. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", -); - -########################################################### -# -# The setup -# -Context('Numeric')->variables->are( - x=>'Real',y=>'Real', - s=>'Real',t=>'Real' -); -$x = Formula('x'); $y = Formula('y'); - -$a = random(1,5,1); -$b = random(-5,5,1); -$c = random(-5,5,1); - -$f = ($a*$x**2 + $b*$x*$y + $c*$y**2) -> reduce; - -$x = random(-5,5,1); -$y = random(-5,5,1); - -########################################################### -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(f(x) = $f\). -$PAR -Then \(f($x,$y)\) = \{ans_rule(20)\},$BR -and \(f(s+t,s-t)\) = \{ans_rule(30)\}. - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answers -# -ANS($f->eval(x=>$x,y=>$y)->cmp); -ANS($f->substitute(x=>'s+t',y=>'s-t')->cmp); -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample21.pg b/doc/parser/problems/sample21.pg deleted file mode 100644 index bf5c213f85..0000000000 --- a/doc/parser/problems/sample21.pg +++ /dev/null @@ -1,62 +0,0 @@ -########################################################### -# -# Example showing how to use the Parser's function -# answer checker. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserUtils.pl", -); - -########################################################### -# -# The setup -# -Context('Vector')->variables->are(x=>'Real',y=>'Real'); -$x = Formula('x'); $y = Formula('y'); - -$a = random(1,16,1); -$b = non_zero_random(-5,5,1); - -$f = ($x**2 + $a*$y**2 + $b*$x**2*$y) -> reduce; - -$x = sqrt(2*$a)/$b; $y = -1/$b; - -########################################################### -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(f(x,y) = $f\). -$PAR -Then \(f\) has critical points at the following -points: \{ans_rule(30)\}. -$PAR -${BCENTER} -${BSMALL} -To enter more than one point, separate them by commas.$BR -Enter ${LQ}${BTT}NONE${ETT}${RQ} if there are none. -${ESMALL} -${ECENTER} - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answers -# -ANS(List(Point(0,0),Point($x,$y),Point(-$x,$y))->cmp); -$showPartialCorrectAnswers = 1; - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/doc/parser/problems/sample22.pg b/doc/parser/problems/sample22.pg deleted file mode 100644 index ba43b73657..0000000000 --- a/doc/parser/problems/sample22.pg +++ /dev/null @@ -1,58 +0,0 @@ -########################################################### -# -# Example showing how to use the Parser's function -# answer checker. -# - -DOCUMENT(); # This should be the first executable line in the problem. - -loadMacros( - "PGbasicmacros.pl", - "PGanswermacros.pl", - "Parser.pl", - "parserUtils.pl", -); - -########################################################### -# -# The setup -# -$context = Context('Vector'); -$context->variables->are(t=>'Real'); -$context->constants->add( - p0 => Point(pi,sqrt(2),3/exp(1)), - v => Vector(exp(1),log(10),-(pi**2)), -); -$context->constants->set(v => {TeX => '\boldsymbol{v}'}); # make it print nicer - -$L = Formula("p0+tv"); -$v = Formula('v'); - -########################################################### -# -# The problem text -# - -Context()->texStrings; -BEGIN_TEXT - -Suppose \(p_0\) is a point and \($v\) a vector in \(n\)-space. -$PAR -Then the vector-parametric form for the line through \(p_0\) in the -direction of \(v\) is$PAR -${BBLOCKQUOTE} -\(L(t)\) = \{ans_rule(30)\}. -${EBLOCKQUOTE} - -END_TEXT -Context()->normalStrings; - -########################################################### -# -# The answers -# -ANS($L->cmp); - -########################################################### - -ENDDOCUMENT(); # This should be the last executable line in the problem. diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000000..ff2a7dc397 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,146 @@ +# docker-compose.test.yml — run the WeBWorK 2.20 (PR #52) stack locally, +# BUILDING the app image from this checkout (no pull from Docker Hub). +# +# Run this from a merge-upstream-2.20 checkout — Dockerfile-prod does +# `COPY . /opt/webwork/webwork2`, so the build uses THIS working directory's +# webwork2 source (PG 2.20 and the UBC OPL are cloned during the build). +# +# Quick start: +# docker compose -f docker-compose.test.yml up --build -d +# # first build is long (~15-30 min: apt + cpanm + npm assets + OPL clone). +# docker compose -f docker-compose.test.yml logs -f app # watch first boot +# open http://localhost:8080/webwork2/admin # login: admin / admin +# open http://localhost:8071 # maildev: outgoing mail +# docker compose -f docker-compose.test.yml down -v # stop + wipe volumes +# +# Rebuild after code changes: docker compose -f docker-compose.test.yml up --build -d +# +# Notes: +# - The image builds for the host architecture. To match the amd64 prod/CI image +# exactly (e.g. on Apple Silicon), add platform: linux/amd64 under app.build +# and to the worker `image:` refs — the build then runs under emulation (slower). +# - `app` forces $CookieSecure=0 (see the entrypoint) so login works over plain +# http://localhost; the trailing `1;` keeps localOverrides.conf returning true. +# - 2FA is on by default (WeBWorK 2.20); add $twoFA{enabled} = 0; to the printf +# to skip TOTP setup locally. + +x-app-env: &app-env + WEBWORK_DB_DRIVER: MariaDB + WEBWORK_DB_HOST: db + WEBWORK_DB_PORT: "3306" + WEBWORK_DB_NAME: webwork + WEBWORK_DB_USER: webwork + WEBWORK_DB_PASSWORD: webwork + WEBWORK_SECRET: local-test-secret-change-me + WEBWORK_URL: /webwork2 + WEBWORK_ROOT_URL: http://localhost:8080 + WEBWORK_TIMEZONE: America/Vancouver + SYSTEM_TIMEZONE: America/Vancouver + WEBWORK_SMTP_SERVER: maildev + WEBWORK_SMTP_PORT: "1025" + WEBWORK_SMTP_SENDER: no-reply@example.edu + SKIP_UPLOAD_OPL_STATISTICS: "1" + MOJO_PUBSUB_EXPERIMENTAL: "1" + DELAYED_JOB_PRIORITIZE: "0" + DEBUG: "1" + LTI_ADMIN_PASSWORD: admin + LTI_ADMIN_TOTP: O5SVGPKRNYWWEQKDPJJWUNRUL4UCWVDD + +services: + db: + image: mariadb:10.11 + command: + - "--max-allowed-packet=256M" + - "--character-set-server=utf8mb4" + - "--collation-server=utf8mb4_unicode_ci" + environment: + MARIADB_ROOT_PASSWORD: root-test-pw + MARIADB_DATABASE: webwork + MARIADB_USER: webwork + MARIADB_PASSWORD: webwork + volumes: + - mysql:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 18 + restart: unless-stopped + + r: + image: ubcctlt/rserve + platform: linux/amd64 # image is built amd64-only (emulated on Apple Silicon) + restart: unless-stopped + + app: + build: + context: . + dockerfile: Dockerfile-prod + # Tag the locally built image so the workers reuse it instead of rebuilding. + image: webwork-local:pr52 + depends_on: + db: + condition: service_healthy + r: + condition: service_started + environment: + <<: *app-env + # Local-only: force $CookieSecure=0 so the session cookie works over plain + # http://localhost. The trailing `1;` keeps localOverrides.conf (which is + # do-evaluated) returning a true value. Appended before the entrypoint copies + # the .dist to the live conf, so it survives restarts. + entrypoint: + - /bin/bash + - -c + - | + printf '\n# local test override\n$$CookieSecure = 0;\n1;\n' >> "$$WEBWORK_ROOT/conf/localOverrides.conf.dist" + exec docker-entrypoint.sh sudo -E -u www-data hypnotoad -f bin/webwork2 + ports: + - "8080:8080" + volumes: + - opl:/opt/webwork/libraries/webwork-open-problem-library + - htdocs_data:/opt/webwork/webwork2/htdocs/DATA + healthcheck: + test: ["CMD", "curl", "-fsS", "http://localhost:8080/webwork2"] + interval: 15s + timeout: 10s + retries: 5 + start_period: 360s + restart: unless-stopped + + worker_lti1p3: + image: webwork-local:pr52 # reuse the image built by `app` + depends_on: + app: + condition: service_healthy + environment: + <<: *app-env + volumes: + - opl:/opt/webwork/libraries/webwork-open-problem-library + - htdocs_data:/opt/webwork/webwork2/htdocs/DATA + command: ["sudo", "-E", "-u", "www-data", "./lib/DelayedJob/Run/run_all_jobs.pl"] + restart: unless-stopped + + worker_mojo: + image: webwork-local:pr52 # reuse the image built by `app` + depends_on: + app: + condition: service_healthy + environment: + <<: *app-env + volumes: + - opl:/opt/webwork/libraries/webwork-open-problem-library + - htdocs_data:/opt/webwork/webwork2/htdocs/DATA + command: ["sudo", "-E", "-u", "www-data", "bin/webwork2", "minion", "worker", "-m", "production"] + restart: unless-stopped + + maildev: + image: maildev/maildev + ports: + - "8071:1080" + restart: unless-stopped + +volumes: + mysql: + opl: + htdocs_data: diff --git a/docker-config/docker-compose.dist.yml b/docker-config/docker-compose.dist.yml index 0ba3e0cab6..4c9346ebd9 100644 --- a/docker-config/docker-compose.dist.yml +++ b/docker-config/docker-compose.dist.yml @@ -50,7 +50,7 @@ services: # "/usr/bin/timedatectl list-timezones" on an Ubuntu system with # that tool installed will find valid values. # See: https://stackoverflow.com/questions/39172652/using-docker-compose-to-set-containers-timezones - + # Enable the auto db upgrader when moving to a newer MariaDB version MARIADB_AUTO_UPGRADE: 1 @@ -63,8 +63,7 @@ services: depends_on: - db - app: &app - # Set up the "build:" configuration: + app: &app # Set up the "build:" configuration: build: # For use/building when docker-compose.yml is in the webwork2 directory context: . @@ -204,7 +203,7 @@ services: # If you use https below, make sure to set up the certificate and SSL configuration # Note if your server uses a non-standard port, that should also be included. #WEBWORK_ROOT_URL: https://myhost.mydomain.edu - WEBWORK_ROOT_URL: "http://localhost:${WEBWORK2_HTTP_PORT_ON_HOST}" + WEBWORK_ROOT_URL: 'http://localhost:${WEBWORK2_HTTP_PORT_ON_HOST}' WEBWORK_SMTP_SERVER: maildev WEBWORK_SMTP_PORT: 1025 diff --git a/docker-config/docker-entrypoint.sh b/docker-config/docker-entrypoint.sh index bf923e5bc4..f6a335690e 100755 --- a/docker-config/docker-entrypoint.sh +++ b/docker-config/docker-entrypoint.sh @@ -217,7 +217,8 @@ chmod ug+w htdocs/tmp # Even if the admin and courses directories already existed their permissions # might not be correct. -# chown www-data:www-data $APP_ROOT/courses +chown www-data:www-data $APP_ROOT/courses +chown www-data:www-data $APP_ROOT/courses/admin chown www-data:www-data $APP_ROOT/courses/admin/* set -e diff --git a/docker-config/idp/Dockerfile b/docker-config/idp/Dockerfile new file mode 100644 index 0000000000..876ba9f7d0 --- /dev/null +++ b/docker-config/idp/Dockerfile @@ -0,0 +1,38 @@ +FROM php:8.3-apache +WORKDIR /var/www + +# Install composer and the php extension installer. +COPY --from=composer/composer:2-bin /composer /usr/bin/composer +COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/ + +RUN apt-get update && \ + apt-get -y install git curl vim && \ + install-php-extensions ldap zip + +# Directories used by simplesamlphp. These need to be accessible by the apache2 user. +RUN mkdir simplesamlphp/ /var/cache/simplesamlphp +RUN chown www-data simplesamlphp/ /var/cache/simplesamlphp + +COPY ./idp.apache2.conf /etc/apache2/conf-available +RUN a2enconf idp.apache2 + +# Composer doesn't like to be root, so run the rest as the apache user. +USER www-data + +# Install simplesamlphp +RUN git clone --branch v2.2.1 https://github.com/simplesamlphp/simplesamlphp.git +WORKDIR /var/www/simplesamlphp + +# Generate the server certificates. +RUN cd cert/ && \ + openssl req -newkey rsa:3072 -new -x509 -days 3652 -nodes -out server.crt -keyout server.pem \ + -subj "/C=US/S=New York/L=Rochester/O=WeBWorK/CN=idp.webwork2" + +# Use composer to install dependencies. +RUN composer install && \ + composer require simplesamlphp/simplesamlphp-module-metarefresh + +# Copy configuration files. +COPY ./config/ config/ +COPY ./metadata/ metadata/ + diff --git a/docker-config/idp/README.md b/docker-config/idp/README.md new file mode 100644 index 0000000000..586b9d9953 --- /dev/null +++ b/docker-config/idp/README.md @@ -0,0 +1,174 @@ +# Development identity provider test instance for SAML2 authentication + +A development SAML2 identity provider is provided that uses SimpleSAMLphp. +Instructions for utilizing this instance follow. + +## Webwork2 Configuration + +Copy `/opt/webwork/webwork2/conf/authen_saml2.conf.dist` to +`/opt/webwork/webwork2/conf/authen_saml2.conf`. + +The default `conf/authen_saml2.conf.dist` is configured to use the docker +identity provider. So for the docker build, it should work as is. + +Without the docker build a few changes are needed. + +- Find the `$saml2{idps}{default}` setting and change its value to + `'http://localhost/simplesaml/module.php/saml/idp/metadata'`. +- Find the `$saml2{sp}{entity_id}` setting and change its value to + `'http://localhost:3000/webwork2/saml2'`. +- In the `$saml2{sp}{org}` hash change the `url` to `'https://localhost:3000/'`. + +The above settings assume you will use `morbo` with the default port. Change +the port as needed. + +## Development IdP test instance with docker + +A docker service that implements a SAML2 identity provider is provided in the +`docker-compose.yml.dist` file. To start this identity provider along with the +rest of webwork2, add the `--profile saml2dev` argument to docker compose as in +the following exmaple. + +```bash +docker compose --profile saml2dev up +``` + +Without the profile argument, the identity provider services do not start. + +Stop all docker services with + +```bash +docker compose --profile saml2dev down +``` + +## Development IdP test instance without docker + +Effective development is not done with docker. So it is usually more useful to +set up an identity provider without docker. The following instructions are for +Ubuntu 24.04, but could be adapted for other operating systems. + +A web server and php are needed to serve the SimpleSAMLphp files. Install these +and other dependencies with: + +```bash +sudo apt install \ + apache2 php php-ldap php-zip php-xml php-curl php-sqlite3 php-fpm \ + composer +``` + +Now download the SimpleSAMLphp source, install php dependencies, install the +SimpleSAMLphp metarefresh module, and set file permissions with + +```bash +cd /var/www +sudo mkdir simplesamlphp /var/cache/simplesamlphp +sudo chown $USER:www-data simplesamlphp +sudo chown www-data /var/cache/simplesamlphp +git clone --branch v2.2.1 https://github.com/simplesamlphp/simplesamlphp.git +sudo chown -R $USER:www-data simplesamlphp +sudo chmod -R g+w simplesamlphp +cd simplesamlphp +composer install +composer require simplesamlphp/simplesamlphp-module-metarefresh +``` + +Next, generate certificates for the SimpleSAMLphp identity provider and make +them owned by the `www-data` user with + +```bash +cd /var/www/simplesamlphp/cert +openssl req -newkey rsa:3072 -new -x509 -days 3652 -nodes \ + -out server.crt -keyout server.pem \ + -subj "/C=US/ST=New York/L=Rochester/O=WeBWorK/CN=idp.webwork2" +sudo chown www-data:www-data server.crt server.pem +``` + +Next, copy the `idp` configuration files from `docker-config`. + +```bash +cp /opt/webwork/webwork2/docker-config/idp/config/* /var/www/simplesamlphp/config/ +cp /opt/webwork/webwork2/docker-config/idp/metadata/* /var/www/simplesamlphp/metadata/ +``` + +The configuration files are setup to work with the docker build. So there are +some changes that are needed. + +Edit the file `/var/www/simplesamlphp/config/config.php` and change +`baseurlpath` to `simplesaml/`. + +Edit the file `/var/www/simplesamlphp/metadata/saml20-idp-hosted.php` and change +the line that reads +`$metadata['http://localhost:8180/simplesaml'] = [` +to +`$metadata['http://localhost/simplesaml'] = [`. + +Enable the apache2 idp configuration with + +```bash +sudo cp /opt/webwork/webwork2/docker-config/idp/idp.apache2.conf /etc/apache2/conf-available +sudo a2enconf idp.apache2 php8.3-fpm +``` + +Edit the file `/etc/apache2/conf-available/idp.apache2.conf` and add the line +`SetEnv SP_METADATA_URL http://localhost:3000/webwork2/saml2/metadata` to the +beginning of the file. This again assumes you will use `morbo` with the default +port, so change the port if necessary. + +Restart (or start) apache2 with `sudo systemctl restart apache2`. + +The SimpleSAMLphp identity provider needs to fetch webwork2's service provider +metadata. For this execute + +```bash +curl -f http://localhost/simplesaml/module.php/cron/run/metarefresh/webwork2 +``` + +That is done automatically with the docker build. The command usually only +needs to be done once, but may need to be run again if settings are changed. + +## Identity provider administration + +The identity provider has an admin interface. You can login to the docker +instance with the password 'admin' at +`http://localhost:8180/simplesaml/module.php/admin/federation` +or without docker at +`http://localhost/simplesaml/module.php/admin/federation`. + +The admin interface lets you check if the identity provider has properly +registered the webwork2 service provider under the 'Federation' tab, it should +be listed under the "Trusted entities" section. + +You can also test login with the user accounts listed below in the "Test" tab +under the "example-userpass" authentication source. + +## Single sign-on users + +The following single sign-on accounts are preconfigured: + +- Username: student01, Password: student01 +- Username: instructor01, Password: instructor01 +- Username: staff01, Password: staff01 + +You can add more accounts to the `docker-config/idp/config/authsources.php` file +in the `example-userpass` section. If using docker the identity provider, the +image will need to be rebuilt for the changes to take effect. + +## Troubleshooting + +### "Error retrieving metadata" + +This error message indicates that the Saml2 authentication module wasn't able to +fetch the metadata from the identity provider metadata URL. Make sure the +identity provider is accessible to webwork2. + +### User not found in course + +The user was verified by the identity provider but did not have a corresponding +user account in the Webwork course. The Webwork user account needs to be created +separately as the Saml2 autentication module does not do user provisioning. + +### The WeBWorK service provider does not appear in the service provider Federation tab + +This can occur when using the docker identity provider service because Webwork's +first startup can be slow enough that the IdP wasn't able to successfully fetch +metadata from the webwork2 metadata URL. Restarting everything should fix this. diff --git a/docker-config/idp/certs/saml.crt b/docker-config/idp/certs/saml.crt new file mode 100644 index 0000000000..ca2f952c0f --- /dev/null +++ b/docker-config/idp/certs/saml.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE7zCCA1egAwIBAgIUIteyNYLSAiB0FcNl0GLJNYRppk8wDQYJKoZIhvcNAQEL +BQAwgYYxCzAJBgNVBAYTAkFBMQswCQYDVQQIDAJBQTEQMA4GA1UEBwwHRXhhbXBs +ZTEQMA4GA1UECgwHRXhhbXBsZTEQMA4GA1UECwwHRXhhbXBsZTEQMA4GA1UEAwwH +RXhhbXBsZTEiMCAGCSqGSIb3DQEJARYTZXhhbXBsZUBleGFtcGxlLmVkdTAeFw0y +NDA1MDMwMTA2MzNaFw0zNDA1MDMwMTA2MzNaMIGGMQswCQYDVQQGEwJBQTELMAkG +A1UECAwCQUExEDAOBgNVBAcMB0V4YW1wbGUxEDAOBgNVBAoMB0V4YW1wbGUxEDAO +BgNVBAsMB0V4YW1wbGUxEDAOBgNVBAMMB0V4YW1wbGUxIjAgBgkqhkiG9w0BCQEW +E2V4YW1wbGVAZXhhbXBsZS5lZHUwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGK +AoIBgQC45DCHUejzAeq+eVwEX5zSQWC+kqydEmoxpydT4YiSXnNeoNAkilKfHGOY +Uc4djwx148N14A+S0GCys2j3Ey2wuL7DSep5y1Z9Uxj6Ayg23XGIFFFJJLMy1Qfe +pjCcr1djPH9PpwglG1nTsiWqvHGGc3WWn1u6RfyrCf+jxbhygNRTA+LVPpqNvko6 +MWKsbVLKrYMV2kPcQ0PQByNHJnjBy3KH2k99lS20h32sgHbgbVpJWdAWjeyJrOh9 +aDt4/AfK90BvhkjF4BuQ+Jw5oIwMhbx7YmzIfiJmBLLaGjVRppuoAQtLX9uLst9l +aLZzaeutg+G3RUYcvDMlnP7cU8Sq4BD7uK0ChKxxCMFcihAhQ8wqCKncaaE9WqPs +CM16SB/6xptOxoLcg/5q3PJyUi2g4VDKXuQc6AURKIJxSM9nlrcv/R7fCFgk3Nj/ +piWykDk6/BDWFpEHaj+NnFE9ZIxKr9CjTxdmqiDTyqSv50rNCjleyL/iASBTSCCF +OPVOYQECAwEAAaNTMFEwHQYDVR0OBBYEFGs8F3VIGSEk+DE2MBqqNKX6UuZTMB8G +A1UdIwQYMBaAFGs8F3VIGSEk+DE2MBqqNKX6UuZTMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggGBAIpDktpfGH7ZqgdWvxbJrjekb1IyCGrWsHOYSjwM ++MxnhAA6oY63wC04a2i31zIMNOkY9F0tAdd4uDchxA9IWHqpb7t7zBlZdDabPPC3 +WoDYnKhtZBULVVo7AvWO0UJGfZNJE393aKer3ePvfoG0OpCyrw4eFI/GCd4UjJBF +DnD7hvUxE7RRwOhbuYrtDRuB3Z7CeeP8o81eDVexyuBpM/9UQjYPqBBAfoeYKQzu +ZIhpGRWXw0ntH+EEOWagRXA5pRru61hteParZe4LBjPqisqN4Ek6ZR7MD9gB5xnt +Pn1BKRY08quFOZyaogzwfkYk5SCF8F8jBA8ZNAYwJWe1gtO3iw5vpUaQc2iCabvI +Y+Pc6qsSNwbkl7+sFrVHzI9QZVyz1cARUXxvrgGNLBkYtprkG91k6mCjX90cQspb +ZwHixcQyCNv+4H738e99h/Wf0YzjxFjDKrbGoosYBzWAsYYtzrtsBvw3SJMTXIh7 +OvFMA+rbIL8XWs8oNmZDDh8g0A== +-----END CERTIFICATE----- diff --git a/docker-config/idp/certs/saml.pem b/docker-config/idp/certs/saml.pem new file mode 100644 index 0000000000..65accf00b2 --- /dev/null +++ b/docker-config/idp/certs/saml.pem @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQC45DCHUejzAeq+ +eVwEX5zSQWC+kqydEmoxpydT4YiSXnNeoNAkilKfHGOYUc4djwx148N14A+S0GCy +s2j3Ey2wuL7DSep5y1Z9Uxj6Ayg23XGIFFFJJLMy1QfepjCcr1djPH9PpwglG1nT +siWqvHGGc3WWn1u6RfyrCf+jxbhygNRTA+LVPpqNvko6MWKsbVLKrYMV2kPcQ0PQ +ByNHJnjBy3KH2k99lS20h32sgHbgbVpJWdAWjeyJrOh9aDt4/AfK90BvhkjF4BuQ ++Jw5oIwMhbx7YmzIfiJmBLLaGjVRppuoAQtLX9uLst9laLZzaeutg+G3RUYcvDMl +nP7cU8Sq4BD7uK0ChKxxCMFcihAhQ8wqCKncaaE9WqPsCM16SB/6xptOxoLcg/5q +3PJyUi2g4VDKXuQc6AURKIJxSM9nlrcv/R7fCFgk3Nj/piWykDk6/BDWFpEHaj+N +nFE9ZIxKr9CjTxdmqiDTyqSv50rNCjleyL/iASBTSCCFOPVOYQECAwEAAQKCAYAR +p6iCo22tFrfFrGz+9epRoXCNgg/9h66gQyfcOKMD5wT5Oj3l31d4XgucleMqq2gz +MaaOcPDLwh4ZskwJm8k3IM0GdN5w9tuxZ+fwp7CFXKvkpJwGcfyyk+kGd7QYoh2k +GjjF8Fs0v+HZ9x7lqMzmW8wUr+7gYKJ56qCAkPbF6EteCfb1Cd9UPaF04RZdBKtt +MxhbU9Y7CClHigbyWlgZmUW8dzoz8bTFklKL0FCJqad/bZYTMUYu91XT88oKCXbD +AUxpF2Ikbkfj820XOqq8iV3xGpYszt1aMRpsdXbDAhCqfKoNet2X7jnRWlNXZutC +RIUGm4VUNDNeD4nXW8aLgDa8bNQnvsSmM9DUVuPjbejUs0VN7uwxo8rYqvkAKiBQ +1ZqxoBK4ShZVcqgWE6CUj9FRZ3CVzSzydxZSQzex/ZRYPuYLUhQJFHLVIdJSYhf3 +XTEki0+ndwAB7yP/tBNlcxLftCzAaS7mPLLn1tf0A27QPCSjwOsTLxuJ4WYVkmkC +gcEAuuh8EImBfE9WOg3ITmJpr95WlVi8WE6BHWowV8dQwODQLj+38itDDL1xLn9+ +Vuz4o9AaIBiH5fCr6otun28lVp/sNVdWnBVeioSpu3tGV18OiDNaXtXOo7qkUnBI +Z+V7cD69gJLS6byD3OXlGi42h3XxK4mVlhwQtkQ69qI/zhl6rc0O2/iXXUAFa5T5 +MJ84Cw1B9kHFB/NC27sraee+cwAK0Pogj5WnqaBOIPeIO/f+br65xMUvEYvDD1m4 +TwIzAoHBAP082l0IQ5KHBY4WuFIDOoevO5SxHN5EUp2sPRDZwZxwOrjHxFRXPc/h +pDrVEHEn/4HQ706AHYpED0diumr4gee7gusNIDcGpXwjGVdFmFvxKoDbhz1C5vL3 +xC7qgyS/ZtAopxpCPH3+7IrQyBk8e6He+8F97bA0e9sYSBQSuPLcdKQXGNbLYb6s +yLbP02cB2CNeI1GJMQIOXe9bi9Cz5w+hCGMvEKt5oAz5SLWlPBvv1YATpG5Ux8Wy +RbGPD4zj+wKBwBGVDx6rIMAl4nGhnEcrYM/HdZOk/kq8T88JjzSirkkGnO7M1av1 +P+Bx7bS3D5Zzwkv+poaAaEBMLI/qv+RFm1iTwK+f4KjcJcGYCzN0vEA50+8iDY1A +RakHRK/wmg8T+lGrxT3UEf0k266q/atBz6VchexXi/fL+hJ7RqSuzJvBr9WrpYsx +zmNaQ2hEYlCdmbMIcz0MINHHo3FyIPpcb4D37wyLiwaWyGffiZn2Tx19DbUzQdxt +xCi9YgMOqJTeGwKBwQD4rJ0x5j+U0ApgcWcnAgyj2SwE47eZfDY0p0KAHZXGbV78 +vQ7KU7FbRhTjwP6YX9LEQ8v7pktbz2HBk+3DxayrRrNU5lrQLjKrKDxmOu1WvAgk +6W5wdhYcWbnI6HlHyLzJhGIzov+MKp1V45fbUE2Hs1Q9uc+CzMcja0C8lXYQ5vOT +fyrhIm8lsr6W5paN/H2mnXbJRpNdlYYg2iD+HOu1qUh3PWx9Nr44f0MrPMs+E9Hw +J1m9DnvuYxWVOwrmK6kCgcADfcatftIJWMqeYJsDnB9jJaANmjln2G3bppo9WcIC +lvfXFE+Rf3FleaijVrUFbgxDU2MHh/2VPjJgIQT3QtfqS5+OnF1Z5+uOTGwbDNmT +3Th0IcSt6TjvLJwkanNeSkvc+2lMnuNtH6TQLXB0qEs3D7xND0kFWHfyies+RYNC +eualoZJ/6UL9X2gkPG5jmzXjInEBguAL0ll5yETXgx6v0hXR058TcvPl58j73cCQ +dzDq+xUD8nHpKM33A2EaUFY= +-----END PRIVATE KEY----- diff --git a/docker-config/idp/config/authsources.php b/docker-config/idp/config/authsources.php new file mode 100644 index 0000000000..03740f20bf --- /dev/null +++ b/docker-config/idp/config/authsources.php @@ -0,0 +1,354 @@ + [ + // The default is to use core:AdminPassword, but it can be replaced with + // any authentication source. + + 'core:AdminPassword', + ], + + + // An authentication source which can authenticate against SAML 2.0 IdPs. + //'default-sp' => [ + // 'saml:SP', + + // // The entity ID of this SP. + // 'entityID' => 'https://myapp.example.org/', + + // // The entity ID of the IdP this SP should contact. + // // Can be NULL/unset, in which case the user will be shown a list of available IdPs. + // 'idp' => null, + + // // The URL to the discovery service. + // // Can be NULL/unset, in which case a builtin discovery service will be used. + // 'discoURL' => null, + + // /* + // * If SP behind the SimpleSAMLphp in IdP/SP proxy mode requests + // * AuthnContextClassRef, decide whether the AuthnContextClassRef will be + // * processed by the IdP/SP proxy or if it will be passed to the original + // * IdP in front of the IdP/SP proxy. + // */ + // 'proxymode.passAuthnContextClassRef' => false, + + // /* + // * The attributes parameter must contain an array of desired attributes by the SP. + // * The attributes can be expressed as an array of names or as an associative array + // * in the form of 'friendlyName' => 'name'. This feature requires 'name' to be set. + // * The metadata will then be created as follows: + // * + // */ + // /* + // 'name' => [ + // 'en' => 'A service', + // 'no' => 'En tjeneste', + // ], + + // 'attributes' => [ + // 'attrname' => 'urn:oid:x.x.x.x', + // ], + // 'attributes.required' => [ + // 'urn:oid:x.x.x.x', + // ], + // */ + //], + + /* + 'example-sql' => [ + 'sqlauth:SQL', + 'dsn' => 'pgsql:host=sql.example.org;port=5432;dbname=simplesaml', + 'username' => 'simplesaml', + 'password' => 'secretpassword', + 'query' => 'SELECT uid, givenName, email, eduPersonPrincipalName FROM users WHERE uid = :username ' . + 'AND password = SHA2(CONCAT((SELECT salt FROM users WHERE uid = :username), :password), 256);', + ], + */ + + /* + 'example-static' => [ + 'exampleauth:StaticSource', + 'uid' => ['testuser'], + 'eduPersonAffiliation' => ['member', 'employee'], + 'cn' => ['Test User'], + ], + */ + + 'example-userpass' => [ + 'exampleauth:UserPass', + + // Give the user an option to save their username for future login attempts + // And when enabled, what should the default be, to save the username or not + //'remember.username.enabled' => false, + //'remember.username.checked' => false, + + 'users' => [ + 'student01:student01' => [ + 'uid' => ['student01'], + 'displayName' => 'Student 01', + 'eduPersonAffiliation' => ['student'], + 'mail' => 'student01@example.edu' + ], + 'instructor01:instructor01' => [ + 'uid' => ['instructor01'], + 'displayName' => 'Instructor 01', + 'alt' => '51092d7f-2f38-4a91-bfb0-13a021c02df3', + 'eduPersonAffiliation' => ['faculty', 'student'], + 'mail' => 'instructor01@example.edu' + ], + 'staff01:staff01' => [ + 'uid' => ['staff01'], + 'displayName' => 'Staff 01', + 'eduPersonAffiliation' => ['staff', 'alumni'], + 'mail' => 'staff01@example.edu' + ], + ], + ], + + /* + 'crypto-hash' => [ + 'authcrypt:Hash', + // hashed version of 'verysecret', made with bin/pwgen.php + 'professor:{SSHA256}P6FDTEEIY2EnER9a6P2GwHhI5JDrwBgjQ913oVQjBngmCtrNBUMowA==' => [ + 'uid' => ['prof_a'], + 'eduPersonAffiliation' => ['member', 'employee', 'board'], + ], + ], + */ + + /* + 'htpasswd' => [ + 'authcrypt:Htpasswd', + 'htpasswd_file' => '/var/www/foo.edu/legacy_app/.htpasswd', + 'static_attributes' => [ + 'eduPersonAffiliation' => ['member', 'employee'], + 'Organization' => ['University of Foo'], + ], + ], + */ + + /* + // This authentication source serves as an example of integration with an + // external authentication engine. Take a look at the comment in the beginning + // of modules/exampleauth/lib/Auth/Source/External.php for a description of + // how to adjust it to your own site. + 'example-external' => [ + 'exampleauth:External', + ], + */ + + /* + 'yubikey' => [ + 'authYubiKey:YubiKey', + 'id' => '000', + // 'key' => '012345678', + ], + */ + + /* + 'facebook' => [ + 'authfacebook:Facebook', + // Register your Facebook application on http://www.facebook.com/developers + // App ID or API key (requests with App ID should be faster; https://github.com/facebook/php-sdk/issues/214) + 'api_key' => 'xxxxxxxxxxxxxxxx', + // App Secret + 'secret' => 'xxxxxxxxxxxxxxxx', + // which additional data permissions to request from user + // see http://developers.facebook.com/docs/authentication/permissions/ for the full list + // 'req_perms' => 'email,user_birthday', + // Which additional user profile fields to request. + // When empty, only the app-specific user id and name will be returned + // See https://developers.facebook.com/docs/graph-api/reference/v2.6/user for the full list + // 'user_fields' => 'email,birthday,third_party_id,name,first_name,last_name', + ], + */ + + /* + // Twitter OAuth Authentication API. + // Register your application to get an API key here: + // http://twitter.com/oauth_clients + 'twitter' => [ + 'authtwitter:Twitter', + 'key' => 'xxxxxxxxxxxxxxxx', + 'secret' => 'xxxxxxxxxxxxxxxx', + // Forces the user to enter their credentials to ensure the correct users account is authorized. + // Details: https://dev.twitter.com/docs/api/1/get/oauth/authenticate + 'force_login' => false, + ], + */ + + /* + // Microsoft Account (Windows Live ID) Authentication API. + // Register your application to get an API key here: + // https://apps.dev.microsoft.com/ + 'windowslive' => [ + 'authwindowslive:LiveID', + 'key' => 'xxxxxxxxxxxxxxxx', + 'secret' => 'xxxxxxxxxxxxxxxx', + ], + */ + + /* + // Example of a LDAP authentication source. + 'example-ldap' => [ + 'ldap:Ldap', + + // The connection string for the LDAP-server. + // You can add multiple by separating them with a space. + 'connection_string' => 'ldap.example.org', + + // Whether SSL/TLS should be used when contacting the LDAP server. + // Possible values are 'ssl', 'tls' or 'none' + 'encryption' => 'ssl', + + // The LDAP version to use when interfacing the LDAP-server. + // Defaults to 3 + 'version' => 3, + + // Set to TRUE to enable LDAP debug level. Passed to the LDAP connector class. + // + // Default: FALSE + // Required: No + 'ldap.debug' => false, + + // The LDAP-options to pass when setting up a connection + // See [Symfony documentation][1] + 'options' => [ + + // Set whether to follow referrals. + // AD Controllers may require 0x00 to function. + // Possible values are 0x00 (NEVER), 0x01 (SEARCHING), + // 0x02 (FINDING) or 0x03 (ALWAYS). + 'referrals' => 0x00, + + 'network_timeout' => 3, + ], + + // The connector to use. + // Defaults to '\SimpleSAML\Module\ldap\Connector\Ldap', but can be set + // to '\SimpleSAML\Module\ldap\Connector\ActiveDirectory' when + // authenticating against Microsoft Active Directory. This will + // provide you with more specific error messages. + 'connector' => '\SimpleSAML\Module\ldap\Connector\Ldap', + + // Which attributes should be retrieved from the LDAP server. + // This can be an array of attribute names, or NULL, in which case + // all attributes are fetched. + 'attributes' => null, + + // Which attributes should be base64 encoded after retrieval from + // the LDAP server. + 'attributes.binary' => [ + 'jpegPhoto', + 'objectGUID', + 'objectSid', + 'mS-DS-ConsistencyGuid' + ], + + // The pattern which should be used to create the user's DN given + // the username. %username% in this pattern will be replaced with + // the user's username. + // + // This option is not used if the search.enable option is set to TRUE. + 'dnpattern' => 'uid=%username%,ou=people,dc=example,dc=org', + + // As an alternative to specifying a pattern for the users DN, it is + // possible to search for the username in a set of attributes. This is + // enabled by this option. + 'search.enable' => false, + + // An array on DNs which will be used as a base for the search. In + // case of multiple strings, they will be searched in the order given. + 'search.base' => [ + 'ou=people,dc=example,dc=org', + ], + + // The scope of the search. Valid values are 'sub' and 'one' and + // 'base', first one being the default if no value is set. + 'search.scope' => 'sub', + + // The attribute(s) the username should match against. + // + // This is an array with one or more attribute names. Any of the + // attributes in the array may match the value the username. + 'search.attributes' => ['uid', 'mail'], + + // Additional filters that must match for the entire LDAP search to + // be true. + // + // This should be a single string conforming to [RFC 1960][2] + // and [RFC 2544][3]. The string is appended to the search attributes + 'search.filter' => '(&(objectClass=Person)(|(sn=Doe)(cn=John *)))', + + // The username & password where SimpleSAMLphp should bind to before + // searching. If this is left NULL, no bind will be performed before + // searching. + 'search.username' => null, + 'search.password' => null, + ], + */ + + /* + // Example of an LDAPMulti authentication source. + 'example-ldapmulti' => [ + 'ldap:LdapMulti', + + // The way the organization as part of the username should be handled. + // Three possible values: + // - 'none': No handling of the organization. Allows '@' to be part + // of the username. + // - 'allow': Will allow users to type 'username@organization'. + // - 'force': Force users to type 'username@organization'. The dropdown + // list will be hidden. + // + // The default is 'none'. + 'username_organization_method' => 'none', + + // Whether the organization should be included as part of the username + // when authenticating. If this is set to TRUE, the username will be on + // the form @. If this is FALSE, the + // username will be used as the user enters it. + // + // The default is FALSE. + 'include_organization_in_username' => false, + + // A list of available LDAP servers. + // + // The index is an identifier for the organization/group. When + // 'username_organization_method' is set to something other than 'none', + // the organization-part of the username is matched against the index. + // + // The value of each element is an array in the same format as an LDAP + // authentication source. + 'mapping' => [ + 'employees' => [ + // A short name/description for this group. Will be shown in a + // dropdown list when the user logs on. + // + // This option can be a string or an array with + // language => text mappings. + 'description' => 'Employees', + 'authsource' => 'example-ldap', + ], + + 'students' => [ + 'description' => 'Students', + 'authsource' => 'example-ldap-2', + ], + ], + ], + */ +]; diff --git a/docker-config/idp/config/config.php b/docker-config/idp/config/config.php new file mode 100644 index 0000000000..3ecbe1d3ad --- /dev/null +++ b/docker-config/idp/config/config.php @@ -0,0 +1,1301 @@ + 'http://localhost:8180/simplesaml/', + + /* + * The 'application' configuration array groups a set configuration options + * relative to an application protected by SimpleSAMLphp. + */ + 'application' => [ + /* + * The 'baseURL' configuration option allows you to specify a protocol, + * host and optionally a port that serves as the canonical base for all + * your application's URLs. This is useful when the environment + * observed in the server differs from the one observed by end users, + * for example, when using a load balancer to offload TLS. + * + * Note that this configuration option does not allow setting a path as + * part of the URL. If your setup involves URL rewriting or any other + * tricks that would result in SimpleSAMLphp observing a URL for your + * application's scripts different than the canonical one, you will + * need to compute the right URLs yourself and pass them dynamically + * to SimpleSAMLphp's API. + */ + //'baseURL' => 'https://example.com', + ], + + /* + * The following settings are *filesystem paths* which define where + * SimpleSAMLphp can find or write the following things: + * - 'cachedir': Where SimpleSAMLphp can write its cache. + * - 'loggingdir': Where to write logs. MUST be set to NULL when using a logging + * handler other than `file`. + * - 'datadir': Storage of general data. + * - 'tempdir': Saving temporary files. SimpleSAMLphp will attempt to create + * this directory if it doesn't exist. DEPRECATED - replaced by cachedir. + * When specified as a relative path, this is relative to the SimpleSAMLphp + * root directory. + */ + 'cachedir' => '/var/cache/simplesamlphp', + //'loggingdir' => '/var/log/', + //'datadir' => '/var/data/', + //'tempdir' => '/tmp/simplesamlphp', + + /* + * Certificate and key material can be loaded from different possible + * locations. Currently two locations are supported, the local filesystem + * and the database via pdo using the global database configuration. Locations + * are specified by a URL-link prefix before the file name/path or database + * identifier. + */ + + /* To load a certificate or key from the filesystem, it should be specified + * as 'file://' where is either a relative filename or a fully + * qualified path to a file containing the certificate or key in PEM + * format, such as 'cert.pem' or '/path/to/cert.pem'. If the path is + * relative, it will be searched for in the directory defined by the + * 'certdir' parameter below. When 'certdir' is specified as a relative + * path, it will be interpreted as relative to the SimpleSAMLphp root + * directory. Note that locations with no prefix included will be treated + * as file locations. + */ + 'certdir' => 'cert/', + + /* To load a certificate or key from the database, it should be specified + * as 'pdo://' where is the identifier in the database table that + * should be matched. While the certificate and key tables are expected to + * be in the simplesaml database, they are not created or managed by + * simplesaml. The following parameters control how the pdo location + * attempts to retrieve certificates and keys from the database: + * + * - 'cert.pdo.table': name of table where certificates are stored + * - 'cert.pdo.keytable': name of table where keys are stored + * - 'cert.pdo.apply_prefix': whether or not to prepend the database.prefix + * parameter to the table names; if you are using + * database.prefix to separate multiple SSP instances + * in the same database but want to share certificate/key + * data between them, set this to false + * - 'cert.pdo.id_column': name of column to use as identifier + * - 'cert.pdo.data_column': name of column where PEM data is stored + * + * Basically, the query executed will be: + * + * SELECT cert.pdo.data_column FROM cert.pdo.table WHERE cert.pdo.id_column = :id + * + * Defaults are shown below, to change them, uncomment the line and update as + * needed + */ + //'cert.pdo.table' => 'certificates', + //'cert.pdo.keytable' => 'private_keys', + //'cert.pdo.apply_prefix' => true, + //'cert.pdo.id_column' => 'id', + //'cert.pdo.data_column' => 'data', + + /* + * Some information about the technical persons running this installation. + * The email address will be used as the recipient address for error reports, and + * also as the technical contact in generated metadata. + */ + 'technicalcontact_name' => 'Administrator', + 'technicalcontact_email' => 'na@example.org', + + /* + * (Optional) The method by which email is delivered. Defaults to mail which utilizes the + * PHP mail() function. + * + * Valid options are: mail, sendmail and smtp. + */ + //'mail.transport.method' => 'smtp', + + /* + * Set the transport options for the transport method specified. The valid settings are relative to the + * selected transport method. + */ + /* + 'mail.transport.options' => [ + 'host' => 'mail.example.org', // required + 'port' => 25, // optional + 'username' => 'user@example.org', // optional: if set, enables smtp authentication + 'password' => 'password', // optional: if set, enables smtp authentication + 'security' => 'tls', // optional: defaults to no smtp security + 'smtpOptions' => [], // optional: passed to stream_context_create when connecting via SMTP + ], + + // sendmail mail transport options + /* + 'mail.transport.options' => [ + 'path' => '/usr/sbin/sendmail' // optional: defaults to php.ini path + ], + */ + + /* + * The envelope from address for outgoing emails. + * This should be in a domain that has your application's IP addresses in its SPF record + * to prevent it from being rejected by mail filters. + */ + //'sendmail_from' => 'no-reply@example.org', + + /* + * The timezone of the server. This option should be set to the timezone you want + * SimpleSAMLphp to report the time in. The default is to guess the timezone based + * on your system timezone. + * + * See this page for a list of valid timezones: http://php.net/manual/en/timezones.php + */ + 'timezone' => 'America/New_York', + + /********************************** + | SECURITY CONFIGURATION OPTIONS | + **********************************/ + + /* + * This is a secret salt used by SimpleSAMLphp when it needs to generate a secure hash + * of a value. It must be changed from its default value to a secret value. The value of + * 'secretsalt' can be any valid string of any length. + * + * A possible way to generate a random salt is by running the following command from a unix shell: + * LC_ALL=C tr -c -d '0123456789abcdefghijklmnopqrstuvwxyz' /dev/null;echo + */ + 'secretsalt' => 'h6GwzJYCUrc9SgU57Coc7anTduvfnb8U', + + /* + * This password must be kept secret, and modified from the default value 123. + * This password will give access to the installation page of SimpleSAMLphp with + * metadata listing and diagnostics pages. + * You can also put a hash here; run "bin/pwgen.php" to generate one. + */ + 'auth.adminpassword' => 'admin', + + /* + * Set this option to true if you want to require administrator password to access the metadata. + */ + 'admin.protectmetadata' => false, + + /* + * Set this option to false if you don't want SimpleSAMLphp to check for new stable releases when + * visiting the configuration tab in the web interface. + */ + 'admin.checkforupdates' => false, + + /* + * Array of domains that are allowed when generating links or redirects + * to URLs. SimpleSAMLphp will use this option to determine whether to + * to consider a given URL valid or not, but you should always validate + * URLs obtained from the input on your own (i.e. ReturnTo or RelayState + * parameters obtained from the $_REQUEST array). + * + * SimpleSAMLphp will automatically add your own domain (either by checking + * it dynamically, or by using the domain defined in the 'baseurlpath' + * directive, the latter having precedence) to the list of trusted domains, + * in case this option is NOT set to NULL. In that case, you are explicitly + * telling SimpleSAMLphp to verify URLs. + * + * Set to an empty array to disallow ALL redirects or links pointing to + * an external URL other than your own domain. This is the default behaviour. + * + * Set to NULL to disable checking of URLs. DO NOT DO THIS UNLESS YOU KNOW + * WHAT YOU ARE DOING! + * + * Example: + * 'trusted.url.domains' => ['sp.example.com', 'app.example.com'], + */ + 'trusted.url.domains' => [], + + /* + * Enable regular expression matching of trusted.url.domains. + * + * Set to true to treat the values in trusted.url.domains as regular + * expressions. Set to false to do exact string matching. + * + * If enabled, the start and end delimiters ('^' and '$') will be added to + * all regular expressions in trusted.url.domains. + */ + 'trusted.url.regex' => false, + + /* + * Enable secure POST from HTTPS to HTTP. + * + * If you have some SP's on HTTP and IdP is normally on HTTPS, this option + * enables secure POSTing to HTTP endpoint without warning from browser. + * + * For this to work, module.php/core/postredirect.php must be accessible + * also via HTTP on IdP, e.g. if your IdP is on + * https://idp.example.org/ssp/, then + * http://idp.example.org/ssp/module.php/core/postredirect.php must be accessible. + */ + 'enable.http_post' => false, + + /* + * Set the allowed clock skew between encrypting/decrypting assertions + * + * If you have a server that is constantly out of sync, this option + * allows you to adjust the allowed clock-skew. + * + * Allowed range: 180 - 300 + * Defaults to 180. + */ + 'assertion.allowed_clock_skew' => 180, + + /* + * Set custom security headers. The defaults can be found in \SimpleSAML\Configuration::DEFAULT_SECURITY_HEADERS + * + * NOTE: When a header is already set on the response we will NOT overrule it and leave it untouched. + * + * Whenever you change any of these headers, make sure to validate your config by running your + * hostname through a security-test like https://en.internet.nl + 'headers.security' => [ + 'Content-Security-Policy' => "default-src 'none'; frame-ancestors 'self'; object-src 'none'; script-src 'self'; style-src 'self'; font-src 'self'; connect-src 'self'; img-src 'self' data:; base-uri 'none'", + 'X-Frame-Options' => 'SAMEORIGIN', + 'X-Content-Type-Options' => 'nosniff', + 'Referrer-Policy' => 'origin-when-cross-origin', + ], + */ + + + /************************ + | ERRORS AND DEBUGGING | + ************************/ + + /* + * The 'debug' option allows you to control how SimpleSAMLphp behaves in certain + * situations where further action may be taken + * + * It can be left unset, in which case, debugging is switched off for all actions. + * If set, it MUST be an array containing the actions that you want to enable, or + * alternatively a hashed array where the keys are the actions and their + * corresponding values are booleans enabling or disabling each particular action. + * + * SimpleSAMLphp provides some pre-defined actions, though modules could add new + * actions here. Refer to the documentation of every module to learn if they + * allow you to set any more debugging actions. + * + * The pre-defined actions are: + * + * - 'saml': this action controls the logging of SAML messages exchanged with other + * entities. When enabled ('saml' is present in this option, or set to true), all + * SAML messages will be logged, including plaintext versions of encrypted + * messages. + * + * - 'backtraces': this action controls the logging of error backtraces so you + * can debug any possible errors happening in SimpleSAMLphp. + * + * - 'validatexml': this action allows you to validate SAML documents against all + * the relevant XML schemas. SAML 1.1 messages or SAML metadata parsed with + * the XML to SimpleSAMLphp metadata converter or the metaedit module will + * validate the SAML documents if this option is enabled. + * + * If you want to disable debugging completely, unset this option or set it to an + * empty array. + */ + 'debug' => [ + 'saml' => false, + 'backtraces' => true, + 'validatexml' => false, + ], + + /* + * When 'showerrors' is enabled, all error messages and stack traces will be output + * to the browser. + * + * When 'errorreporting' is enabled, a form will be presented for the user to report + * the error to 'technicalcontact_email'. + */ + 'showerrors' => true, + 'errorreporting' => true, + + /* + * Custom error show function called from SimpleSAML\Error\Error::show. + * See docs/simplesamlphp-errorhandling.md for function code example. + * + * Example: + * 'errors.show_function' => ['SimpleSAML\Module\example\Error', 'show'], + */ + + + /************************** + | LOGGING AND STATISTICS | + **************************/ + + /* + * Define the minimum log level to log. Available levels: + * - SimpleSAML\Logger::ERR No statistics, only errors + * - SimpleSAML\Logger::WARNING No statistics, only warnings/errors + * - SimpleSAML\Logger::NOTICE Statistics and errors + * - SimpleSAML\Logger::INFO Verbose logs + * - SimpleSAML\Logger::DEBUG Full debug logs - not recommended for production + * + * Choose logging handler. + * + * Options: [syslog,file,errorlog,stderr] + * + * If you set the handler to 'file', the directory specified in loggingdir above + * must exist and be writable for SimpleSAMLphp. If set to something else, set + * loggingdir above to 'null'. + */ + 'logging.level' => SimpleSAML\Logger::NOTICE, + 'logging.handler' => 'syslog', + + /* + * Specify the format of the logs. Its use varies depending on the log handler used (for instance, you cannot + * control here how dates are displayed when using the syslog or errorlog handlers), but in general the options + * are: + * + * - %date{}: the date and time, with its format specified inside the brackets. See the PHP documentation + * of the date() function for more information on the format. If the brackets are omitted, the standard + * format is applied. This can be useful if you just want to control the placement of the date, but don't care + * about the format. + * + * - %process: the name of the SimpleSAMLphp process. Remember you can configure this in the 'logging.processname' + * option below. + * + * - %level: the log level (name or number depending on the handler used). + * + * - %stat: if the log entry is intended for statistical purposes, it will print the string 'STAT ' (bear in mind + * the trailing space). + * + * - %trackid: the track ID, an identifier that allows you to track a single session. + * + * - %srcip: the IP address of the client. If you are behind a proxy, make sure to modify the + * $_SERVER['REMOTE_ADDR'] variable on your code accordingly to the X-Forwarded-For header. + * + * - %msg: the message to be logged. + * + */ + //'logging.format' => '%date{M j H:i:s} %process %level %stat[%trackid] %msg', + + /* + * Choose which facility should be used when logging with syslog. + * + * These can be used for filtering the syslog output from SimpleSAMLphp into its + * own file by configuring the syslog daemon. + * + * See the documentation for openlog (http://php.net/manual/en/function.openlog.php) for available + * facilities. Note that only LOG_USER is valid on windows. + * + * The default is to use LOG_LOCAL5 if available, and fall back to LOG_USER if not. + */ + 'logging.facility' => defined('LOG_LOCAL5') ? constant('LOG_LOCAL5') : LOG_USER, + + /* + * The process name that should be used when logging to syslog. + * The value is also written out by the other logging handlers. + */ + 'logging.processname' => 'simplesamlphp', + + /* + * Logging: file - Logfilename in the loggingdir from above. + */ + 'logging.logfile' => 'simplesamlphp.log', + + /* + * This is an array of outputs. Each output has at least a 'class' option, which + * selects the output. + */ + 'statistics.out' => [ + // Log statistics to the normal log. + /* + [ + 'class' => 'core:Log', + 'level' => 'notice', + ], + */ + // Log statistics to files in a directory. One file per day. + /* + [ + 'class' => 'core:File', + 'directory' => '/var/log/stats', + ], + */ + ], + + + + /*********************** + | PROXY CONFIGURATION | + ***********************/ + + /* + * Proxy to use for retrieving URLs. + * + * Example: + * 'proxy' => 'tcp://proxy.example.com:5100' + */ + 'proxy' => null, + + /* + * Username/password authentication to proxy (Proxy-Authorization: Basic) + * Example: + * 'proxy.auth' = 'myuser:password' + */ + //'proxy.auth' => 'myuser:password', + + + + /************************** + | DATABASE CONFIGURATION | + **************************/ + + /* + * This database configuration is optional. If you are not using + * core functionality or modules that require a database, you can + * skip this configuration. + */ + + /* + * Database connection string. + * Ensure that you have the required PDO database driver installed + * for your connection string. + */ + 'database.dsn' => 'mysql:host=localhost;dbname=saml', + + /* + * SQL database credentials + */ + 'database.username' => 'simplesamlphp', + 'database.password' => 'secret', + 'database.options' => [], + + /* + * (Optional) Table prefix + */ + 'database.prefix' => '', + + /* + * (Optional) Driver options + */ + 'database.driver_options' => [], + + /* + * True or false if you would like a persistent database connection + */ + 'database.persistent' => false, + + /* + * Database secondary configuration is optional as well. If you are only + * running a single database server, leave this blank. If you have + * a primary/secondary configuration, you can define as many secondary servers + * as you want here. Secondaries will be picked at random to be queried from. + * + * Configuration options in the secondary array are exactly the same as the + * options for the primary (shown above) with the exception of the table + * prefix and driver options. + */ + 'database.secondaries' => [ + /* + [ + 'dsn' => 'mysql:host=mysecondary;dbname=saml', + 'username' => 'simplesamlphp', + 'password' => 'secret', + 'persistent' => false, + ], + */ + ], + + + + /************* + | PROTOCOLS | + *************/ + + /* + * Which functionality in SimpleSAMLphp do you want to enable. Normally you would enable only + * one of the functionalities below, but in some cases you could run multiple functionalities. + * In example when you are setting up a federation bridge. + */ + 'enable.saml20-idp' => true, + 'enable.adfs-idp' => false, + + + + /*********** + | MODULES | + ***********/ + + /* + * Configuration for enabling/disabling modules. By default the 'core', 'admin' and 'saml' modules are enabled. + * + * Example: + * + * 'module.enable' => [ + * 'exampleauth' => true, // Setting to TRUE enables. + * 'consent' => false, // Setting to FALSE disables. + * 'core' => null, // Unset or NULL uses default. + * ], + */ + + 'module.enable' => [ + 'exampleauth' => true, + 'core' => true, + 'admin' => true, + 'saml' => true, + 'cron' => true, + 'metarefresh' => true, + ], + + + /************************* + | SESSION CONFIGURATION | + *************************/ + + /* + * This value is the duration of the session in seconds. Make sure that the time duration of + * cookies both at the SP and the IdP exceeds this duration. + */ + 'session.duration' => 60, // 60 seconds + + /* + * Sets the duration, in seconds, data should be stored in the datastore. As the data store is used for + * login and logout requests, this option will control the maximum time these operations can take. + * The default is 4 hours (4*60*60) seconds, which should be more than enough for these operations. + */ + 'session.datastore.timeout' => (4 * 60 * 60), // 4 hours + + /* + * Sets the duration, in seconds, auth state should be stored. + */ + 'session.state.timeout' => (60 * 60), // 1 hour + + /* + * Option to override the default settings for the session cookie name + */ + 'session.cookie.name' => 'SimpleSAMLSessionIDidp', + + /* + * Expiration time for the session cookie, in seconds. + * + * Defaults to 0, which means that the cookie expires when the browser is closed. + * + * Example: + * 'session.cookie.lifetime' => 30*60, + */ + 'session.cookie.lifetime' => 0, + + /* + * Limit the path of the cookies. + * + * Can be used to limit the path of the cookies to a specific subdirectory. + * + * Example: + * 'session.cookie.path' => '/simplesaml/', + */ + 'session.cookie.path' => '/', + + /* + * Cookie domain. + * + * Can be used to make the session cookie available to several domains. + * + * Example: + * 'session.cookie.domain' => '.example.org', + */ + 'session.cookie.domain' => '', + + /* + * Set the secure flag in the cookie. + * + * Set this to TRUE if the user only accesses your service + * through https. If the user can access the service through + * both http and https, this must be set to FALSE. + * + * If unset, SimpleSAMLphp will try to automatically determine the right value + */ + //'session.cookie.secure' => true, + + /* + * Set the SameSite attribute in the cookie. + * + * You can set this to the strings 'None', 'Lax', or 'Strict' to support + * the RFC6265bis SameSite cookie attribute. If set to null, no SameSite + * attribute will be sent. + * + * A value of "None" is required to properly support cross-domain POST + * requests which are used by different SAML bindings. Because some older + * browsers do not support this value, the canSetSameSiteNone function + * can be called to only set it for compatible browsers. + * + * You must also set the 'session.cookie.secure' value above to true. + * + * Example: + * 'session.cookie.samesite' => 'None', + */ + 'session.cookie.samesite' => $httpUtils->canSetSameSiteNone() ? 'None' : null, + + /* + * Options to override the default settings for php sessions. + */ + 'session.phpsession.cookiename' => 'SimpleSAMLidp', + 'session.phpsession.savepath' => null, + 'session.phpsession.httponly' => true, + + /* + * Option to override the default settings for the auth token cookie + */ + 'session.authtoken.cookiename' => 'SimpleSAMLAuthToken', + + /* + * Options for remember me feature for IdP sessions. Remember me feature + * has to be also implemented in authentication source used. + * + * Option 'session.cookie.lifetime' should be set to zero (0), i.e. cookie + * expires on browser session if remember me is not checked. + * + * Session duration ('session.duration' option) should be set according to + * 'session.rememberme.lifetime' option. + * + * It's advised to use remember me feature with session checking function + * defined with 'session.check_function' option. + */ + 'session.rememberme.enable' => false, + 'session.rememberme.checked' => false, + 'session.rememberme.lifetime' => (14 * 86400), + + /* + * Custom function for session checking called on session init and loading. + * See docs/simplesamlphp-advancedfeatures.md for function code example. + * + * Example: + * 'session.check_function' => ['\SimpleSAML\Module\example\Util', 'checkSession'], + */ + + + + /************************** + | MEMCACHE CONFIGURATION | + **************************/ + + /* + * Configuration for the 'memcache' session store. This allows you to store + * multiple redundant copies of sessions on different memcache servers. + * + * 'memcache_store.servers' is an array of server groups. Every data + * item will be mirrored in every server group. + * + * Each server group is an array of servers. The data items will be + * load-balanced between all servers in each server group. + * + * Each server is an array of parameters for the server. The following + * options are available: + * - 'hostname': This is the hostname or ip address where the + * memcache server runs. This is the only required option. + * - 'port': This is the port number of the memcache server. If this + * option isn't set, then we will use the 'memcache.default_port' + * ini setting. This is 11211 by default. + * + * When using the "memcache" extension, the following options are also + * supported: + * - 'weight': This sets the weight of this server in this server + * group. http://php.net/manual/en/function.Memcache-addServer.php + * contains more information about the weight option. + * - 'timeout': The timeout for this server. By default, the timeout + * is 3 seconds. + * + * Example of redundant configuration with load balancing: + * This configuration makes it possible to lose both servers in the + * a-group or both servers in the b-group without losing any sessions. + * Note that sessions will be lost if one server is lost from both the + * a-group and the b-group. + * + * 'memcache_store.servers' => [ + * [ + * ['hostname' => 'mc_a1'], + * ['hostname' => 'mc_a2'], + * ], + * [ + * ['hostname' => 'mc_b1'], + * ['hostname' => 'mc_b2'], + * ], + * ], + * + * Example of simple configuration with only one memcache server, + * running on the same computer as the web server: + * Note that all sessions will be lost if the memcache server crashes. + * + * 'memcache_store.servers' => [ + * [ + * ['hostname' => 'localhost'], + * ], + * ], + * + * Additionally, when using the "memcached" extension, unique keys must + * be provided for each group of servers if persistent connections are + * desired. Each server group can also have an "options" indexed array + * with the options desired for the given group: + * + * 'memcache_store.servers' => [ + * 'memcache_group_1' => [ + * 'options' => [ + * \Memcached::OPT_BINARY_PROTOCOL => true, + * \Memcached::OPT_NO_BLOCK => true, + * \Memcached::OPT_TCP_NODELAY => true, + * \Memcached::OPT_LIBKETAMA_COMPATIBLE => true, + * ], + * ['hostname' => '127.0.0.1', 'port' => 11211], + * ['hostname' => '127.0.0.2', 'port' => 11211], + * ], + * + * 'memcache_group_2' => [ + * 'options' => [ + * \Memcached::OPT_BINARY_PROTOCOL => true, + * \Memcached::OPT_NO_BLOCK => true, + * \Memcached::OPT_TCP_NODELAY => true, + * \Memcached::OPT_LIBKETAMA_COMPATIBLE => true, + * ], + * ['hostname' => '127.0.0.3', 'port' => 11211], + * ['hostname' => '127.0.0.4', 'port' => 11211], + * ], + * ], + * + */ + 'memcache_store.servers' => [ + [ + ['hostname' => 'localhost'], + ], + ], + + /* + * This value allows you to set a prefix for memcache-keys. The default + * for this value is 'simpleSAMLphp', which is fine in most cases. + * + * When running multiple instances of SSP on the same host, and more + * than one instance is using memcache, you probably want to assign + * a unique value per instance to this setting to avoid data collision. + */ + 'memcache_store.prefix' => '', + + /* + * This value is the duration data should be stored in memcache. Data + * will be dropped from the memcache servers when this time expires. + * The time will be reset every time the data is written to the + * memcache servers. + * + * This value should always be larger than the 'session.duration' + * option. Not doing this may result in the session being deleted from + * the memcache servers while it is still in use. + * + * Set this value to 0 if you don't want data to expire. + * + * Note: The oldest data will always be deleted if the memcache server + * runs out of storage space. + */ + 'memcache_store.expires' => 36 * (60 * 60), // 36 hours. + + + + /************************************* + | LANGUAGE AND INTERNATIONALIZATION | + *************************************/ + + /* + * Languages available, RTL languages, and what language is the default. + */ + 'language.available' => [ + 'en', 'no', 'nn', 'se', 'da', 'de', 'sv', 'fi', 'es', 'ca', 'fr', 'it', 'nl', 'lb', + 'cs', 'sk', 'sl', 'lt', 'hr', 'hu', 'pl', 'pt', 'pt-br', 'tr', 'ja', 'zh', 'zh-tw', + 'ru', 'et', 'he', 'id', 'sr', 'lv', 'ro', 'eu', 'el', 'af', 'zu', 'xh', 'st', + ], + 'language.rtl' => ['ar', 'dv', 'fa', 'ur', 'he'], + 'language.default' => 'en', + + /* + * Options to override the default settings for the language parameter + */ + 'language.parameter.name' => 'language', + 'language.parameter.setcookie' => true, + + /* + * Options to override the default settings for the language cookie + */ + 'language.cookie.name' => 'language', + 'language.cookie.domain' => '', + 'language.cookie.path' => '/', + 'language.cookie.secure' => true, + 'language.cookie.httponly' => false, + 'language.cookie.lifetime' => (60 * 60 * 24 * 900), + 'language.cookie.samesite' => $httpUtils->canSetSameSiteNone() ? 'None' : null, + + /** + * Custom getLanguage function called from SimpleSAML\Locale\Language::getLanguage(). + * Function should return language code of one of the available languages or NULL. + * See SimpleSAML\Locale\Language::getLanguage() source code for more info. + * + * This option can be used to implement a custom function for determining + * the default language for the user. + * + * Example: + * 'language.get_language_function' => ['\SimpleSAML\Module\example\Template', 'getLanguage'], + */ + + /************** + | APPEARANCE | + **************/ + + /* + * Which theme directory should be used? + */ + 'theme.use' => 'default', + + /* + * Set this option to the text you would like to appear at the header of each page. Set to false if you don't want + * any text to appear in the header. + */ + //'theme.header' => 'SimpleSAMLphp', + + /** + * A template controller, if any. + * + * Used to intercept certain parts of the template handling, while keeping away unwanted/unexpected hooks. Set + * the 'theme.controller' configuration option to a class that implements the + * \SimpleSAML\XHTML\TemplateControllerInterface interface to use it. + */ + //'theme.controller' => '', + + /* + * Templating options + * + * By default, twig templates are not cached. To turn on template caching: + * Set 'template.cache' to an absolute path pointing to a directory that + * SimpleSAMLphp has read and write permissions to. + */ + //'template.cache' => '', + + /* + * Set the 'template.auto_reload' to true if you would like SimpleSAMLphp to + * recompile the templates (when using the template cache) if the templates + * change. If you don't want to check the source templates for every request, + * set it to false. + */ + 'template.auto_reload' => false, + + /* + * Set this option to true to indicate that your installation of SimpleSAMLphp + * is running in a production environment. This will affect the way resources + * are used, offering an optimized version when running in production, and an + * easy-to-debug one when not. Set it to false when you are testing or + * developing the software, in which case a banner will be displayed to remind + * users that they're dealing with a non-production instance. + * + * Defaults to true. + */ + 'production' => true, + + /* + * SimpleSAMLphp modules can host static resources which are served through PHP. + * The serving of the resources can be configured through these settings. + */ + 'assets' => [ + /* + * These settings adjust the caching headers that are sent + * when serving static resources. + */ + 'caching' => [ + /* + * Amount of seconds before the resource should be fetched again + */ + 'max_age' => 86400, + /* + * Calculate a checksum of every file and send it to the browser + * This allows the browser to avoid downloading assets again in situations + * where the Last-Modified header cannot be trusted, + * for example in cluster setups + * + * Defaults false + */ + 'etag' => false, + ], + ], + + /** + * Set to a full URL if you want to redirect users that land on SimpleSAMLphp's + * front page to somewhere more useful. If left unset, a basic welcome message + * is shown. + */ + //'frontpage.redirect' => 'https://example.com/', + + /********************* + | DISCOVERY SERVICE | + *********************/ + + /* + * Whether the discovery service should allow the user to save his choice of IdP. + */ + 'idpdisco.enableremember' => true, + 'idpdisco.rememberchecked' => true, + + /* + * The disco service only accepts entities it knows. + */ + 'idpdisco.validate' => true, + + 'idpdisco.extDiscoveryStorage' => null, + + /* + * IdP Discovery service look configuration. + * Whether to display a list of idp or to display a dropdown box. For many IdP' a dropdown box + * gives the best use experience. + * + * When using dropdown box a cookie is used to highlight the previously chosen IdP in the dropdown. + * This makes it easier for the user to choose the IdP + * + * Options: [links,dropdown] + */ + 'idpdisco.layout' => 'dropdown', + + + + /************************************* + | AUTHENTICATION PROCESSING FILTERS | + *************************************/ + + /* + * Authentication processing filters that will be executed for all IdPs + */ + 'authproc.idp' => [ + /* Enable the authproc filter below to add URN prefixes to all attributes + 10 => [ + 'class' => 'core:AttributeMap', 'addurnprefix' + ], + */ + /* Enable the authproc filter below to automatically generated eduPersonTargetedID. + 20 => 'core:TargetedID', + */ + + // Adopts language from attribute to use in UI + 30 => 'core:LanguageAdaptor', + + /* When called without parameters, it will fallback to filter attributes 'the old way' + * by checking the 'attributes' parameter in metadata on IdP hosted and SP remote. + */ + 50 => 'core:AttributeLimit', + + /* + * Search attribute "distinguishedName" for pattern and replaces if found + */ + /* + 60 => [ + 'class' => 'core:AttributeAlter', + 'pattern' => '/OU=studerende/', + 'replacement' => 'Student', + 'subject' => 'distinguishedName', + '%replace', + ], + */ + + /* + * Consent module is enabled (with no permanent storage, using cookies). + */ + /* + 90 => [ + 'class' => 'consent:Consent', + 'store' => 'consent:Cookie', + 'focus' => 'yes', + 'checked' => true + ], + */ + // If language is set in Consent module it will be added as an attribute. + 99 => 'core:LanguageAdaptor', + ], + + /* + * Authentication processing filters that will be executed for all SPs + */ + 'authproc.sp' => [ + /* + 10 => [ + 'class' => 'core:AttributeMap', 'removeurnprefix' + ], + */ + + /* + * Generate the 'group' attribute populated from other variables, including eduPersonAffiliation. + 60 => [ + 'class' => 'core:GenerateGroups', 'eduPersonAffiliation' + ], + */ + /* + * All users will be members of 'users' and 'members' + */ + /* + 61 => [ + 'class' => 'core:AttributeAdd', 'groups' => ['users', 'members'] + ], + */ + + // Adopts language from attribute to use in UI + 90 => 'core:LanguageAdaptor', + ], + + + + /************************** + | METADATA CONFIGURATION | + **************************/ + + /* + * This option allows you to specify a directory for your metadata outside of the standard metadata directory + * included in the standard distribution of the software. + */ + 'metadatadir' => 'metadata', + + /* + * This option configures the metadata sources. The metadata sources is given as an array with + * different metadata sources. When searching for metadata, SimpleSAMLphp will search through + * the array from start to end. + * + * Each element in the array is an associative array which configures the metadata source. + * The type of the metadata source is given by the 'type' element. For each type we have + * different configuration options. + * + * Flat file metadata handler: + * - 'type': This is always 'flatfile'. + * - 'directory': The directory we will load the metadata files from. The default value for + * this option is the value of the 'metadatadir' configuration option, or + * 'metadata/' if that option is unset. + * + * XML metadata handler: + * This metadata handler parses an XML file with either an EntityDescriptor element or an + * EntitiesDescriptor element. The XML file may be stored locally, or (for debugging) on a remote + * web server. + * The XML metadata handler defines the following options: + * - 'type': This is always 'xml'. + * - 'file': Path to the XML file with the metadata. + * - 'url': The URL to fetch metadata from. THIS IS ONLY FOR DEBUGGING - THERE IS NO CACHING OF THE RESPONSE. + * + * MDQ metadata handler: + * This metadata handler looks up for the metadata of an entity at the given MDQ server. + * The MDQ metadata handler defines the following options: + * - 'type': This is always 'mdq'. + * - 'server': Base URL of the MDQ server. Mandatory. + * - 'validateCertificate': The certificates file that may be used to sign the metadata. You don't need this + * option if you don't want to validate the signature on the metadata. Optional. + * - 'cachedir': Directory where metadata can be cached. Optional. + * - 'cachelength': Maximum time metadata can be cached, in seconds. Defaults to 24 + * hours (86400 seconds). Optional. + * + * PDO metadata handler: + * This metadata handler looks up metadata of an entity stored in a database. + * + * Note: If you are using the PDO metadata handler, you must configure the database + * options in this configuration file. + * + * The PDO metadata handler defines the following options: + * - 'type': This is always 'pdo'. + * + * Examples: + * + * This example defines two flatfile sources. One is the default metadata directory, the other + * is a metadata directory with auto-generated metadata files. + * + * 'metadata.sources' => [ + * ['type' => 'flatfile'], + * ['type' => 'flatfile', 'directory' => 'metadata-generated'], + * ], + * + * This example defines a flatfile source and an XML source. + * 'metadata.sources' => [ + * ['type' => 'flatfile'], + * ['type' => 'xml', 'file' => 'idp.example.org-idpMeta.xml'], + * ], + * + * This example defines an mdq source. + * 'metadata.sources' => [ + * [ + * 'type' => 'mdq', + * 'server' => 'http://mdq.server.com:8080', + * 'validateCertificate' => [ + * '/var/simplesamlphp/cert/metadata-key.new.crt', + * '/var/simplesamlphp/cert/metadata-key.old.crt' + * ], + * 'cachedir' => '/var/simplesamlphp/mdq-cache', + * 'cachelength' => 86400 + * ] + * ], + * + * This example defines an pdo source. + * 'metadata.sources' => [ + * ['type' => 'pdo'] + * ], + * + * Default: + * 'metadata.sources' => [ + * ['type' => 'flatfile'] + * ], + */ + 'metadata.sources' => [ + ['type' => 'flatfile'], + # webwork sp metadata dir + ['type' => 'flatfile', 'directory' => 'metadata/metarefresh-webwork'], + ], + + /* + * Should signing of generated metadata be enabled by default. + * + * Metadata signing can also be enabled for a individual SP or IdP by setting the + * same option in the metadata for the SP or IdP. + */ + 'metadata.sign.enable' => false, + + /* + * The default key & certificate which should be used to sign generated metadata. These + * are files stored in the cert dir. + * These values can be overridden by the options with the same names in the SP or + * IdP metadata. + * + * If these aren't specified here or in the metadata for the SP or IdP, then + * the 'certificate' and 'privatekey' option in the metadata will be used. + * if those aren't set, signing of metadata will fail. + */ + 'metadata.sign.privatekey' => null, + 'metadata.sign.privatekey_pass' => null, + 'metadata.sign.certificate' => null, + 'metadata.sign.algorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', + + + /**************************** + | DATA STORE CONFIGURATION | + ****************************/ + + /* + * Configure the data store for SimpleSAMLphp. + * + * - 'phpsession': Limited datastore, which uses the PHP session. + * - 'memcache': Key-value datastore, based on memcache. + * - 'sql': SQL datastore, using PDO. + * - 'redis': Key-value datastore, based on redis. + * + * The default datastore is 'phpsession'. + */ + 'store.type' => 'phpsession', + + /* + * The DSN the sql datastore should connect to. + * + * See http://www.php.net/manual/en/pdo.drivers.php for the various + * syntaxes. + */ + 'store.sql.dsn' => 'sqlite:/path/to/sqlitedatabase.sq3', + + /* + * The username and password to use when connecting to the database. + */ + 'store.sql.username' => null, + 'store.sql.password' => null, + + /* + * The prefix we should use on our tables. + */ + 'store.sql.prefix' => 'SimpleSAMLphp', + + /* + * The driver-options we should pass to the PDO-constructor. + */ + 'store.sql.options' => [], + + /* + * The hostname and port of the Redis datastore instance. + */ + 'store.redis.host' => 'localhost', + 'store.redis.port' => 6379, + + /* + * The credentials to use when connecting to Redis. + * + * If your Redis server is using the legacy password protection (config + * directive "requirepass" in redis.conf) then you should only provide + * a password. + * + * If your Redis server is using ACL's (which are recommended as of + * Redis 6+) then you should provide both a username and a password. + * See https://redis.io/docs/manual/security/acl/ + */ + 'store.redis.username' => '', + 'store.redis.password' => '', + + /* + * Communicate with Redis over a secure connection instead of plain TCP. + * + * This setting affects both single host connections as + * well as Sentinel mode. + */ + 'store.redis.tls' => false, + + /* + * Verify the Redis server certificate. + */ + 'store.redis.insecure' => false, + + /* + * Files related to secure communication with Redis. + * + * Files are searched in the 'certdir' when using relative paths. + */ + 'store.redis.ca_certificate' => null, + 'store.redis.certificate' => null, + 'store.redis.privatekey' => null, + + /* + * The prefix we should use on our Redis datastore. + */ + 'store.redis.prefix' => 'SimpleSAMLphp', + + /* + * The master group to use for Redis Sentinel. + */ + 'store.redis.mastergroup' => 'mymaster', + + /* + * The Redis Sentinel hosts. + * Example: + * 'store.redis.sentinels' => [ + * 'tcp://[yoursentinel1]:[port]', + * 'tcp://[yoursentinel2]:[port]', + * 'tcp://[yoursentinel3]:[port] + * ], + * + * Use 'tls' instead of 'tcp' in order to make use of the additional + * TLS settings. + */ + 'store.redis.sentinels' => [], + + /********************* + | IdP/SP PROXY MODE | + *********************/ + + /* + * If the IdP in front of SimpleSAMLphp in IdP/SP proxy mode sends + * AuthnContextClassRef, decide whether the AuthnContextClassRef will be + * processed by the IdP/SP proxy or if it will be passed to the SP behind + * the IdP/SP proxy. + */ + 'proxymode.passAuthnContextClassRef' => false, +]; diff --git a/docker-config/idp/config/module_cron.php b/docker-config/idp/config/module_cron.php new file mode 100644 index 0000000000..a05be61da2 --- /dev/null +++ b/docker-config/idp/config/module_cron.php @@ -0,0 +1,8 @@ + 'webwork2', + 'allowed_tags' => ['metarefresh'], + 'debug_message' => true, + 'sendemail' => false, +]; diff --git a/docker-config/idp/config/module_metarefresh.php b/docker-config/idp/config/module_metarefresh.php new file mode 100644 index 0000000000..1b2cf60d61 --- /dev/null +++ b/docker-config/idp/config/module_metarefresh.php @@ -0,0 +1,21 @@ + [ + 'webwork2' => [ + 'cron' => ['metarefresh'], + 'sources' => [ + ['src' => $metadataURL] + ], + 'expiresAfter' => 60 * 60 * 24 * 365 * 10, // 10 years, basically never + 'outputDir' => 'metadata/metarefresh-webwork/', + 'outputFormat' => 'flatfile', + ] + ] +]; diff --git a/docker-config/idp/idp.apache2.conf b/docker-config/idp/idp.apache2.conf new file mode 100644 index 0000000000..5f2e656ebe --- /dev/null +++ b/docker-config/idp/idp.apache2.conf @@ -0,0 +1,7 @@ +SetEnv SIMPLESAMLPHP_CONFIG_DIR /var/www/simplesamlphp/config + +Alias /simplesaml /var/www/simplesamlphp/public + + + Require all granted + diff --git a/docker-config/idp/metadata/saml20-idp-hosted.php b/docker-config/idp/metadata/saml20-idp-hosted.php new file mode 100644 index 0000000000..f0843f3b28 --- /dev/null +++ b/docker-config/idp/metadata/saml20-idp-hosted.php @@ -0,0 +1,50 @@ + '__DEFAULT__', + + // X.509 key and certificate. Relative to the cert directory. + 'privatekey' => 'server.pem', + 'certificate' => 'server.crt', + + /* + * Authentication source to use. Must be one that is configured in + * 'config/authsources.php'. + */ + 'auth' => 'example-userpass', + + /* Uncomment the following to use the uri NameFormat on attributes. */ + 'attributes.NameFormat' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri', + 'authproc' => [ + // Convert attribute names to oids. + 100 => ['class' => 'core:AttributeMap', 'name2oid'], + ], + + /* + * Uncomment the following to specify the registration information in the + * exported metadata. Refer to: + * http://docs.oasis-open.org/security/saml/Post2.0/saml-metadata-rpi/v1.0/cs01/saml-metadata-rpi-v1.0-cs01.html + * for more information. + */ + /* + 'RegistrationInfo' => [ + 'authority' => 'urn:mace:example.org', + 'instant' => '2008-01-17T11:28:03Z', + 'policies' => [ + 'en' => 'http://example.org/policy', + 'es' => 'http://example.org/politica', + ], + ], + */ +]; diff --git a/docker-config/pgfsys-dvisvmg-bbox-fix.patch b/docker-config/pgfsys-dvisvmg-bbox-fix.patch new file mode 100644 index 0000000000..6e29205368 --- /dev/null +++ b/docker-config/pgfsys-dvisvmg-bbox-fix.patch @@ -0,0 +1,19 @@ +--- a/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-dvisvgm.def 2024-02-22 13:30:26.167777811 -0600 ++++ b/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-dvisvgm.def 2024-02-22 13:36:29.820956330 -0600 +@@ -127,7 +127,15 @@ + \dp#1=0pt% + \leavevmode% + \pgf@xa=\pgf@trimleft@final\relax \ifdim\pgf@xa=0pt \else\kern\pgf@xa\fi% +- \raise-\pgf@ya\hbox{\ifpgf@sys@svg@inpicture\else\special{dvisvgm:bbox \pgf@sys@tonumber\pgf@picmaxx\space\pgf@sys@tonumber\pgf@picmaxy}\fi\box#1}% ++ \raise-\pgf@ya\hbox{% ++ \ifpgf@sys@svg@inpicture ++ \box#1% ++ \else ++ \special{dvisvgm:bbox \pgf@sys@tonumber\pgf@picmaxx\space\pgf@sys@tonumber\pgf@picmaxy}% ++ \special{dvisvgm:bbox lock}% ++ \box#1% ++ \special{dvisvgm:bbox unlock}% ++ \fi}% + \pgf@xa=\pgf@trimright@final\relax \ifdim\pgf@xa=0pt \else\kern\pgf@xa\fi% + } + diff --git a/htdocs/css/rtl.css b/htdocs/css/rtl.css index 95691f2401..b1ad162418 100644 --- a/htdocs/css/rtl.css +++ b/htdocs/css/rtl.css @@ -1,20 +1,5 @@ -/* WeBWorK Online Homework Delivery System - * Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of either: (a) the GNU General Public License as published by the - * Free Software Foundation; either version 2, or (at your option) any later - * version, or (b) the "Artistic License" which comes with this package. - * - * 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. See either the GNU General Public License or the - * Artistic License for more details. - */ - /* --- Modify some CSS for Right to left courses/problems --- */ /* The changes which were needed here in WeBWorK 2.16 are no * longer needed in WeBWorK 2.17. The file is being retained * for potential future use. */ - diff --git a/htdocs/generate-assets.js b/htdocs/generate-assets.js index d297e8b214..81ee6c7b10 100755 --- a/htdocs/generate-assets.js +++ b/htdocs/generate-assets.js @@ -15,7 +15,10 @@ const rtlcss = require('rtlcss'); const cssMinify = require('cssnano'); const argv = yargs - .usage('$0 Options').version(false).alias('help', 'h').wrap(100) + .usage('$0 Options') + .version(false) + .alias('help', 'h') + .wrap(100) .option('enable-sourcemaps', { alias: 's', description: 'Generate source maps. (Not for use in production!)', @@ -30,8 +33,7 @@ const argv = yargs alias: 'd', description: 'Delete all generated files.', type: 'boolean' - }) - .argv; + }).argv; const assetFile = path.resolve(__dirname, 'static-assets.json'); const assets = {}; @@ -48,7 +50,7 @@ const cleanDir = (dir) => { } } } -} +}; // The is set to true after all files are processed for the first time. let ready = false; @@ -75,12 +77,13 @@ const processFile = async (file, _details) => { return; } - const minJS = result.code + ( - argv.enableSourcemaps && result.map - ? `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${ - Buffer.from(result.map).toString('base64')}` - : '' - ); + const minJS = + result.code + + (argv.enableSourcemaps && result.map + ? `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${Buffer.from( + result.map + ).toString('base64')}` + : ''); const contentHash = crypto.createHash('sha256'); contentHash.update(minJS); @@ -114,18 +117,19 @@ const processFile = async (file, _details) => { return; } - if (result.sourceMap) result.sourceMap.sources = [ baseName ]; + if (result.sourceMap) result.sourceMap.sources = [baseName]; // Pass the compiled css through the autoprefixer. // This is really only needed for the bootstrap.css files, but doesn't hurt for the rest. let prefixedResult = await postcss([autoprefixer, cssMinify]).process(result.css, { from: baseName }); - const minCSS = prefixedResult.css + ( - argv.enableSourcemaps && result.sourceMap - ? `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${ - Buffer.from(JSON.stringify(result.sourceMap)).toString('base64')}*/` - : '' - ); + const minCSS = + prefixedResult.css + + (argv.enableSourcemaps && result.sourceMap + ? `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${Buffer.from( + JSON.stringify(result.sourceMap) + ).toString('base64')}*/` + : ''); const contentHash = crypto.createHash('sha256'); contentHash.update(minCSS); @@ -149,18 +153,21 @@ const processFile = async (file, _details) => { // Pass the compiled css through rtlcss and autoprefixer to generate css for right-to-left languages. let rtlResult = await postcss([rtlcss, autoprefixer, cssMinify]).process(result.css, { from: baseName }); - const rtlCSS = rtlResult.css + ( - argv.enableSourcemaps && result.sourceMap - ? `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${ - Buffer.from(JSON.stringify(result.sourceMap)).toString('base64')}*/` - : '' - ); + const rtlCSS = + rtlResult.css + + (argv.enableSourcemaps && result.sourceMap + ? `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${Buffer.from( + JSON.stringify(result.sourceMap) + ).toString('base64')}*/` + : ''); const rtlContentHash = crypto.createHash('sha256'); rtlContentHash.update(rtlCSS); - const newRTLVersion = file.replace(/\.s?css$/, - `.rtl.${rtlContentHash.digest('hex').substring(0, 8)}.min.css`); + const newRTLVersion = file.replace( + /\.s?css$/, + `.rtl.${rtlContentHash.digest('hex').substring(0, 8)}.min.css` + ); fs.writeFileSync(path.resolve(__dirname, newRTLVersion), rtlCSS); const rtlAssetName = file.replace(/\.s?css$/, '.rtl.css'); @@ -180,8 +187,9 @@ const processFile = async (file, _details) => { } } else { if (argv.watchFiles) - console.log('\x1b[33mWatches established, and initial build complete.\n' - + 'Press Control-C to stop.\x1b[0m'); + console.log( + '\x1b[33mWatches established, and initial build complete.\n' + 'Press Control-C to stop.\x1b[0m' + ); ready = true; } @@ -202,22 +210,25 @@ for (const file of fs.readdirSync(themesDir, { withFileTypes: true })) { if (!file.isDirectory()) continue; if (!fs.existsSync(path.resolve(themesDir, file.name, 'math4-overrides.js'))) fs.closeSync(fs.openSync(path.resolve(themesDir, file.name, 'math4-overrides.js'), 'w')); - if (!fs.existsSync(path.resolve(themesDir, file.name, 'math4-overrides.css')) - && !fs.existsSync(path.resolve(themesDir, file.name, 'math4-overrides.scss'))) + if ( + !fs.existsSync(path.resolve(themesDir, file.name, 'math4-overrides.css')) && + !fs.existsSync(path.resolve(themesDir, file.name, 'math4-overrides.scss')) + ) fs.closeSync(fs.openSync(path.resolve(themesDir, file.name, 'math4-overrides.css'), 'w')); } // Set up the watcher. if (argv.watchFiles) console.log('\x1b[32mEstablishing watches and performing initial build.\x1b[0m'); -chokidar.watch(['js', 'themes'], { - ignored: /layouts|\.min\.(js|css)$/, - cwd: __dirname, // Make sure all paths are given relative to the htdocs directory. - usePolling: true, // Needed to get changes to symlinks. - interval: 500, - awaitWriteFinish: { stabilityThreshold: 500 }, - persistent: argv.watchFiles ? true : false -}) - .on('add', processFile).on('change', processFile).on('ready', processFile) +chokidar + .watch(['js', 'themes'], { + ignored: /layouts|\.min\.(js|css)$/, + cwd: __dirname, // Make sure all paths are given relative to the htdocs directory. + awaitWriteFinish: { stabilityThreshold: 500 }, + persistent: argv.watchFiles ? true : false + }) + .on('add', processFile) + .on('change', processFile) + .on('ready', processFile) .on('unlink', (file) => { // If a file is deleted, then also delete the corresponding generated file. if (assets[file]) { diff --git a/htdocs/images/webwork_logo.png b/htdocs/images/webwork_logo.png new file mode 100644 index 0000000000..4790e1c4d6 Binary files /dev/null and b/htdocs/images/webwork_logo.png differ diff --git a/htdocs/index.dist.html b/htdocs/index.dist.html index 532c6f4032..a82efd7355 100644 --- a/htdocs/index.dist.html +++ b/htdocs/index.dist.html @@ -1,4 +1,4 @@ - + WeBWorK Placeholder Page @@ -6,12 +6,10 @@

WeBWorK Placeholder Page

Exploring?

-

- This is the default page for the root url of this site. -

+

This is the default page for the root url of this site.

If you want to see something better here, then copy webwork2/htdocs/index.dist.html (this file) to - webwork/htdocs/index.html, and modify it to show what you want to show. Then that file will be displayed + webwork/htdocs/index.html, and modify it to show what you want to show. Then that file will be displayed instead.

diff --git a/htdocs/js/AchievementItems/achievementitems.js b/htdocs/js/AchievementItems/achievementitems.js deleted file mode 100644 index 567dea616c..0000000000 --- a/htdocs/js/AchievementItems/achievementitems.js +++ /dev/null @@ -1,17 +0,0 @@ -(() => { - for (const setSelect of document.querySelectorAll('select[data-problems]')) { - setSelect.addEventListener('change', () => { - const max = parseInt(Array.from(setSelect.querySelectorAll('option')) - .find((option) => option.value === setSelect.value)?.dataset.max ?? '0'); - - document.querySelectorAll(`#${setSelect.dataset.problems} option`).forEach((option, index) => { - option.style.display = index < max ? '' : 'none'; - }); - - // This is only used by the "Box of Transmogrification". - document.querySelectorAll(`#${setSelect.dataset.problems2} option`).forEach((option, index) => { - option.style.display = index < max ? '' : 'none'; - }); - }); - } -})(); diff --git a/htdocs/js/AchievementList/achievementlist.js b/htdocs/js/AchievementList/achievementlist.js new file mode 100644 index 0000000000..08406848b1 --- /dev/null +++ b/htdocs/js/AchievementList/achievementlist.js @@ -0,0 +1,138 @@ +(() => { + // Action form validation. + // Store event listeners so they can be removed. + const event_listeners = {}; + + const show_errors = (ids, elements) => { + for (const id of ids) elements.push(document.getElementById(id)); + for (const element of elements) { + if (element?.id.endsWith('_err_msg')) { + element?.classList.remove('d-none'); + } else { + element?.classList.add('is-invalid'); + if (!(element.id in event_listeners)) { + event_listeners[element.id] = hide_errors([], elements); + element?.addEventListener('change', event_listeners[element.id]); + } + } + } + }; + + const hide_errors = (ids, elements) => { + return () => { + for (const id of ids) elements.push(document.getElementById(id)); + for (const element of elements) { + if (element?.id.endsWith('_err_msg')) { + element?.classList.add('d-none'); + if (element.id === 'select_achievement_err_msg' && 'achievement_table' in event_listeners) { + document + .getElementById('achievement-table') + ?.removeEventListener('change', event_listeners.achievement_table); + delete event_listeners.achievement_table; + } + } else { + element?.classList.remove('is-invalid'); + if (element.id in event_listeners) { + element?.removeEventListener('change', event_listeners[element.id]); + delete event_listeners[element.id]; + } + } + } + }; + }; + + const is_achievement_selected = () => { + for (const achievement of document.getElementsByName('selected_achievements')) { + if (achievement.checked) return true; + } + const err_msg = document.getElementById('select_achievement_err_msg'); + err_msg?.classList.remove('d-none'); + if (!('achievement_table' in event_listeners)) { + event_listeners.achievement_table = hide_errors( + ['filter_select', 'edit_select', 'assign_select', 'export_select', 'score_select'], + [err_msg] + ); + document.getElementById('achievement-table')?.addEventListener('change', event_listeners.achievement_table); + } + return false; + }; + + document.getElementById('achievement-list')?.addEventListener('submit', (e) => { + const action = document.getElementById('current_action')?.value || ''; + if (action === 'filter') { + const filter_select = document.getElementById('filter_select'); + const filter = filter_select?.value || ''; + const filter_text = document.getElementById('filter_text'); + if (filter === 'selected' && !is_achievement_selected()) { + e.preventDefault(); + e.stopPropagation(); + show_errors(['select_achievement_err_msg'], [filter_select]); + } else if (filter === 'match_ids' && filter_text?.value === '') { + e.preventDefault(); + e.stopPropagation(); + show_errors(['filter_text_err_msg'], [filter_select, filter_text]); + } + } else if (['edit', 'assign', 'export', 'score'].includes(action)) { + const action_select = document.getElementById(`${action}_select`); + if (action_select.value === 'selected' && !is_achievement_selected()) { + e.preventDefault(); + e.stopPropagation(); + show_errors(['select_achievement_err_msg'], [action_select]); + } + } else if (action === 'import') { + const import_file = document.getElementById('import_file_select'); + if (!import_file.value.endsWith('.axp')) { + e.preventDefault(); + e.stopPropagation(); + show_errors(['import_file_err_msg'], [import_file]); + } + } else if (action === 'create') { + const create_text = document.getElementById('create_text'); + const create_select = document.getElementById('create_select'); + if (create_text.value === '') { + e.preventDefault(); + e.stopPropagation(); + show_errors(['create_file_err_msg'], [create_text]); + } else if (create_select?.value == 'copy' && !is_achievement_selected()) { + e.preventDefault(); + e.stopPropagation(); + show_errors(['select_achievement_err_msg'], [create_select]); + } + } else if (action === 'delete') { + const delete_confirm = document.getElementById('delete_select'); + if (!is_achievement_selected()) { + e.preventDefault(); + e.stopPropagation(); + } else if (delete_confirm.value != 'yes') { + e.preventDefault(); + e.stopPropagation(); + show_errors(['delete_confirm_err_msg'], [delete_confirm]); + } + } + }); + + // Remove all error messages when changing tabs. + for (const tab of document.querySelectorAll('a[data-bs-toggle="tab"]')) { + tab.addEventListener('shown.bs.tab', () => { + if (Object.keys(event_listeners).length != 0) + hide_errors( + [], + document.getElementById('achievement-list')?.querySelectorAll('div[id$=_err_msg], .is-invalid') + )(); + }); + } + + // Toggle the display of the filter elements as the filter select changes. + const filter_select = document.getElementById('filter_select'); + const filter_text_elements = document.getElementById('filter_text_elements'); + const filter_category_elements = document.getElementById('filter_category_elements'); + const filterElementToggle = () => { + if (filter_select?.value === 'match_ids') filter_text_elements.style.display = 'flex'; + else filter_text_elements.style.display = 'none'; + if (filter_select?.value === 'match_category') filter_category_elements.style.display = 'flex'; + else filter_category_elements.style.display = 'none'; + }; + + if (filter_select) filterElementToggle(); + filter_select?.addEventListener('change', filterElementToggle); +})(); diff --git a/htdocs/js/Achievements/achievements.scss b/htdocs/js/Achievements/achievements.scss new file mode 100644 index 0000000000..0a59532577 --- /dev/null +++ b/htdocs/js/Achievements/achievements.scss @@ -0,0 +1,52 @@ +.levelouterbar { + height: 20px; + border-style: solid; + border-width: 2px; + max-width: 400px; +} + +.levelinnerbar { + height: 100%; + background-color: var(--ww-achievement-level-color, #88d); +} + +.locked { + opacity: 0.65; + + img { + opacity: 0.4; + } +} + +/* width of cheevo progress bar controlled by perl code */ +.cheevoouterbar { + height: 10px; + border-style: solid; + border-width: 2px; + width: 200px; +} + +.cheevoinnerbar { + height: 100%; + background-color: var(--ww-achievement-level-color, #88d); +} + +.cheevo-toast-container { + z-index: 21; +} + +.cheevo-toast img { + height: 75px; + width: 75px; +} + +.cheevopopuptext { + margin-left: 15px; + + h1 { + margin-top: 0px; + margin-bottom: 5px; + font-size: 20px; + font-weight: bold; + } +} diff --git a/htdocs/js/ActionTabs/actiontabs.js b/htdocs/js/ActionTabs/actiontabs.js index b7c7aca97f..50c43da164 100644 --- a/htdocs/js/ActionTabs/actiontabs.js +++ b/htdocs/js/ActionTabs/actiontabs.js @@ -1,11 +1,48 @@ (() => { const takeAction = document.getElementById('take_action'); + const currentAction = document.getElementById('current_action'); document.querySelectorAll('.action-link').forEach((actionLink) => { - const currentAction = document.getElementById('current_action'); actionLink.addEventListener('show.bs.tab', () => { if (takeAction) takeAction.value = actionLink.textContent; if (currentAction) currentAction.value = actionLink.dataset.action; }); }); + + // Submit the form when a sort header is clicked or enter or space is pressed when it has focus. + if (currentAction) { + for (const header of document.querySelectorAll('.sort-header')) { + const submitSortMethod = (e) => { + e.preventDefault(); + + currentAction.value = 'sort'; + + const sortInput = document.createElement('input'); + sortInput.name = 'labelSortMethod'; + sortInput.value = header.dataset.sortField; + sortInput.type = 'hidden'; + currentAction.form.append(sortInput); + + currentAction.form.submit(); + }; + + header.addEventListener('click', submitSortMethod); + header.addEventListener('keydown', (e) => { + if (e.key === ' ' || e.key === 'Enter') submitSortMethod(e); + }); + + const orderToggleButton = header.parentElement.querySelector('button.sort-order'); + orderToggleButton?.addEventListener('click', () => { + currentAction.value = 'sort'; + + const sortOrderInput = document.createElement('input'); + sortOrderInput.name = 'labelSortOrder'; + sortOrderInput.value = orderToggleButton.dataset.sortPriority; + sortOrderInput.type = 'hidden'; + currentAction.form.append(sortOrderInput); + + currentAction.form.submit(); + }); + } + } })(); diff --git a/htdocs/js/AddUsers/add-users.js b/htdocs/js/AddUsers/add-users.js new file mode 100644 index 0000000000..5976698420 --- /dev/null +++ b/htdocs/js/AddUsers/add-users.js @@ -0,0 +1,16 @@ +(() => { + const passwordSelect = document.getElementById('fallback_password_source'); + + const setPlaceholders = () => { + for (const input of document.querySelectorAll('.new_password')) { + let placeholder = 'placeholder'; + for (const part of passwordSelect.value.split('_')) { + placeholder += part.charAt(0).toUpperCase() + part.slice(1); + } + input.setAttribute('placeholder', passwordSelect.dataset[placeholder]); + } + }; + + passwordSelect.addEventListener('change', setPlaceholders); + setPlaceholders(); +})(); diff --git a/htdocs/js/Config/config.js b/htdocs/js/Config/config.js new file mode 100644 index 0000000000..9ebcf860a1 --- /dev/null +++ b/htdocs/js/Config/config.js @@ -0,0 +1,28 @@ +(() => { + const configForm = document.getElementById('config-form'); + if (!configForm) return; + + const elementInitialValues = []; + for (const element of configForm.elements) { + if (element.name === 'current_tab') continue; + elementInitialValues.push([element, element.type === 'checkbox' ? element.checked : element.value]); + } + + window.onbeforeunload = () => { + for (const [element, initialValue] of elementInitialValues) { + if ( + (element.type === 'checkbox' && element.checked !== initialValue) || + (element.type !== 'checkbox' && element.value !== initialValue) + ) + return true; + } + }; + + configForm.addEventListener('submit', () => (window.onbeforeunload = null)); + + if (configForm.current_tab) { + document.querySelectorAll('.tab-link').forEach((tabLink) => { + tabLink.addEventListener('show.bs.tab', () => (configForm.current_tab.value = tabLink.dataset.tab)); + }); + } +})(); diff --git a/htdocs/js/CourseAdmin/manage_otp_secrets.js b/htdocs/js/CourseAdmin/manage_otp_secrets.js new file mode 100644 index 0000000000..d452567068 --- /dev/null +++ b/htdocs/js/CourseAdmin/manage_otp_secrets.js @@ -0,0 +1,34 @@ +(() => { + // Save user menus to be updated. + const sourceSingleUserMenu = document.getElementById('sourceSingleUserID'); + const destSingleUserMenu = document.getElementById('destSingleUserID'); + const sourceMultipleUserMenu = document.getElementById('sourceMultipleUserID'); + const destResetUserMenu = document.getElementById('destResetUserID'); + + const updateUserMenu = (e, menu, selectFirst) => { + const userList = e.target.options[e.target.selectedIndex].dataset.users.split(':'); + while (menu.length > 1) menu.lastChild.remove(); + if (selectFirst) { + menu.selectedIndex = 0; + } + userList.forEach((user) => { + const userOption = document.createElement('option'); + userOption.value = userOption.text = user; + menu.append(userOption); + }); + }; + + // Update user menu when course ID is selected/changed. + document.getElementById('sourceSingleCourseID')?.addEventListener('change', (e) => { + updateUserMenu(e, sourceSingleUserMenu, true); + }); + document.getElementById('destSingleCourseID')?.addEventListener('change', (e) => { + updateUserMenu(e, destSingleUserMenu, true); + }); + document.getElementById('sourceMultipleCourseID')?.addEventListener('change', (e) => { + updateUserMenu(e, sourceMultipleUserMenu, false); + }); + document.getElementById('sourceResetCourseID')?.addEventListener('change', (e) => { + updateUserMenu(e, destResetUserMenu, false); + }); +})(); diff --git a/htdocs/js/DatePicker/datepicker.js b/htdocs/js/DatePicker/datepicker.js index bdaef40623..193ae9b6a1 100644 --- a/htdocs/js/DatePicker/datepicker.js +++ b/htdocs/js/DatePicker/datepicker.js @@ -5,6 +5,7 @@ const datetimeFormats = { en: 'L/d/yy, h:mm a', 'en-US': 'L/d/yy, h:mm a', + 'en-GB': 'dd/LL/yyyy, HH:mm', 'cs-CZ': 'dd.LL.yy H:mm', de: 'dd.LL.yy, HH:mm', el: 'd/L/yy, h:mm a', @@ -24,39 +25,69 @@ const name = open_rule.name.replace('.open_date', ''); const groupRules = [ - open_rule, - document.querySelector('input[id="' + name + '.due_date_id"]'), - document.querySelector('input[id="' + name + '.answer_date_id"]') + [open_rule], + [document.getElementById(`${name}.due_date_id`)], + [document.getElementById(`${name}.answer_date_id`)] ]; - const reduced_rule = document.querySelector('input[id="' + name + '.reduced_scoring_date_id"]'); - if (reduced_rule) groupRules.splice(1, 0, reduced_rule); + const reduced_rule = document.getElementById(`${name}.reduced_scoring_date_id`); + if (reduced_rule) groupRules.splice(1, 0, [reduced_rule]); - const update = () => { - for (let i = 1; i < groupRules.length; ++i) { - const prevFieldDate = groupRules[i - 1].parentNode._flatpickr.selectedDates[0]; - const thisFieldDate = groupRules[i].parentNode._flatpickr.selectedDates[0]; - if (prevFieldDate && thisFieldDate && prevFieldDate > thisFieldDate) { - groupRules[i].parentNode._flatpickr.setDate(prevFieldDate, true); - } - } + // Compute the time difference between a time in the browser timezone and the same time in the course timezone. + // flatpickr gives the time in the browser's timezone, and this is used to adjust to the course timezone. + // Note that the input time is in seconds and output times is in milliseconds. + const timezoneAdjustment = (time) => { + const dateTime = new Date(0); + dateTime.setUTCSeconds(time); + return ( + new Date(dateTime.toLocaleString('en-US')).getTime() - + new Date( + dateTime.toLocaleString('en-US', { timeZone: open_rule.dataset.timezone ?? 'America/New_York' }) + ).getTime() + ); }; for (const rule of groupRules) { - const orig_value = rule.value; + const classValue = document.getElementsByName(`${rule[0].name}.class_value`)[0]?.dataset.classValue; + const value = rule[0].value || classValue; + rule.push(value ? parseInt(value) * 1000 - timezoneAdjustment(parseInt(value)) : 0); + if (classValue) rule.push(parseInt(classValue) * 1000 - timezoneAdjustment(parseInt(classValue))); + } - luxon.Settings.defaultLocale = rule.dataset.locale ?? 'en'; + const update = (input) => { + const activeIndex = groupRules.findIndex((r) => r[0] === input); + if (activeIndex == -1) return; + const activeFieldDate = + groupRules[activeIndex][0]?.parentNode._flatpickr.selectedDates[0]?.getTime() || + groupRules[activeIndex][2] || + groupRules[activeIndex][1]; - // Compute the time difference between the current browser timezone and the course timezone. - // flatpickr gives the time in the browser's timezone, and this is used to adjust to the course timezone. - // Note that this is in seconds. - const timezoneAdjustment = ( - (new Date((new Date).toLocaleString('en-US'))).getTime() - - (new Date((new Date).toLocaleString('en-US', - { timeZone: rule.dataset.timezone ?? 'America/New_York' }))).getTime() - ); + for (let i = 0; i < groupRules.length; ++i) { + if (i == activeIndex) continue; + const thisFieldDate = + groupRules[i][0]?.parentNode._flatpickr.selectedDates[0]?.getTime() || + groupRules[i][2] || + groupRules[i][1]; + if (i < activeIndex && thisFieldDate > activeFieldDate) + groupRules[i][0].parentNode._flatpickr.setDate( + activeFieldDate === groupRules[i][2] ? undefined : activeFieldDate, + true + ); + else if (i > activeIndex && thisFieldDate < activeFieldDate) + groupRules[i][0].parentNode._flatpickr.setDate( + activeFieldDate === groupRules[i][2] ? undefined : activeFieldDate, + true + ); + } + }; + + for (const rule of groupRules) { + const orig_value = rule[0].value; + let fallbackDate = rule[1] ? new Date(rule[1]) : new Date(); + + luxon.Settings.defaultLocale = rule[0].dataset.locale ?? 'en'; - const fp = flatpickr(rule.parentNode, { + const fp = flatpickr(rule[0].parentNode, { allowInput: true, enableTime: true, minuteIncrement: 1, @@ -74,15 +105,15 @@ disableMobile: true, wrap: true, plugins: [ - new confirmDatePlugin({ confirmText: rule.dataset.doneText ?? 'Done', showAlways: true }), + new confirmDatePlugin({ confirmText: rule[0].dataset.doneText ?? 'Done', showAlways: true }), new ShortcutButtonsPlugin({ button: [ { - label: rule.dataset.todayText ?? 'Today', + label: rule[0].dataset.todayText ?? 'Today', attributes: { class: 'btn btn-sm btn-secondary ms-auto me-1 mb-1' } }, { - label: rule.dataset.nowText ?? 'Now', + label: rule[0].dataset.nowText ?? 'Now', attributes: { class: 'btn btn-sm btn-secondary me-auto mb-1' } } ], @@ -94,51 +125,59 @@ selectedDate.setFullYear(today.getFullYear()); selectedDate.setMonth(today.getMonth()); selectedDate.setDate(today.getDate()); - fp.setDate(selectedDate); + fp.setDate(selectedDate, true); } else if (index === 1) { - fp.setDate(new Date()); + fp.setDate(new Date(), true); } } }) ], - onChange(selectedDates) { + onChange() { if (this.input.value === orig_value) this.altInput.classList.remove('changed'); else this.altInput.classList.add('changed'); }, - onClose: update, - onReady(selectedDates) { + onClose() { + return update(this.input); + }, + onReady() { // Flatpickr hides the original input and adds the alternate input after it. That messes up the // bootstrap input group styling. So move the now hidden original input after the created alternate // input to fix that. this.altInput.after(this.input); + // Move the id of the now hidden input onto the added input so the labels still work. + this.altInput.id = this.input.id; + + // Remove the placeholder from the hidden input. Flatpickr has copied that to the added input, and + // that isn't valid on a hidden input. + this.input.removeAttribute('id'); + this.input.removeAttribute('placeholder'); + // Make the alternate input left-to-right even for right-to-left languages. this.altInput.dir = 'ltr'; - this.altInput.addEventListener('blur', update); + this.altInput.addEventListener('blur', () => update(this.input)); }, parseDate(datestr, format) { // Deal with the case of a unix timestamp. The timezone needs to be adjusted back as this is for // the unix timestamp stored in the hidden input whose value will be sent to the server. - if (format === 'U') return new Date(parseInt(datestr) * 1000 - timezoneAdjustment); + if (format === 'U') + return new Date(parseInt(datestr) * 1000 - timezoneAdjustment(parseInt(datestr))); // Next attempt to parse the datestr with the current format. This should not be adjusted. It is // for display only. const date = luxon.DateTime.fromFormat(datestr.replaceAll(/\u202F/g, ' ').trim(), format); - if (date.isValid) return date.toJSDate(); + if (date.isValid) fallbackDate = date.toJSDate(); // Finally, fall back to the previous value in the original input if that failed. This is the case // that the user typed a time that isn't in the valid format. So fallback to the last valid time // that was displayed. This also should not be adjusted. - return new Date(this.lastFormattedDate.getTime()); + return fallbackDate; }, formatDate(date, format) { - // Save this date for the fallback in parseDate. - this.lastFormattedDate = date; - // In this case the date provided is in the browser's time zone. So it needs to be adjusted to the // timezone of the course. - if (format === 'U') return (date.getTime() + timezoneAdjustment) / 1000; + if (format === 'U') return (date.getTime() + timezoneAdjustment(date.getTime() / 1000)) / 1000; return luxon.DateTime.fromMillis(date.getTime()).toFormat( datetimeFormats[luxon.Settings.defaultLocale] @@ -146,7 +185,7 @@ } }); - rule.nextElementSibling.addEventListener('keydown', (e) => { + rule[0].nextElementSibling.addEventListener('keydown', (e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); fp.open(); diff --git a/htdocs/js/FileManager/filemanager.js b/htdocs/js/FileManager/filemanager.js index 8f579d6bf8..4cd96ff428 100644 --- a/htdocs/js/FileManager/filemanager.js +++ b/htdocs/js/FileManager/filemanager.js @@ -12,31 +12,83 @@ document.getElementsByName('directory')[0]?.addEventListener('change', () => doAction('Go')); document.getElementsByName('dates')[0]?.addEventListener('click', () => doAction('Refresh')); - files?.addEventListener('dblclick', () => doAction('View')); + files?.addEventListener('dblclick', () => { + if (files.selectedOptions[0].dataset.type & 0b11010) doAction('View'); + else { + const container = document.createElement('div'); + container.classList.add('toast-container', 'top-50', 'start-50', 'translate-middle'); - // If on the confirmation page, then focus the "name" input. - form.querySelector('input[name="name"]')?.focus(); + const toast = document.createElement('div'); + toast.classList.add('toast'); + toast.setAttribute('role', 'alert'); + toast.setAttribute('aria-live', 'assertive'); + toast.setAttribute('aria-atomit', 'true'); + const toastContent = document.createElement('div'); + toastContent.classList.add('d-flex', 'alert', 'alert-danger', 'mb-0', 'p-0'); + + const toastBody = document.createElement('div'); + toastBody.classList.add('toast-body'); + toastBody.textContent = + files.selectedOptions[0].dataset.type & 0b1 + ? files.dataset.linkMessage || 'Symbolic links can not be followed.' + : files.dataset.nonViewableMessage || 'This is not a viewable file type.'; + + const closeButton = document.createElement('button'); + closeButton.type = 'button'; + closeButton.classList.add('btn-close', 'me-2', 'm-auto'); + closeButton.dataset.bsDismiss = 'toast'; + closeButton.setAttribute('aria-label', files.dataset.closeTitle || 'Close'); + + toastContent.append(toastBody, closeButton); + + toast.append(toastContent); + container.append(toast); + document.body.append(container); + + const bsToast = new bootstrap.Toast(toast); + bsToast.show(); + toast.addEventListener('hidden.bs.toast', () => { + bsToast.dispose(); + container.remove(); + }); + } + }); + + // If on the confirmation page (and not the edit page), then focus the "name" input. + if (!document.getElementsByName('data')[0]) document.getElementsByName('name')[0]?.focus(); } - const fileActionButtons = ['View', 'Edit', 'Download', 'Rename', 'Copy', 'Delete', 'MakeArchive'].map((buttonId) => - document.getElementById(buttonId) - ); + // The bits for types from least to most significant digit are set in the directoryListing method of + // lib/WeBWorK/ContentGenerator/Instructor/FileManager.pm to mean a file is a + // link, directory, regular file, text file, or image file. + const fileActions = [ + { id: 'View', types: 0b11010, multiple: 0 }, + { id: 'Edit', types: 0b01000, multiple: 0 }, + { id: 'Download', types: 0b100, multiple: 0 }, + { id: 'Rename', types: 0b111, multiple: 0 }, + { id: 'Copy', types: 0b100, multiple: 0 }, + { id: 'Delete', types: 0b111, multiple: 1 }, + { id: 'MakeArchive', types: 0b111, multiple: 1 } + ]; + fileActions.map((button) => (button.elt = document.getElementById(button.id))); const archiveButton = document.getElementById('MakeArchive'); const checkFiles = () => { - const state = files.selectedIndex < 0; + const selectedFiles = files.selectedOptions; - for (const button of fileActionButtons) { - if (button) button.disabled = state; + for (const button of fileActions) { + if (!button.elt) continue; + if (selectedFiles.length) { + if (selectedFiles.length == 1 && !button.multiple) + button.elt.disabled = !(button.types & selectedFiles[0].dataset.type); + else button.elt.disabled = !button.multiple; + } else { + button.elt.disabled = true; + } } - if (archiveButton && !state) { - const numSelected = files.querySelectorAll('option:checked').length; - if ( - numSelected === 0 || - numSelected > 1 || - !/\.(tar|tar\.gz|tgz)$/.test(files.children[files.selectedIndex].value) - ) + if (archiveButton && selectedFiles.length) { + if (selectedFiles.length > 1 || !/\.(tar|tar\.gz|tgz|zip)$/.test(selectedFiles[0].value)) archiveButton.value = archiveButton.dataset.archiveText; else archiveButton.value = archiveButton.dataset.unarchiveText; } @@ -45,6 +97,19 @@ files?.addEventListener('change', checkFiles); if (files) checkFiles(); + const archiveFilenameInput = document.getElementById('archive-filename'); + const archiveTypeSelect = document.getElementById('archive-type'); + if (archiveFilenameInput && archiveTypeSelect) { + archiveTypeSelect.addEventListener('change', () => { + if (archiveTypeSelect.value) { + archiveFilenameInput.value = archiveFilenameInput.value.replace( + /\.(zip|tgz|tar.gz)$/, + `.${archiveTypeSelect.value}` + ); + } + }); + } + const file = document.getElementById('file'); const uploadButton = document.getElementById('Upload'); const checkFile = () => (uploadButton.disabled = file.value === ''); diff --git a/htdocs/js/GatewayQuiz/gateway.js b/htdocs/js/GatewayQuiz/gateway.js index c5261833e4..66a8599d96 100644 --- a/htdocs/js/GatewayQuiz/gateway.js +++ b/htdocs/js/GatewayQuiz/gateway.js @@ -12,9 +12,10 @@ const timerDiv = document.getElementById('gwTimer'); // The timer div element let actuallySubmit = false; // This needs to be set to true to allow an actual submission. // The 'Grade Test' submit button. - const submitAnswers = document.gwquiz.elements.submitAnswers instanceof NodeList - ? document.gwquiz.elements.submitAnswers[document.gwquiz.elements.submitAnswers.length - 1] - : document.gwquiz.elements.submitAnswers; + const submitAnswers = + document.gwquiz.elements.submitAnswers instanceof NodeList + ? document.gwquiz.elements.submitAnswers[document.gwquiz.elements.submitAnswers.length - 1] + : document.gwquiz.elements.submitAnswers; let timeDelta; // The difference between the browser time and the server time let serverDueTime; // The time the test is due let gracePeriod; // The grace period @@ -32,7 +33,14 @@ const alertToast = (message, delay = 5000) => { const toastContainer = document.createElement('div'); toastContainer.classList.add( - 'gwAlert', 'toast-container', 'position-fixed', 'top-0', 'start-50', 'translate-middle-x', 'p-3'); + 'gwAlert', + 'toast-container', + 'position-fixed', + 'top-0', + 'start-50', + 'translate-middle-x', + 'p-3' + ); toastContainer.innerHTML = '