diff --git a/.gitattributes b/.gitattributes index 7e1e2899e..9db083dea 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,2 @@ -lib/WeBWorK/htdocs/** linguist-vendored public/** linguist-vendored *.pl linguist-language=Perl diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 0106e6c4c..000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,2 +0,0 @@ -# Default reviewers on all PRs -* @drdrew42 diff --git a/.github/workflows/createContainer.yml b/.github/workflows/createContainer.yml index 1878c42ba..f237093f4 100644 --- a/.github/workflows/createContainer.yml +++ b/.github/workflows/createContainer.yml @@ -3,7 +3,7 @@ name: Github Packages Release on: push: branches: - - master + - main - development tags: - v* diff --git a/.gitignore b/.gitignore index 7204d00bb..e127c93b2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,14 @@ lib/WeBWorK/bin/* webwork-open-problem-library/ private/ tmp/* +!tmp/.gitkeep logs/*.log + node_modules -node_modules/* +public/**/*.min.js +public/**/*.min.css +public/static-assets.json + *.o *.pm.tdy *.bs diff --git a/Dockerfile b/Dockerfile index 1018d7de0..011fc4114 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,5 @@ FROM ubuntu:20.04 -LABEL org.opencontainers.image.source=https://github.com/drdrew42/renderer -MAINTAINER drdrew42 +LABEL org.opencontainers.image.source=https://github.com/openwebwork/renderer WORKDIR /usr/app ARG DEBIAN_FRONTEND=noninteractive @@ -11,10 +10,8 @@ RUN apt-get update \ apt-utils \ git \ gcc \ - npm \ make \ curl \ - nodejs \ dvipng \ openssl \ libc-dev \ @@ -43,6 +40,9 @@ RUN apt-get update \ libmath-random-secure-perl \ libdata-structure-util-perl \ liblocale-maketext-lexicon-perl \ + libyaml-libyaml-perl \ + && curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \ + && apt-get install -y --no-install-recommends --no-install-suggests nodejs \ && apt-get clean \ && rm -fr /var/lib/apt/lists/* /tmp/* @@ -51,7 +51,13 @@ RUN cpanm install Mojo::Base Statistics::R::IO::Rserve Date::Format Future::Asyn COPY . . -RUN cd lib/WeBWorK/htdocs && npm install && cd ../../.. +RUN cp render_app.conf.dist render_app.conf + +RUN cp conf/pg_config.yml lib/PG/conf/pg_config.yml + +RUN cd public/ && npm install && cd .. + +RUN cd lib/PG/htdocs && npm install && cd ../../.. EXPOSE 3000 diff --git a/Dockerfile_with_OPL b/Dockerfile_with_OPL index eb5847acb..ecde4c5a0 100644 --- a/Dockerfile_with_OPL +++ b/Dockerfile_with_OPL @@ -1,6 +1,5 @@ FROM ubuntu:20.04 -LABEL org.opencontainers.image.source=https://github.com/drdrew42/renderer -MAINTAINER drdrew42 +LABEL org.opencontainers.image.source=https://github.com/openwebwork/renderer WORKDIR /usr/app ARG DEBIAN_FRONTEND=noninteractive @@ -11,10 +10,8 @@ RUN apt-get update \ apt-utils \ git \ gcc \ - npm \ make \ curl \ - nodejs \ dvipng \ openssl \ libc-dev \ @@ -43,6 +40,9 @@ RUN apt-get update \ libmath-random-secure-perl \ libdata-structure-util-perl \ liblocale-maketext-lexicon-perl \ + libyaml-libyaml-perl \ + && curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \ + && apt-get install -y --no-install-recommends --no-install-suggests nodejs \ && apt-get clean \ && rm -fr /var/lib/apt/lists/* /tmp/* @@ -64,7 +64,11 @@ COPY . . RUN cp render_app.conf.dist render_app.conf -RUN cd lib/WeBWorK/htdocs && npm install && cd ../../.. +RUN cp conf/pg_config.yml lib/PG/conf/pg_config.yml + +RUN npm install + +RUN cd lib/PG/htdocs && npm install && cd ../../.. EXPOSE 3000 diff --git a/README.md b/README.md index fe51a4216..d1ae2f953 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # WeBWorK Standalone Problem Renderer & Editor -![Commit Activity](https://img.shields.io/github/commit-activity/m/drdrew42/renderer?style=plastic) -![License](https://img.shields.io/github/license/drdrew42/renderer?style=plastic) - +![Commit Activity](https://img.shields.io/github/commit-activity/m/openwebwork/renderer?style=plastic) +![License](https://img.shields.io/github/license/openwebwork/renderer?style=plastic) This is a PG Renderer derived from the WeBWorK2 codebase -* https://github.com/openwebwork/WeBWorK2 -## DOCKER CONTAINER INSTALL ### +* [https://github.com/openwebwork/webwork2](https://github.com/openwebwork/webwork2) -``` +## DOCKER CONTAINER INSTALL + +```bash mkdir volumes mkdir container git clone https://github.com/openwebwork/webwork-open-problem-library volumes/webwork-open-problem-library -git clone --recursive https://github.com/drdrew42/renderer container/ +git clone --recursive https://github.com/openwebwork/renderer container/ docker build --tag renderer:1.0 ./container docker run -d \ @@ -25,35 +25,43 @@ docker run -d \ renderer:1.0 ``` -If you have non-OPL content, it can be mounted as a volume at `/usr/app/private` by adding the following line to the `docker run` command: +If you have non-OPL content, it can be mounted as a volume at `/usr/app/private` by adding the following line to the +`docker run` command: -``` +```bash --mount type=bind,source=/pathToYourLocalContentRoot,target=/usr/app/private \ ``` -A default configuration file is included in the container, but it can be overridden by mounting a replacement at the application root. This is necessary if, for example, you want to run the container in `production` mode. +A default configuration file is included in the container, but it can be overridden by mounting a replacement at the + application root. This is necessary if, for example, you want to run the container in `production` mode. -``` +```bash --mount type=bind,source=/pathToYour/render_app.conf,target=/usr/app/render_app.conf \ ``` -## LOCAL INSTALL ### +## LOCAL INSTALL If using a local install instead of docker: -* Clone the renderer and its submodules: `git clone --recursive https://github.com/drdrew42/renderer` +* Clone the renderer and its submodules: `git clone --recursive https://github.com/openwebwork/renderer` * Enter the project directory: `cd renderer` -* Install perl dependencies listed in Dockerfile (CPANMinus recommended) +* Install Perl dependencies listed in Dockerfile (CPANMinus recommended) * clone webwork-open-problem-library into the provided stub ./webwork-open-problem-library - - `git clone https://github.com/openwebwork/webwork-open-problem-library ./webwork-open-problem-library` + * `git clone https://github.com/openwebwork/webwork-open-problem-library ./webwork-open-problem-library` * copy `render_app.conf.dist` to `render_app.conf` and make any desired modifications -* install other dependencies - - `cd lib/WeBWorK/htdocs` - - `npm install` -* start the app with `morbo ./script/render_app` or `morbo -l http://localhost:3000 ./script/render_app` if changing root url +* copy `conf/pg_config.yml` to `lib/PG/pg_config.yml` and make any desired modifications +* install third party JavaScript dependencies + * `cd private/` + * `npm install` + * `cd ..` +* install PG JavaScript dependencies + * `cd lib/PG/htdocs` + * `npm install` +* start the app with `morbo ./script/render_app` or `morbo -l http://localhost:3000 ./script/render_app` if changing + root url * access on `localhost:3000` by default or otherwise specified root url -# Editor Interface +## Editor Interface * point your browser at [`localhost:3000`](http://localhost:3000/) * select an output format (see below) @@ -64,55 +72,115 @@ If using a local install instead of docker: ![image](https://user-images.githubusercontent.com/3385756/129100124-72270558-376d-4265-afe2-73b5c9a829af.png) -# Renderer API -Can be interfaced through `/render-api` - -## Parameters -| Key | Type | Default Value | Required | Description | Notes | -| --- | ---- | ------------- | -------- | ----------- | ----- | -| problemSourceURL | string | null | true if `sourceFilePath` and `problemSource` are null | The URL from which to fetch the problem source code | Takes precedence over `problemSource` and `sourceFilePath`. A request to this URL is expected to return valid pg source code in base64 encoding. | -| problemSource | string (base64 encoded) | null | true if `problemSourceURL` and `sourceFilePath` are null | The source code of a problem to be rendered | Takes precedence over `sourceFilePath`. | -| sourceFilePath | string | null | true if `problemSource` and `problemSourceURL` are null | The path to the file that contains the problem source code | Can begin with Library/ or Contrib/, in which case the renderer will automatically adjust the path relative to the webwork-open-problem-library root. Path may also begin with `private/` for local, non-OPL content. | -| problemSeed | number | NA | true | The seed to determine the randomization of a problem | | -| psvn | number | 123 | false | used for consistent randomization between problems | | -| formURL | string | /render-api | false | the URL for form submission | | -| baseURL | string | / | false | the URL for relative paths | | -| format | string | '' | false | Determine how the response is formatted ('html' or 'json') || -| outputFormat | string (enum) | static | false | Determines how the problem should render, see below descriptions below | | -| language | string | en | false | Language to render the problem in (if supported) | | -| showHints | number (boolean) | 1 | false | Whether or not to show hints (restrictions apply) | Irrelevant if `permissionLevel >= 10`, in which case `showHints` is regarded as automatically 'true' | -| showSolutions | number (boolean) | 0 | false | Whether or not to show the solutions (restrictions apply) | Irrelevant if `permissionLevel >= 10`, in which case `showSolutions` is regarded as automatically 'true' | -| permissionLevel | number | 0 | false | Affects the rendering of hints and solutions. Also controls display of scaffold problems (possibly more) | See the levels we use below | -| problemNumber | number | 1 | false | We don't use this | | -| numCorrect | number | 0 | false | The number of correct attempts on a problem | | -| numIncorrect | number | 1000 | false | the number of incorrect attempts on this problem | Relevant for triggering hints that are not immediately available | -| processAnswers | number (boolean) | 1 | false | Determines whether or not answer json is populated, and whether or not problem_result and problem_state are non-empty | | -| answersSubmitted | number (boolean) | ? | false? | Determines whether to process form-data associated to the available input fields | | -| showSummary | number (boolean) | ? | false? | Determines whether or not to show the summary result of processing the form-data associated with `answersSubmitted` above || -| showComments | number (boolean) | 0 | false | Renders author comment field at the end of the problem || -| includeTags | number (boolean) | 0 | false | Includes problem tags in the returned JSON | Only relevant when requesting `format: 'json'` | - -## Output Format -| Key | Description | -| ----- | ----- | -| static | zero buttons, locked form fields (read-only) | -| nosubmit | zero buttons, editable (for exams, save problem state and submit all together) | -| single | one submit button (intended for graded content) | -| classic | preview + submit buttons | -| simple | preview + submit + show answers buttons | -| practice | check answers + show answers buttons | - -## Permission level -| Key | Value | -| --- | ----- | -| student | 0 | -| prof | 10 | -| admin | 20 | - -## Permission logic summary for hints and solutions -* If `permissionLevel >= 10`, then hints and solutions will be rendered - no exceptions. -* If `permissionLevel < 10`, then: - - solutions (if they are provided in the pg source code) will be rendered if and only if `showSolutions` is true. - - hints (if they are provided in the pg source code) will be rendered if and only if: - + `showHints` is true, and - + `numCorrect + numIncorrect > n` where `n` is set by the pg sourcce code being rendered +## Server Configuration + +Modification of `baseURL` may be necessary to separate multiple services running on `SITE_HOST`, and will be used to extend `SITE_HOST`. The result of this extension will serve as the root URL for accessing the renderer (and any supplementary assets it may need to provide in support of a rendered problem). If `baseURL` is an absolute URL, it will be used verbatim -- userful if the renderer is running behind a load balancer. + +By default, `formURL` will further extend `baseURL`, and serve as the form-data target for user interactions with problems rendered by this service. If `formURL` is an absolute URL, it will be used verbatim -- useful if your implementation intends to sit in between the user and the renderer. + +## Renderer API + +Can be accessed by POST to `{SITE_HOST}{baseURL}{formURL}`. + +By default, `localhost:3000/render-api`. + +### **REQUIRED PARAMETERS** + +The bare minimum of parameters that must be included are: +* the code for the problem, so, **ONE** of the following (in order of precedence): + * `problemSource` (raw pg source code, _can_ be base64 encoded) + * `sourceFilePath` (relative to OPL `Library/`, `Contrib/`; or in `private/`) + * `problemSourceURL` (fetch the pg source from remote server) +* a "seed" value for consistent randomization + * `problemSeed` (integer) + +| Key | Type | Description | Notes | +| --- | ---- | ----------- | ----- | +| problemSource | string (possibly base64 encoded) | The source code of a problem to be rendered | Takes precedence over `sourceFilePath`. | +| sourceFilePath | string | The path to the file that contains the problem source code | Renderer will automatically adjust `Library/` and `Contrib/` relative to the webwork-open-problem-library root. Path may also begin with `private/` for local, non-OPL content. | +| problemSourceURL | string | The URL from which to fetch the problem source code | Takes precedence over `problemSource` and `sourceFilePath`. A request to this URL is expected to return valid pg source code in base64 encoding. | +| problemSeed | number | The seed that determines the randomization of a problem | | + +**ALL** other request parameters are optional. + +### Infrastructure Parameters + +The defaults for these parameters are set in `render_app.conf`, but these can be overridden on a per-request basis. + +| Key | Type | Default Value | Description | Notes | +| --- | ---- | ------------- | ----------- | ----- | +| baseURL | string | '/' (as set in `render_app.conf`) | the URL for relative paths | | +| formURL | string | '/render-api' (as set in `render_app.conf`) | the URL for form submission | | + +### Display Parameters + +#### Formatting + +Parameters that control the structure and templating of the response. + +| Key | Type | Default Value | Description | Notes | +| --- | ---- | ------------- | ----------- | ----- | +| language | string | en | Language to render the problem in (if supported) | affects the translation of template strings, _not_ actual problem content | +| _format | string | 'html' | Determine how the response is _structured_ ('html' or 'json') | usually 'html' if the user is directly interacting with the renderer, 'json' if your CMS sits between user and renderer | +| outputFormat | string | 'default' | Determines how the problem should be formatted | 'default', 'static', 'PTX', 'raw', or | +| displayMode | string | 'MathJax' | How to prepare math content for display | 'MathJax' or 'ptx' | + +#### User Interactions + +Control how the user is allowed to interact with the rendered problem. + +Requesting `outputFormat: 'static'` will prevent any buttons from being included in the rendered output, regardless of the following options. + +| Key | Type | Default Value | Description | Notes | +| --- | ---- | ------------- | ----------- | ----- | +| hidePreviewButton | number (boolean) | false | "Preview My Answers" is enabled by default | | +| hideCheckAnswersButton | number (boolean) | false | "Submit Answers" is enabled by default | | +| showCorrectAnswersButton | number (boolean) | `isInstructor` | "Show Correct Answers" is disabled by default, enabled if `isInstructor` is true (see below) | | + +#### Content + +Control what is shown to the user: hints, solutions, attempt results, scores, etc. + +| Key | Type | Default Value | Description | Notes | +| --- | ---- | ------------- | ----------- | ----- | +| permissionLevel | number | 0 | **DEPRECATED.** Use `isInstructor` instead. | +| isInstructor | number (boolean) | 0 | Is the user viewing the problem an instructor or not. | Used by PG to determine if scaffolds can be allowed to be open among other things | +| showHints | number (boolean) | 1 | Whether or not to show hints | | +| showSolutions | number (boolean) | `isInstructor` | Whether or not to show the solutions | | +| hideAttemptsTable | number (boolean) | 0 | Hide the table of answer previews/results/messages | If you have a replacement for flagging the submitted entries as correct/incorrect | +| showSummary | number (boolean) | 1 | Determines whether or not to show a summary of the attempt underneath the table | Only relevant if the Attempts Table is shown `hideAttemptsTable: false` (default) | +| showComments | number (boolean) | 0 | Renders author comment field at the end of the problem | | +| showFooter | number (boolean) | 0 | Show version information and WeBWorK copyright footer | | +| includeTags | number (boolean) | 0 | Includes problem tags in the returned JSON | Only relevant when requesting `_format: 'json'` | + +## Using JWTs + +There are three JWT structures that the Renderer uses, each containing its predecessor: +* problemJWT +* sessionJWT +* answerJWT + +### ProblemJWT + +This JWT encapsulates the request parameters described above, under the API heading. Any value set in the JWT cannot be overridden by form-data. For example, if the problemJWT includes `isInstructor: 0`, then any subsequent interaction with the problem rendered by this JWT cannot override this setting by including `isInstructor: 1` in the form-data. + +### SessionJWT + +This JWT encapsulates a user's attempt on a problem, including: +* the text and LaTeX versions of each answer entry +* count of incorrect attempts (stopping after a correct attempt, or after `showCorrectAnswers` is used) +* the problemJWT + +If stored (see next), this JWT can be submitted as the sole request parameter, and the response will effectively restore the users current state of interaction with the problem (as of their last submission). + +### AnswerJWT + +If the initial problemJWT contains a value for `JWTanswerURL`, this JWT will be generated and sent to the specified URL. The answerJWT is the only content provided to the URL. The renderer is intended to to be user-agnostic. It is recommended that the JWTanswerURL specify the unique identifier for the user/problem combination. (e.g. `JWTanswerURL: 'https://db.yoursite.org/grades-api/:user_problem_id'`) + +For security purposes, this parameter is only accepted when included as part of a JWT. + +This JWT encapsulates the status of the user's interaction with the problem. +* score +* sessionJWT + +The goal here is to update the `JWTanswerURL` with the score and "state" for the user. If you have uses for additional information, please feel free to suggest as a GitHub Issue. diff --git a/conf/pg_config.yml b/conf/pg_config.yml new file mode 100644 index 000000000..950ac9570 --- /dev/null +++ b/conf/pg_config.yml @@ -0,0 +1,249 @@ +# Configuration options needed by PG. + +# You should not edit pg_config.dist.yml directly. Copy pg_config.dist.yml file to pg_config.yml and make changes to +# that file. + +# $pg_root may be used in values and will be replaced with the pg root directory set from the PG_ROOT environment +# variable. + +# $render_root may be used in values and will be replaced with the renderer root directory set from the RENDER_ROOT +# environment variable. + +# $OPL_dir may be used in values and will be replaced with the value of the directories OPL setting below. +# $Contrib_dir may be used in values and will be replaced with the value of the directories Contrib setting below. +# $pg_root_url may be used in values and will be replaced with the value of the URLs html setting below. + +directories: + # The OPL and Contrib directories. + OPL: $render_root/Library + Contrib: $render_root/Contrib + + # The root PG location. This will be set from the PG_ROOT environment variable, but can be overriden here. + root: $pg_root + + # Global temporary directory. This location must be writable. + tmp: $render_root/tmp + + # Public html location containing the PG javascript, css, and help files. + html: $pg_root/htdocs + + # Temporary directory from which generated files will be served. This location must be writable. + html_temp: $render_root/tmp + + # Directory from which WeBWorK::PG::IO allows files to be read. + permitted_read_dir: $render_root + + # Location of cached equation images. + equationCache: $render_root/tmp/equations + + # The macro file search path. Each directory in this list is searched + # (in this order) by loadMacros when it looks for a .pl macro file. + macrosPath: + - . + - $render_root/private/macros + - $pg_root/macros + - $pg_root/macros/answers + - $pg_root/macros/capa + - $pg_root/macros/contexts + - $pg_root/macros/core + - $pg_root/macros/graph + - $pg_root/macros/math + - $pg_root/macros/misc + - $pg_root/macros/parsers + - $pg_root/macros/ui + - $pg_root/macros/deprecated + +URLs: + # The public URL of the html directory above. + html: $pg_root_url/pg_files + + # The public URL of the html_temp directory above. + tempURL: $pg_root_url/pg_files/tmp + + # The public URL of the PG help files. + localHelpURL: $pg_root_url/pg_files/helpFiles + + # URL of cached equation images. + equationCache: $pg_root_url/pg_files/tmp/equations + + # Paths to search for auxiliary html files (requires full url). + htmlPath: + - . + - $pg_root_url/pg_files + + # Paths to search for image files (requires full url). + imagesPath: + - . + - $pg_root_url/pg_files/images + +# Flat-file database used to protect against MD5 hash collisions. TeX equations +# are hashed to determine the name of the image file. There is a tiny chance of +# a collision between two TeX strings. This file allows for that. However, this +# is slow, so most people chose not to worry about it. Set this to '' if you +# don't want to use the equation cache file. +equationCacheDB: '' + +externalPrograms: + curl: /usr/bin/curl + cp: /bin/cp + mv: /bin/mv + rm: /bin/rm + tar: /bin/tar + latex: /usr/bin/latex --no-shell-escape + pdflatex: /usr/bin/pdflatex --no-shell-escape + dvisvgm: /usr/bin/dvisvgm + pdf2svg: /usr/bin/pdf2svg + convert: /usr/bin/convert + dvipng: /usr/bin/dvipng + +specialPGEnvironmentVars: + # switch to remove explanation essay block from questions that have one + waiveExplanations: 0 + + # To disable the Parser-based versions of num_cmp and fun_cmp, and use the + # original versions instead, set this value to 1. + useOldAnswerMacros: 0 + + # Determines whether or not MathObjects contexts will parse the alternative tokens + # listed in the "alternatives" property (mostly for unicode alternatives for parse tokens). + parseAlternatives: 0 + + # Determines whether or not the MathObjects parser will convert the Full Width Unicode block + # (U+FF01 to U+FF5E) to their corresponding ASCII characters (U+0021 to U+007E) automatically. + convertFullWidthCharacters: 0 + + # Binary that the PGtikz.pl and PGlateximage.pl macros will use to create svg images. + # This should be either 'pdf2svg' or 'dvisvgm'. + latexImageSVGMethod: pdf2svg + + # 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. + # convert will be called as: + # convert file.ext1 file.ext2 + latexImageConvertOptions: + input: + density: 300 + output: + quality: 100 + + # Strings to insert at the start and end of the body of a problem. + problemPreamble: + TeX: '' + HTML: '' + problemPostamble: + TeX: '' + HTML: '' + + # Math entry assistance + entryAssist: MathQuill + + # Whether to use javascript for rendering Live3D graphs. + use_javascript_for_live3d: 1 + + # Size in pixels of dynamically-generated images, i.e. graphs. + onTheFlyImageSize: 400 + + # Locations of CAPA resources. (Only necessary if you need to use converted CAPA problems.) + CAPA_Tools: $Contrib_dir/CAPA/macros/CAPA_Tools/ + CAPA_MCTools: $Contrib_dir/Contrib/CAPA/macros/CAPA_MCTools/ + CAPA_GraphicsDirectory: $Contrib_dir/Contrib/CAPA/CAPA_Graphics/ + CAPA_Graphics_URL: $pg_root_url/pg_files/CAPA_Graphics/ + +# Answer evaluatior defaults +ansEvalDefaults: + functAbsTolDefault: 0.001 + functLLimitDefault: 0.0000001 + functMaxConstantOfIntegration: 1E8 + functNumOfPoints: 3 + functRelPercentTolDefault: 0.1 + functULimitDefault: 0.9999999 + functVarDefault: x + functZeroLevelDefault: 1E-14 + functZeroLevelTolDefault: 1E-12 + numAbsTolDefault: 0.001 + numFormatDefault: '' + numRelPercentTolDefault: 0.1 + numZeroLevelDefault: 1E-14 + numZeroLevelTolDefault: 1E-12 + useBaseTenLog: 0 + defaultDisplayMatrixStyle: '[s]' # left delimiter, middle line delimiters, right delimiter + +options: + # The default grader to use, if a problem doesn't specify. + grader: avg_problem_grader + + # Note that the first of useMathQuill, useMathView, and useWirisEditor that is set (in that order) to 1 will be used. + + # Set to 1 use MathQuill in answer boxes. + useMathQuill: 1 + + # Set to 1 to use the MathView preview system with answer boxes. + useMathView: 0 + + # This is the operations file to use for mathview, each contains a different locale. + mathViewLocale: mv_locale_us.js + + # Catch translation warnings internally. + catchWarnings: 1 + +# "images" mode has several settings: +displayModeOptions: + images: + # Determines the method used to align images in output. Can be any valid value for the css vertical-align rule such + # as 'baseline' or 'middle'. + dvipng_align: baseline + + # If dbsource is set to a nonempty value, then this database connection information will be used to store depths. + # It is assumed that the 'depths' table exists in the database. + dvipng_depth_db: + dbsource: '' + user: '' + passwd: '' + +# PG modules to load +# The first item of each list is the module to load. The remaining items are additional packages to import. +# That is: If you wish to include a module MyModule.pm which depends on additional modules Dependency1.pm and +# Dependency2.pm, these should appear as [Mymodule, Dependency1, Dependency2] +modules: + - [Encode] + - ['Encode::Encoding'] + - ['HTML::Parser'] + - ['HTML::Entities'] + - [DynaLoader] + - [Exporter] + - [GD] + - [AlgParser, AlgParserWithImplicitExpand, Expr, ExprWithImplicitExpand, utf8] + - [AnswerHash, AnswerEvaluator] + - [LaTeXImage] + - [WWPlot] # required by Circle (and others) + - [Circle] + - ['Class::Accessor'] + - [Complex] + - [Complex1] + - [Distributions] + - [Fraction] + - [Fun] + - [Hermite] + - [Label] + - [ChoiceList] + - [Match] + - [MatrixReal1] # required by Matrix + - [Matrix] + - [Multiple] + - [PGrandom] + - [Regression] + - [Select] + - [Units] + - [VectorField] + - [Parser, Value] + - ['Parser::Legacy'] + - [Statistics] + - [Chromatic] # for Northern Arizona graph problems + - [Applet] + - [PGcore, PGalias, PGresource, PGloadfiles, PGanswergroup, PGresponsegroup, 'Tie::IxHash'] + - ['Locale::Maketext'] + - [JSON] + - [Rserve, 'Class::Tiny', 'IO::Handle'] + - [DragNDrop] + - ['Types::Serialiser'] + - ['WeBWorK::Localize'] diff --git a/docs/make_translation_files.md b/docs/make_translation_files.md index b59723d9a..a31d5ac9f 100644 --- a/docs/make_translation_files.md +++ b/docs/make_translation_files.md @@ -1,16 +1,18 @@ +# How to generate translation files - Go to the location under which the renderer was installed. - You need to have `xgettext.pl` installed. +- This assumes that you are starting in the directory of the renderer clone. -``` -cd container/lib -xgettext.pl -o WeBWorK/lib/WeBWorK/Localize/standalone.pot -D PG/lib -D PG/macros -D RenderApp -D WeBWorK/lib RenderApp.pm +```bash +cd lib +xgettext.pl -o WeBWorK/Localize/standalone.pot -D PG/lib -D PG/macros -D RenderApp -D WeBWorK RenderApp.pm ``` - That creates the POT file of all strings found -``` -cd WeBWorK/lib/WeBWorK/Localize +```bash +cd WeBWorK/Localize find . -name '*.po' -exec bash -c "echo \"Updating {}\"; msgmerge -qUN {} standalone.pot" \; ``` diff --git a/lib/PG b/lib/PG index d242aab31..da6a6dcfe 160000 --- a/lib/PG +++ b/lib/PG @@ -1 +1 @@ -Subproject commit d242aab3175b905a9b428789bf720efe1c75ee3f +Subproject commit da6a6dcfeb87dd833f52ec767fe64ea4cd1c6228 diff --git a/lib/RenderApp.pm b/lib/RenderApp.pm index 28e93b946..565feed56 100644 --- a/lib/RenderApp.pm +++ b/lib/RenderApp.pm @@ -3,41 +3,32 @@ use Mojo::Base 'Mojolicious'; BEGIN { use Mojo::File; - $main::dirname = Mojo::File::curfile->dirname; + $main::libname = Mojo::File::curfile->dirname; # RENDER_ROOT is required for initializing conf files. - $ENV{RENDER_ROOT} = $main::dirname->dirname + $ENV{RENDER_ROOT} = $main::libname->dirname unless ( defined( $ENV{RENDER_ROOT} ) ); - # WEBWORK_ROOT is required for PG/lib/WeBWorK/IO. - # PG_ROOT is required for PG/lib/PGEnvironment.pm. - # These are hardcoded to avoid conflict with environment variables for webwork2. - # There is no need for these to be configurable. - $ENV{WEBWORK_ROOT} = $main::dirname . '/WeBWorK'; - $ENV{PG_ROOT} = $main::dirname . '/PG'; - - # Used for reconstructing library paths from sym-links. - $ENV{OPL_DIRECTORY} = "webwork-open-problem-library"; - $WeBWorK::Constants::WEBWORK_DIRECTORY = $main::dirname . "/WeBWorK"; - $WeBWorK::Constants::PG_DIRECTORY = $main::dirname . "/PG"; - unless ( -r $WeBWorK::Constants::WEBWORK_DIRECTORY ) { - die "Cannot read webwork root directory at $WeBWorK::Constants::WEBWORK_DIRECTORY"; - } - unless ( -r $WeBWorK::Constants::PG_DIRECTORY ) { - die "Cannot read webwork pg directory at $WeBWorK::Constants::PG_DIRECTORY"; - } + # PG_ROOT is required for PG/lib/PGEnvironment.pm, FormatRenderedProblem.pm, and RenderProblem.pm. + # This is hardcoded to avoid conflict with the environment variable for webwork2. + # There is no need for this to be configurable. + $ENV{PG_ROOT} = $main::libname . '/PG'; + + # Used for reconstructing library paths from sym-links. + $ENV{OPL_DIRECTORY} = "$ENV{RENDER_ROOT}/webwork-open-problem-library"; $ENV{MOJO_CONFIG} = (-r "$ENV{RENDER_ROOT}/render_app.conf") ? "$ENV{RENDER_ROOT}/render_app.conf" : "$ENV{RENDER_ROOT}/render_app.conf.dist"; # $ENV{MOJO_MODE} = 'production'; # $ENV{MOJO_LOG_LEVEL} = 'debug'; } -use lib "$main::dirname"; -print "home directory " . $main::dirname . "\n"; +use lib "$main::libname"; +print "using root directory: $ENV{RENDER_ROOT}\n"; use RenderApp::Model::Problem; -use RenderApp::Controller::RenderProblem; use RenderApp::Controller::IO; +use WeBWorK::RenderProblem; +use WeBWorK::FormatRenderedProblem; sub startup { my $self = shift; @@ -50,16 +41,12 @@ sub startup { $ENV{$_} //= $self->config($_); }; - $ENV{baseURL} = '' if ( $ENV{baseURL} eq '/' ); - $ENV{SITE_HOST} =~ s|/$||; # remove trailing slash - - # $r needs to be defined before the SITE_HOST is added to the baseURL + sanitizeHostURLs(); + # baseURL sets the root at which the renderer is listening, and is used in Environment for pg_root_url my $r = $self->routes->under($ENV{baseURL}); - # while iFrame embedded problems are likely to need the baseURL to include SITE_HOST - # convert to absolute URLs - $ENV{baseURL} = $ENV{SITE_HOST} . $ENV{baseURL} unless ( $ENV{baseURL} =~ m|^https?://| ); - $ENV{formURL} = $ENV{baseURL} . $ENV{formURL} unless ( $ENV{formURL} =~ m|^https?://| ); + print "Renderer is based at $main::basehref\n"; + print "Problem attempts will be sent to $main::formURL\n"; # Handle optional CORS settings if (my $CORS_ORIGIN = $self->config('CORS_ORIGIN')) { @@ -76,6 +63,7 @@ sub startup { $self->helper(newProblem => sub { shift; RenderApp::Model::Problem->new(@_) }); # Helpers + $self->helper(format => sub { WeBWorK::FormatRenderedProblem::formatRenderedProblem(@_) }); $self->helper(validateRequest => sub { RenderApp::Controller::IO::validate(@_) }); $self->helper(parseRequest => sub { RenderApp::Controller::Render::parseRequest(@_) }); $self->helper(croak => sub { RenderApp::Controller::Render::croak(@_) }); @@ -106,29 +94,41 @@ sub startup { $r->any('/render-api/cat')->to('IO#catalog'); $r->any('/render-api/find')->to('IO#search'); $r->post('/render-api/upload')->to('IO#upload'); + $r->delete('/render-api/remove')->to('IO#remove'); + $r->post('/render-api/clone')->to('IO#clone'); $r->post('/render-api/sma')->to('IO#findNewVersion'); $r->post('/render-api/unique')->to('IO#findUniqueSeeds'); $r->post('/render-api/tags')->to('IO#setTags'); } # Static file routes - $r->any('/webwork2_files/CAPA_Graphics/*static')->to('StaticFiles#CAPA_graphics_file'); - $r->any('/webwork2_files/tmp/*static')->to('StaticFiles#temp_file'); + $r->any('/pg_files/CAPA_Graphics/*static')->to('StaticFiles#CAPA_graphics_file'); + $r->any('/pg_files/tmp/*static')->to('StaticFiles#temp_file'); $r->any('/pg_files/*static')->to('StaticFiles#pg_file'); + $r->any('/*static')->to('StaticFiles#public_file'); +} - # any other requests fall through - $r->any('/*fail' => sub { - my $c = shift; - my $report = $c->stash('fail')."\nCOOKIE:"; - for my $cookie (@{$c->req->cookies}) { - $report .= "\n".$cookie->to_string; - } - $report .= "\nFORM DATA:"; - foreach my $k (@{$c->req->params->names}) { - $report .= "\n$k = ".join ', ', @{$c->req->params->every_param($k)}; - } - $c->log->fatal($report); - $c->rendered(404)}); +sub sanitizeHostURLs { + $ENV{baseURL} = "/$ENV{baseURL}"; + warn "*** Configuration error: baseURL should not end in a slash\n" if $ENV{baseURL} =~ s!/$!!; + warn "*** Configuration error: baseURL should begin with a slash\n" unless $ENV{baseURL} =~ s!^//!/!; + + # set an absolute base href for iframe embedding + my $basehref = $ENV{baseURL} =~ m!/$! ? $ENV{baseURL} : "$ENV{baseURL}/"; + my $baseURL = Mojo::URL->new($basehref); + $main::basehref = $baseURL->is_abs + ? $baseURL + : Mojo::URL->new($ENV{SITE_HOST})->path($baseURL); + + # respect absolute form URLs for man-in-the-middle implementations + warn "*** Configuration error: formURL should not begin with a slash\n" if $ENV{formURL} =~ s!^/!!; + my $renderEndpoint = $ENV{formURL} || 'render-api'; + my $formURL = Mojo::URL->new($renderEndpoint); + warn "*** Possible configuration error: are you sure you want to use $main::basehref$renderEndpoint as the render endpoint?\n" + unless $formURL->is_abs || $renderEndpoint eq 'render-api'; + $main::formURL = $formURL->is_abs + ? $formURL + : Mojo::URL->new($ENV{SITE_HOST})->path($basehref.$renderEndpoint); } 1; diff --git a/lib/RenderApp/Controller/FormatRenderedProblem.pm b/lib/RenderApp/Controller/FormatRenderedProblem.pm deleted file mode 100755 index c9b8ff566..000000000 --- a/lib/RenderApp/Controller/FormatRenderedProblem.pm +++ /dev/null @@ -1,364 +0,0 @@ -#!/usr/bin/perl -w - -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WebworkClient.pm,v 1.1 2010/06/08 11:46:38 gage Exp $ -# -# 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 - -FormatRenderedProblem.pm - -=cut - -package RenderApp::Controller::FormatRenderedProblem; - -use warnings; -use strict; - -use lib "$WeBWorK::Constants::WEBWORK_DIRECTORY/lib"; -use lib "$WeBWorK::Constants::PG_DIRECTORY/lib"; -use MIME::Base64 qw( encode_base64 decode_base64); -use WeBWorK::Utils::AttemptsTable; #import from ww2 -use WeBWorK::PG::ImageGenerator; # import from ww2 -use WeBWorK::Utils::LanguageAndDirection; -use WeBWorK::Utils qw(wwRound getAssetURL); # required for score summary -use WeBWorK::Localize ; # for maketext -our $UNIT_TESTS_ON = 0; - -##################### -# error formatting - -sub format_hash_ref { - my $hash = shift; - warn "Use a hash reference" unless ref($hash) =~/HASH/; - return join(" ", map {$_="--" unless defined($_);$_ } %$hash),"\n"; -} - -sub new { - my $invocant = shift; - my $class = ref $invocant || $invocant; - my $self = { # Is this function redundant given the declarations within sub formatRenderedProblem? - return_object => {}, - encoded_source => {}, - sourceFilePath => '', - baseURL => $ENV{baseURL}, - form_action_url =>$ENV{formURL}, - maketext => sub {return @_}, - courseID => 'foo', # optional? - userID => 'bar', # optional? - course_password => 'baz', - inputs_ref => {}, - @_, - }; - bless $self, $class; -} - -sub return_object { # out - my $self = shift; - my $object = shift; - $self->{return_object} = $object if defined $object and ref($object); # source is non-empty - $self->{return_object}; -} - -sub encoded_source { - my $self = shift; - my $source = shift; - $self->{encoded_source} =$source if defined $source and $source =~/\S/; # source is non-empty - $self->{encoded_source}; -} - -sub url { - my $self = shift; - my $new_url = shift; - $self->{url} = $new_url if defined($new_url) and $new_url =~ /\S/; - $self->{url}; -} - -sub formatRenderedProblem { - my $self = shift; - my $problemText =''; - my $rh_result = $self->return_object() || {}; # wrap problem in formats - $problemText = "No output from rendered Problem" unless $rh_result; - print "\nformatRenderedProblem return_object $rh_result = ",join(" ", sort keys %$rh_result),"\n" if $UNIT_TESTS_ON; - if (ref($rh_result) and $rh_result->{text} ) { ##text vs body_text - $problemText = $rh_result->{text}; - $problemText .= $rh_result->{flags}{comment} if ( $rh_result->{flags}{comment} && $self->{inputs_ref}{showComments} ); - } else { - $problemText .= "Unable to decode problem text
\n". - $self->{error_string}."\n".format_hash_ref($rh_result); - } - my $problemHeadText = $rh_result->{header_text}//''; ##head_text vs header_text - my $problemPostHeaderText = $rh_result->{post_header_text}//''; - my $rh_answers = $rh_result->{answers}//{}; - my $answerOrder = $rh_result->{flags}->{ANSWER_ENTRY_ORDER}; #[sort keys %{ $rh_result->{answers} }]; - my $encoded_source = $self->encoded_source//''; - my $sourceFilePath = $self->{sourceFilePath}//''; - my $problemSourceURL = $self->{inputs_ref}->{problemSourceURL}; - my $warnings = ''; - print "\n return_object answers ", - join( " ", %{ $rh_result->{PG_ANSWERS_HASH} } ) - if $UNIT_TESTS_ON; - - - ################################################# - # regular Perl warning messages generated with warn - ################################################# - - if ( defined ($rh_result->{WARNINGS}) and $rh_result->{WARNINGS} ){ - $warnings = "
-

WARNINGS

" - . Encode::decode("UTF-8", decode_base64($rh_result->{WARNINGS}) ) - . "

"; - } - #warn "keys: ", join(" | ", sort keys %{$rh_result }); - - ################################################# - # PG debug messages generated with DEBUG_message(); - ################################################# - - my $debug_messages = $rh_result->{debug_messages} || []; - $debug_messages = join("
\n", @{ $debug_messages }); - - ################################################# - # PG warning messages generated with WARN_message(); - ################################################# - - my $PG_warning_messages = $rh_result->{warning_messages} || []; - $PG_warning_messages = join("
\n", @{ $PG_warning_messages } ); - - ################################################# - # internal debug messages generated within PG_core - # these are sometimes needed if the PG_core warning message system - # isn't properly set up before the bug occurs. - # In general don't use these unless necessary. - ################################################# - - my $internal_debug_messages = $rh_result->{internal_debug_messages} || []; - $internal_debug_messages = join("
\n", @{ $internal_debug_messages } ); - - my $fileName = $self->{input}->{envir}->{fileName} || ""; - - ################################################# - - my $XML_URL = $self->url // ''; - my $FORM_ACTION_URL = $self->{form_action_url} // ''; - my $SITE_URL = $self->{baseURL} // ''; - my $SITE_HOST = $ENV{SITE_HOST} // ''; - my $courseID = $self->{courseID} // ''; - my $userID = $self->{userID} // ''; - my $course_password = $self->{course_password} // ''; - my $session_key = $rh_result->{session_key} // ''; - my $displayMode = $self->{inputs_ref}{displayMode} // 'MathJax'; - my $problemJWT = $self->{inputs_ref}{problemJWT} // ''; - my $sessionJWT = $self->{return_object}{sessionJWT} // ''; - my $webwork_htdocs_url = $self->{ce}{webworkURLs}{htdocs}; - - - my $previewMode = defined( $self->{inputs_ref}{previewAnswers} ) || 0; - my $checkMode = defined( $self->{inputs_ref}{checkAnswers} ) || 0; - my $submitMode = defined( $self->{inputs_ref}{submitAnswers} ) || 0; - my $showCorrectMode = defined( $self->{inputs_ref}{showCorrectAnswers} ) || 0; - - # use Data::Dumper; - # print Dumper($self->{inputs_ref}); - - # problemIdentifierPrefix can be added to the request as a parameter. - # It adds a prefix to the - # identifier used by the format so that several different problems - # can appear on the same page. - my $problemIdentifierPrefix = - $self->{inputs_ref}->{problemIdentifierPrefix} // ''; - my $problemResult = $rh_result->{problem_result} // ''; - my $problemState = $rh_result->{problem_state} // ''; - my $showPartialCorrectAnswers = $self->{inputs_ref}{showPartialCorrectAnswers} - // $rh_result->{flags}{showPartialCorrectAnswers}; - my $showSummary = $self->{inputs_ref}{showSummary} // 1; #default to show summary for the moment - my $formLanguage = $self->{inputs_ref}{language} // 'en'; - my $scoreSummary = ''; - - my $COURSE_LANG_AND_DIR = get_lang_and_dir($formLanguage); - # Set up the problem language and direction - # PG files can request their language and text direction be set. If we do - # not have access to a default course language, fall back to the - # $formLanguage instead. - my %PROBLEM_LANG_AND_DIR = get_problem_lang_and_dir($rh_result->{flags}, "auto:en:ltr", $formLanguage); - my $PROBLEM_LANG_AND_DIR = join(" ", map { qq{$_="$PROBLEM_LANG_AND_DIR{$_}"} } keys %PROBLEM_LANG_AND_DIR); - my $mt = WeBWorK::Localize::getLangHandle($self->{inputs_ref}{language} // 'en'); - - my $tbl = WeBWorK::Utils::AttemptsTable->new( - $rh_answers, - answersSubmitted => $self->{inputs_ref}{answersSubmitted}//0, - answerOrder => $answerOrder//[], - displayMode => $self->{inputs_ref}{displayMode}, - imgGen => undef, # $imgGen, - ce => '', #used only to build the imgGen - showAnswerNumbers => 0, - showAttemptAnswers => 0, - showAttemptPreviews => ($previewMode or $submitMode or $showCorrectMode), - showAttemptResults => ($submitMode and $showPartialCorrectAnswers), - showCorrectAnswers => ($showCorrectMode), - showMessages => ($previewMode or $submitMode or $showCorrectMode), - showSummary => ( ($showSummary and ($submitMode or $showCorrectMode) )//0 )?1:0, - maketext => WeBWorK::Localize::getLoc($formLanguage//'en'), - summary => $problemResult->{summary} //'', # can be set by problem grader??? - ); - - my $answerTemplate = $tbl->answerTemplate; - $tbl->imgGen->render(refresh => 1) if $tbl->displayMode eq 'images'; - - # warn "imgGen is ", $tbl->imgGen; - #warn "answerOrder ", $tbl->answerOrder; - #warn "answersSubmitted ", $tbl->answersSubmitted; - # render equation images - - if ($submitMode && $problemResult && $showSummary) { - $scoreSummary = CGI::p($mt->maketext('Your score on this attempt is [_1]', wwRound(0, $problemResult->{score} * 100).'%')); - - #$scoreSummary .= CGI::p($mt->maketext("Your score was not recorded.")); - - #scoreSummary .= CGI::p('Your score on this problem has not been recorded.'); - #$scoreSummary .= CGI::hidden({id=>'problem-result-score', name=>'problem-result-score',value=>$problemResult->{score}}); - } - - # this should never? be blocked -- contains relevant info for - if ($problemResult->{msg}) { - $scoreSummary .= CGI::p($problemResult->{msg}); - } - - # This stuff is put here because eventually we will add locale support so the - # text will have to be done server side. - my $localStorageMessages = CGI::start_div({id=>'local-storage-messages'}); - $localStorageMessages.= CGI::p('Your overall score for this problem is'.' '.CGI::span({id=>'problem-overall-score'},'')); - $localStorageMessages .= CGI::end_div(); - - # Add JS files requested by problems via ADD_JS_FILE() in the PG file. - my $extra_js_files = ''; - if (ref($rh_result->{flags}{extra_js_files}) eq 'ARRAY') { - $rh_result->{js} = []; - my %jsFiles; - for (@{ $rh_result->{flags}{extra_js_files} }) { - next if $jsFiles{ $_->{file} }; - $jsFiles{ $_->{file} } = 1; - my %attributes = ref($_->{attributes}) eq 'HASH' ? %{ $_->{attributes} } : (); - if ($_->{external}) { - push @{ $rh_result->{js} }, $_->{file}; - $extra_js_files .= CGI::script({ src => $_->{file}, %attributes }, ''); - } else { - my $url = getAssetURL($self->{inputs_ref}{language} // 'en', $_->{file}); - push @{ $rh_result->{js} }, $SITE_URL.$url; - $extra_js_files .= CGI::script({ src => $url, %attributes }, ''); - } - } - } - - # Add CSS files requested by problems via ADD_CSS_FILE() in the PG file - # or via a setting of $self->{ce}{pg}{specialPGEnvironmentVars}{extra_css_files} - # (the value should be an anonomous array). - my $extra_css_files = ''; - my @cssFiles; - if (ref($self->{ce}{pg}{specialPGEnvironmentVars}{extra_css_files}) eq 'ARRAY') { - push(@cssFiles, { file => $_, external => 0 }) for @{ $self->{ce}{pg}{specialPGEnvironmentVars}{extra_css_files} }; - } - if (ref($rh_result->{flags}{extra_css_files}) eq 'ARRAY') { - push @cssFiles, @{ $rh_result->{flags}{extra_css_files} }; - } - my %cssFilesAdded; # Used to avoid duplicates - $rh_result->{css} = []; - for (@cssFiles) { - next if $cssFilesAdded{ $_->{file} }; - $cssFilesAdded{ $_->{file} } = 1; - if ($_->{external}) { - push @{ $rh_result->{css} }, $_->{file}; - $extra_css_files .= CGI::Link({ rel => 'stylesheet', href => $_->{file} }); - } else { - my $url = getAssetURL($self->{inputs_ref}{language} // 'en', $_->{file}); - push @{ $rh_result->{css} }, $SITE_URL.$url; - $extra_css_files .= CGI::Link({ href => $url, rel => 'stylesheet' }); - } - } - - my $STRING_Preview = $mt->maketext("Preview My Answers"); - my $STRING_ShowCorrect = $mt->maketext("Show Correct Answers"); - my $STRING_Submit = $mt->maketext("Submit Answers"); - - #my $pretty_print_self = pretty_print($self); - - ###################################################### - # Return interpolated problem template - ###################################################### - my $format_name = $self->{inputs_ref}->{outputFormat}; - - if ($format_name eq "ww3") { - my $json_output = do("WebworkClient/ww3_format.pl"); - for my $key (keys %$json_output) { - # Interpolate values - $json_output->{$key} =~ s/(\$\w+)/"defined $1 ? $1 : ''"/gee; - } - $json_output->{submitButtons} = []; - push(@{$json_output->{submitButtons}}, { name => 'previewAnswers', value => $STRING_Preview }) - if $self->{inputs_ref}{showPreviewButton}; - push(@{$json_output->{submitButtons}}, { name => 'submitAnswers', value => $STRING_Submit }) - if $self->{inputs_ref}{showCheckAnswersButton}; - push(@{$json_output->{submitButtons}}, { name => 'showCorrectAnswers', value => $STRING_ShowCorrect }) - if $self->{inputs_ref}{showCorrectAnswersButton}; - return $json_output; - } - - $format_name //= 'formatRenderedProblemFailure'; - # find the appropriate template in WebworkClient folder - my $template = do("WebworkClient/${format_name}_format.pl")//''; - die "Unknown format name $format_name" unless $template; - # interpolate values into template - $template =~ s/(\$\w+)/"defined $1 ? $1 : ''"/gee; - return $template; -} - -sub pretty_print { # provides html output -- NOT a method - my $r_input = shift; - my $level = shift; - $level = 4 unless defined($level); - $level--; - return '' unless $level > 0; # only print three levels of hashes (safety feature) - my $out = ''; - if ( not ref($r_input) ) { - $out = $r_input if defined $r_input; # not a reference - $out =~ s/"; - - foreach my $key ( sort ( keys %$r_input )) { - $out .= " $key=> ".pretty_print($r_input->{$key}) . ""; - } - $out .=""; - } elsif (ref($r_input) eq 'ARRAY' ) { - my @array = @$r_input; - $out .= "( " ; - while (@array) { - $out .= pretty_print(shift @array, $level) . " , "; - } - $out .= " )"; - } elsif (ref($r_input) eq 'CODE') { - $out = "$r_input"; - } else { - $out = $r_input; - $out =~ s/render( text => 'File successfully uploaded', status => 200 ); } +sub remove { + my $c = shift; + my $required = []; + push @$required, + { + field => 'removeFilePath', + checkType => 'like', + check => $regex->{privateOnly}, + }; + my $validatedInput = $c->validateRequest( { required => $required } ); + return unless $validatedInput; + + my $file_path = $validatedInput->{removeFilePath}; + my $file = Mojo::File->new($file_path); + + return $c->render( text => 'Path does not exist', status => 404 ) + unless (-e $file); + + if (-d $file) { + return $c->render( text => 'Directory is not empty', status => 400 ) + unless ($file->list({ dir => 1 })->size == 0); + + $file->remove_tree; + } else { + $file->remove; + } + + return $c->render( text => 'Path deleted' ); +} + +sub clone { + my $c = shift; + my $required = []; + push @$required, + { + field => 'sourceFilePath', + checkType => 'like', + check => $regex->{privateOnly}, + }; + push @$required, + { + field => 'targetFilePath', + checkType => 'like', + check => $regex->{privateOnly}, + }; + my $validatedInput = $c->validateRequest( { required => $required } ); + return unless $validatedInput; + + my $source_path = $validatedInput->{sourceFilePath}; + my $source_file = Mojo::File->new($source_path); + my $target_path = $validatedInput->{targetFilePath}; + my $target_file = Mojo::File->new($target_path); + + return $c->render( text => 'source does not exist', status => 404 ) + unless (-e $source_file); + + return $c->render( text => 'target already exists', status => 400 ) + if (-e $target_file); + + # allow cloning of directories - problems with static assets + # no recursing through directories! + if (-d $source_file) { + return $c->render( text => 'source does not contain clone-able files', status => 400) + if ($source_file->list->size == 0); + + return $c->render( text => 'target must also be a directory', status => 400) + unless ($target_path =~ m!.*/$!); + + $target_file->make_path; + for ($source_file->list->each) { + $_->copy_to($target_path . $_->basename); + } + } else { + return $c->render( text => 'you may not create new directories with this method', status => 400) + unless (-e $target_file->dirname); + + return($c->render( text => 'file extensions do not match')) + unless ($source_file->extname eq $target_file->extname); + + $source_file->copy_to($target_file); + } + + return $c->render( text => 'clone successful' ); +} + async sub catalog { my $c = shift; my $required = []; @@ -169,13 +254,12 @@ sub depthSearch_p { my $wanted = sub { # measure depth relative to root_path ( my $rel = $File::Find::name ) =~ s!^\Q$root_path\E/?!!; + return unless $rel; my $path = $File::Find::name; $File::Find::prune = 1 if File::Spec::Functions::splitdir($rel) >= $depth; $path = $path . '/' if -d $File::Find::name; - # only report .pg files and directories - $all{$rel} = $path - if ( $rel =~ /\S/ && ( $path =~ m!.+/$! || $path =~ m!.+\.pg$! ) ); + $all{$rel} = $path; }; File::Find::find { wanted => $wanted, no_chdir => 1 }, $root_path; return \%all, 200; diff --git a/lib/RenderApp/Controller/Render.pm b/lib/RenderApp/Controller/Render.pm index 62de9e173..5686e55c6 100644 --- a/lib/RenderApp/Controller/Render.pm +++ b/lib/RenderApp/Controller/Render.pm @@ -37,7 +37,6 @@ sub parseRequest { foreach my $key (keys %$claims) { $params{$key} //= $claims->{$key}; } - # @params{ keys %$claims } = values %$claims; } # problemJWT sets basic problem request configuration and rendering options @@ -57,9 +56,19 @@ sub parseRequest { return undef; }; $claims = $claims->{webwork} if defined $claims->{webwork}; - # $claims->{problemJWT} = $problemJWT; # because we're merging claims, this is unnecessary? # override key-values in params with those provided in the JWT @params{ keys %$claims } = values %$claims; + } else { + # if no JWT is provided, create one + $params{aud} = $ENV{SITE_HOST}; + my $req_jwt = encode_jwt( + payload => \%params, + key => $ENV{problemJWTsecret}, + alg => 'PBES2-HS512+A256KW', + enc => 'A256GCM', + auto_iat => 1 + ); + $params{problemJWT} = $req_jwt; } return \%params; } @@ -81,7 +90,7 @@ sub fetchRemoteSource_p { then( sub { my $tx = shift; - return encode_base64($tx->result->body); + return $tx->result->body; })-> catch( sub { @@ -97,12 +106,11 @@ async sub problem { my $c = shift; my $inputs_ref = $c->parseRequest; return unless $inputs_ref; + $inputs_ref->{problemSource} = fetchRemoteSource_p($c, $inputs_ref->{problemSourceURL}) if $inputs_ref->{problemSourceURL}; my $file_path = $inputs_ref->{sourceFilePath}; my $random_seed = $inputs_ref->{problemSeed}; - $inputs_ref->{baseURL} ||= $ENV{baseURL}; - $inputs_ref->{formURL} ||= $ENV{formURL}; my $problem_contents; if ( $inputs_ref->{problemSource} && $inputs_ref->{problemSource} =~ /Mojo::Promise/ ) { @@ -120,69 +128,67 @@ async sub problem { return $c->exception($problem->{_message}, $problem->{status}) unless $problem->success(); - $inputs_ref->{sourceFilePath} = $problem->{read_path}; # in case the path was updated... - - my $input_errs = checkInputs($inputs_ref); - $c->render_later; # tell Mojo that this might take a while my $ww_return_json = await $problem->render($inputs_ref); return $c->exception( $problem->{_message}, $problem->{status} ) unless $problem->success(); - my $ww_return_hash = decode_json($ww_return_json); - my $output_errs = checkOutputs($ww_return_hash); - - $ww_return_hash->{debug}->{render_warn} = [$input_errs, $output_errs]; - - # if answers are submitted and there is a provided answerURL... - if ($inputs_ref->{JWTanswerURL} && $ww_return_hash->{JWT}{answer} && $inputs_ref->{submitAnswers}) { - my $answerJWTresponse = { - iss => $ENV{SITE_HOST}, - subject => 'webwork.result', - status => 502, - message => 'initial message' - }; - my $reqBody = { - Origin => $ENV{SITE_HOST}, - 'Content-Type' => 'text/plain', - }; - - $c->log->info("sending answerJWT to $inputs_ref->{JWTanswerURL}"); - await $c->ua->max_redirects(5)->request_timeout(7)->post_p($inputs_ref->{JWTanswerURL}, $reqBody, $ww_return_hash->{JWT}{answer})-> - then(sub { - my $response = shift->result; - - $answerJWTresponse->{status} = int($response->code); - if ($response->is_success) { - $answerJWTresponse->{message} = $response->body; - } - elsif ($response->is_error) { - $answerJWTresponse->{message} = '[' . $c->logID . '] ' . $response->message; - } + my $return_object = decode_json($ww_return_json); - $answerJWTresponse->{message} =~ s/"/\\"/g; - $answerJWTresponse->{message} =~ s/'/\'/g; - })-> - catch(sub { - my $response = shift; - $c->log->error($response); + # if answerURL provided and this is a submit, then send the answerJWT + if ($inputs_ref->{JWTanswerURL} && $inputs_ref->{submitAnswers} && !$inputs_ref->{showCorrectAnswers}) { + $return_object->{JWTanswerURLstatus} = await sendAnswerJWT($c, $inputs_ref->{JWTanswerURL}, $return_object->{answerJWT}); + } - $answerJWTresponse->{status} = 500; - $answerJWTresponse->{message} = '[' . $c->logID . '] ' . $response; - }); - $answerJWTresponse = encode_json($answerJWTresponse); - $c->log->info("answerJWT response ".$answerJWTresponse); + # format the response + $c->format($return_object); +} - $ww_return_hash->{renderedHTML} =~ s/JWTanswerURLstatus/$answerJWTresponse/g; - } else { - $ww_return_hash->{renderedHTML} =~ s/JWTanswerURLstatus//; - } +async sub sendAnswerJWT { + my $c = shift; + my $JWTanswerURL = shift; + my $answerJWT = shift; + + my $answerJWTresponse = { + iss => $ENV{SITE_HOST}, + subject => 'webwork.result', + status => 502, + message => 'initial message' + }; + my $reqBody = { + Origin => $ENV{SITE_HOST}, + 'Content-Type' => 'text/plain', + }; - $c->respond_to( - html => { text => $ww_return_hash->{renderedHTML} }, - json => { json => $ww_return_hash } - ); + $c->log->info("sending answerJWT to $JWTanswerURL"); + await $c->ua->max_redirects(5)->request_timeout(7)->post_p($JWTanswerURL, $reqBody, $answerJWT)-> + then(sub { + my $response = shift->result; + + $answerJWTresponse->{status} = int($response->code); + # answerURL responses are expected to be JSON + if ($response->json) { + # munge data with default response object + $answerJWTresponse = { %$answerJWTresponse, %{$response->json} }; + } else { + # otherwise throw the whole body as the message + $answerJWTresponse->{message} = $response->body; + } + })-> + catch(sub { + my $err = shift; + $c->log->error($err); + + $answerJWTresponse->{status} = 500; + $answerJWTresponse->{message} = '[' . $c->logID . '] ' . $err; + }); + + $answerJWTresponse = encode_json($answerJWTresponse); + # this will become a string literal, so single-quote characters must be escaped + $answerJWTresponse =~ s/'/\\'/g; + $c->log->info("answerJWT response ".$answerJWTresponse); + return $answerJWTresponse; } sub checkInputs { @@ -203,11 +209,12 @@ sub checkInputs { push @errs, $err; } } - return "Form data submitted for " + return @errs ? "Form data submitted for " . $inputs_ref->{sourceFilePath} . " contained errors: {" . join "}, {", @errs - . "}"; + . "}" + : undef; } sub checkOutputs { @@ -235,11 +242,12 @@ sub checkOutputs { } } } - return + return @errs ? "Output from rendering " - . ($outputs_ref->{sourceFilePath} // '') - . " contained errors: {" - . join "}, {", @errs . "}"; + . ($outputs_ref->{sourceFilePath} // '') + . " contained errors: {" + . join "}, {", @errs . "}" + : undef; } sub exception { @@ -278,7 +286,6 @@ sub jweFromRequest { my $inputs_ref = $c->parseRequest; return unless $inputs_ref; $inputs_ref->{aud} = $ENV{SITE_HOST}; - $inputs_ref->{key} = $ENV{problemJWTsecret}; my $req_jwt = encode_jwt( payload => $inputs_ref, key => $ENV{problemJWTsecret}, @@ -294,7 +301,6 @@ sub jwtFromRequest { my $inputs_ref = $c->parseRequest; return unless $inputs_ref; $inputs_ref->{aud} = $ENV{SITE_HOST}; - $inputs_ref->{key} = $ENV{problemJWTsecret}; my $req_jwt = encode_jwt( payload => $inputs_ref, key => $ENV{problemJWTsecret}, diff --git a/lib/RenderApp/Controller/RenderProblem.pm b/lib/RenderApp/Controller/RenderProblem.pm deleted file mode 100644 index 8c165ea8e..000000000 --- a/lib/RenderApp/Controller/RenderProblem.pm +++ /dev/null @@ -1,685 +0,0 @@ -#/usr/bin/perl -w - -use strict; -use warnings; - -package RenderApp::Controller::RenderProblem; - -use Time::HiRes qw/time/; -use Date::Format; -use MIME::Base64 qw(encode_base64 decode_base64); -use File::Find; -use FileHandle; -use File::Path; -use File::Basename; - -#use File::Temp qw/tempdir/; -use String::ShellQuote; -use Cwd 'abs_path'; -use JSON::XS; -use Crypt::JWT qw( encode_jwt ); -use Digest::MD5 qw( md5_hex ); - -use lib "$WeBWorK::Constants::WEBWORK_DIRECTORY/lib"; -use lib "$WeBWorK::Constants::PG_DIRECTORY/lib"; - -use Proc::ProcessTable; # use for log memory use -use WeBWorK::PG; #webwork2 (use to set up environment) -use WeBWorK::CourseEnvironment; -use WeBWorK::Utils::Tags; -use RenderApp::Controller::FormatRenderedProblem; - -use 5.10.0; -$Carp::Verbose = 1; - -### verbose output when UNIT_TESTS_ON =1; -our $UNIT_TESTS_ON = 0; - -#our @path_list; - -################################################## -# create log files :: expendable -################################################## - -my $path_to_log_file = 'logs/standalone_results.log'; - -eval { # attempt to create log file - local (*FH); - open( FH, '>>:encoding(UTF-8)', $path_to_log_file ) - or die "Can't open file $path_to_log_file for writing"; - close(FH); -}; - -die -"You must first create an output file at $path_to_log_file with permissions 777 " - unless -w $path_to_log_file; - -################################################## -# define universal TO_JSON for JSON::XS unbless -################################################## - -sub UNIVERSAL::TO_JSON { - my ($self) = shift; - - use Storable qw(dclone); - use Data::Structure::Util qw(unbless); - - my $clone = unbless( dclone($self) ); - - $clone; -} - -########################################################## -# END MAIN :: BEGIN SUBROUTINES -########################################################## - -####################################################################### -# Process the pg file -####################################################################### - -sub process_pg_file { - my $problem = shift; - my $inputHash = shift; - - our $seed_ce = create_course_environment(); - - my $file_path = $problem->path; - my $problem_seed = $problem->seed || '666'; - - # just make sure we have the fundamentals covered... - $inputHash->{displayMode} = - 'MathJax'; # is there any reason for this to be anything else? - $inputHash->{sourceFilePath} ||= $file_path; - $inputHash->{outputFormat} ||= 'static'; - $inputHash->{language} ||= 'en'; - - # HACK: required for problemRandomize.pl - $inputHash->{effectiveUser} = 'red.ted'; - $inputHash->{user} = 'red.ted'; - - # OTHER fundamentals - urls have been handled already... - # form_action_url => $inputHash->{form_action_url}||'http://failure.org', - # base_url => $inputHash->{base_url}||'http://failure.org' - # #psvn => $psvn//'23456', # DEPRECATED - # #forcePortNumber => $credentials{forcePortNumber}//'', - - my $pg_start = - time; # this is Time::HiRes's time, which gives floating point values - - my ( $error_flag, $formatter, $error_string ) = - process_problem( $seed_ce, $file_path, $inputHash ); - - my $pg_stop = time; - my $pg_duration = $pg_stop - $pg_start; - - # format result - my $html = $formatter->formatRenderedProblem; - my $pg_obj = $formatter->{return_object}; - my $json_rh = { - renderedHTML => $html, - answers => $pg_obj->{answers}, - debug => { - perl_warn => Encode::decode("UTF-8", decode_base64( $pg_obj->{WARNINGS} ) ), - pg_warn => $pg_obj->{warning_messages}, - debug => $pg_obj->{debug_messages}, - internal => $pg_obj->{internal_debug_messages} - }, - problem_result => $pg_obj->{problem_result}, - problem_state => $pg_obj->{problem_state}, - flags => $pg_obj->{flags}, - resources => { - regex => $pg_obj->{resources}, - tags => $pg_obj->{pgResources}, - js => $pg_obj->{js}, - css => $pg_obj->{css}, - }, - form_data => $inputHash, - raw_metadata_text => $pg_obj->{raw_metadata_text}, - JWT => { - problem => $inputHash->{problemJWT}, - session => $pg_obj->{sessionJWT}, - answer => $pg_obj->{answerJWT} - }, - }; - - # havoc caused by problemRandomize.pl inserting CODE ref into pg->{flags} - # HACK: remove flags->{problemRandomize} if it exists -- cannot include CODE refs - delete $json_rh->{flags}{problemRandomize} - if $json_rh->{flags}{problemRandomize}; - # similar things happen with compoundProblem -- delete CODE refs - delete $json_rh->{flags}{compoundProblem}{grader} - if $json_rh->{flags}{compoundProblem}{grader}; - - - $json_rh->{tags} = WeBWorK::Utils::Tags->new($file_path, $inputHash->{problemSource}) if ( $inputHash->{includeTags} ); - my $coder = JSON::XS->new->ascii->pretty->allow_unknown->convert_blessed; - my $json = $coder->encode($json_rh); - return $json; -} - -####################################################################### -# Process Problem -####################################################################### - -sub process_problem { - my $ce = shift; - my $file_path = shift; - my $inputs_ref = shift; - my $adj_file_path; - my $source; - - # obsolete if using JSON return format - # These can FORCE display of AnsGroup AnsHash PGInfo and ResourceInfo - # $inputs_ref->{showAnsGroupInfo} = 1; #$print_answer_group; - # $inputs_ref->{showAnsHashInfo} = 1; #$print_answer_hash; - # $inputs_ref->{showPGInfo} = 1; #$print_pg_hash; - # $inputs_ref->{showResourceInfo} = 1; #$print_resource_hash; - - ### stash inputs that get wiped by PG - my $problem_seed = $inputs_ref->{problemSeed}; - die "problem seed not defined in Controller::RenderProblem::process_problem" - unless $problem_seed; - my $display_mode = $inputs_ref->{displayMode}; - - # if base64 source is provided, use that over fetching problem path - if ( $inputs_ref->{problemSource} && $inputs_ref->{problemSource} =~ m/\S/ ) - { - # such hackery - but Mojo::Promises are so well-built that they are invisible - # ... until you leave the Mojo space - $inputs_ref->{problemSource} = $inputs_ref->{problemSource}{results}[0] if $inputs_ref->{problemSource} =~ /Mojo::Promise/; - # sanitize the base64 encoded source - $inputs_ref->{problemSource} =~ s/\s//gm; - # while ($source =~ /([^A-Za-z0-9+])/gm) { - # warn "invalid character found: ".sprintf( "\\u%04x", ord($1) )."\n"; - # } - $source = Encode::decode("UTF-8", decode_base64( $inputs_ref->{problemSource} ) ); - } - else { - ( $adj_file_path, $source ) = get_source($file_path); - - # WHY are there so many fields in which to stash the file path? - #$inputs_ref->{fileName} = $adj_file_path; - #$inputs_ref->{probFileName} = $adj_file_path; - #$inputs_ref->{sourceFilePath} = $adj_file_path; - #$inputs_ref->{pathToProblemFile} = $adj_file_path; - } - my $raw_metadata_text = $1 if ($source =~ /(.*?)DOCUMENT\(\s*\)\s*;/s); - $inputs_ref->{problemUUID} = md5_hex(Encode::encode_utf8($source)); - - # TODO verify line ending are LF instead of CRLF - - # included (external) pg content is not recorded by PGalias - # record the dependency separately -- TODO: incorporate into PG.pl or PGcore? - my $pgResources = []; - while ($source =~ m/includePG(?:problem|file)\(["'](.*)["']\);/g ) - { - warn "PG asset reference found: $1\n" if $UNIT_TESTS_ON; - push @$pgResources, $1; - } - - # # this does not capture _all_ image asset references, unfortunately... - # # asset filenames may be stored as variables before image() is called - # while ($source =~ m/image\(\s*("[^\$]+?"|'[^\$]+?')\s*[,\)]/g) { - # warn "Image asset reference found!\n" . $1 . "\n" if $UNIT_TESTS_ON; - # my $image = $1; - # $image =~ s/['"]//g; - # $image = dirname($file_path) . '/' . $image if ($image =~ /^[^\/]*\.(?:gif|jpg|jpeg|png)$/i); - # warn "Recording image asset as: $image\n" if $UNIT_TESTS_ON; - # push @$assets, $image; - # } - - # $inputs_ref->{pathToProblemFile} = $adj_file_path - # if ( defined $adj_file_path ); - - ################################################## - # Process the pg file - ################################################## - ### store the time before we invoke the content generator - my $cg_start = - time; # this is Time::HiRes's time, which gives floating point values - - ############################################ - # Call server via standaloneRenderer to render problem - ############################################ - - our ( $return_object, $error_flag, $error_string ); - $error_flag = 0; - $error_string = ''; - - my $memory_use_start = get_current_process_memory(); - - # can include @args as fourth input below - $return_object = standaloneRenderer( $ce, \$source, $inputs_ref ); - - # stash assets list in $return_object - $return_object->{pgResources} = $pgResources; - - # stash raw metadata text in $return_object - $return_object->{raw_metadata_text} = $raw_metadata_text; - - # generate sessionJWT to store session data and answerJWT to update grade store - # only occurs if problemJWT exists! - my ($sessionJWT, $answerJWT) = generateJWTs($return_object, $inputs_ref); - $return_object->{sessionJWT} = $sessionJWT // ''; - $return_object->{answerJWT} = $answerJWT // ''; - - ####################################################################### - # Handle errors - ####################################################################### - - print "\n\n Result of renderProblem \n\n" if $UNIT_TESTS_ON; - print pretty_print_rh($return_object) if $UNIT_TESTS_ON; - if ( not defined $return_object ) - { #FIXME make sure this is the right error message if site is unavailable - $error_string = "0\t Could not process $file_path problem file \n"; - } - elsif ( defined( $return_object->{flags}->{error_flag} ) - and $return_object->{flags}->{error_flag} ) - { - $error_string = "0\t $file_path has errors\n"; - } - elsif ( defined( $return_object->{errors} ) and $return_object->{errors} ) { - $error_string = "0\t $file_path has syntax errors\n"; - } - $error_flag = 1 if $return_object->{errors}; - - ################################################## - # Create FormatRenderedProblems object - ################################################## - - # PG/macros/PG.pl wipes out problemSeed -- put it back! - # $inputs_ref->{problemSeed} = $problem_seed; # NO DONT - $inputs_ref->{displayMode} = $display_mode; - - # my $encoded_source = encode_base64($source); # create encoding of source_file; - my $formatter = RenderApp::Controller::FormatRenderedProblem->new( - return_object => $return_object, - encoded_source => '', #encode_base64($source), - sourceFilePath => $file_path, - url => $inputs_ref->{baseURL}, - form_action_url => $inputs_ref->{formURL}, - maketext => sub {return @_}, - courseID => 'blackbox', - userID => 'Motoko_Kusanagi', - course_password => 'daemon', - inputs_ref => $inputs_ref, - ce => $ce, - ); - - ################################################## - # log elapsed time - ################################################## - my $scriptName = 'standalonePGproblemRenderer'; - my $log_file_path = $file_path // 'source provided without path'; - my $cg_end = time; - my $cg_duration = $cg_end - $cg_start; - my $memory_use_end = get_current_process_memory(); - my $memory_use = $memory_use_end - $memory_use_start; - writeRenderLogEntry( - "", - "{script:$scriptName; file:$log_file_path; " - . sprintf( "duration: %.3f sec;", $cg_duration ) - . sprintf( " memory: %6d bytes;", $memory_use ) . "}", - '' - ); - - ####################################################################### - # End processing of the pg file - ####################################################################### - - return $error_flag, $formatter, $error_string; -} - -########################################### -# standalonePGproblemRenderer -########################################### - -sub standaloneRenderer { - - #print "entering standaloneRenderer\n\n"; - my $ce = shift; - my $problemFile = shift // ''; - my $inputs_ref = shift // ''; - my %args = @_; - - # my $key = $r->param('key'); - # WTF is this even here for? PG doesn't do authz - but it wants key? - my $key = '3211234567654321'; - - my $user = fake_user(); - my $set = fake_set(); - my $showHints = $inputs_ref->{showHints} // 1; # default is to showHint if neither showHints nor numIncorrect is provided - my $showSolutions = $inputs_ref->{showSolutions} // 0; - my $problemNumber = $inputs_ref->{problemNumber} // 1; # ever even relevant? - my $displayMode = $inputs_ref->{displayMode} || 'MathJax'; # $ce->{pg}->{options}->{displayMode}; - my $problem_seed = $inputs_ref->{problemSeed} || 1234; - my $permission_level = $inputs_ref->{permissionLevel} || 0; # permissionLevel >= 10 will show hints, solutions + open all scaffold - my $num_correct = $inputs_ref->{numCorrect} || 0; # consider replacing - this may never be relevant... - my $num_incorrect = $inputs_ref->{numIncorrect} // 1000; # default to exceed any problem's showHint threshold unless provided - my $processAnswers = $inputs_ref->{processAnswers} // 1; # default to 1, explicitly avoid generating answer components - my $psvn = $inputs_ref->{psvn} // 123; # by request from Tani - - print "NOT PROCESSING ANSWERS" unless $processAnswers == 1; - - my $translationOptions = { - displayMode => $displayMode, - showHints => $showHints, - showSolutions => $showSolutions, - refreshMath2img => 1, - processAnswers => $processAnswers, - QUIZ_PREFIX => $inputs_ref->{answerPrefix} // '', - useMathQuill => !defined $inputs_ref->{entryAssist} || $inputs_ref->{entryAssist} eq 'MathQuill' ? 1 : 0, - - #use_site_prefix => 'http://localhost:3000', - use_opaque_prefix => 0, - permissionLevel => $permission_level, - effectivePermissionLevel => $permission_level - }; - my $extras = {}; # passed as arg to renderer->new() - - # Create template of problem then add source text or a path to the source file - local $ce->{pg}{specialPGEnvironmentVars}{problemPreamble} = - { TeX => '', HTML => '' }; - local $ce->{pg}{specialPGEnvironmentVars}{problemPostamble} = - { TeX => '', HTML => '' }; - - my $problem = fake_problem(); # eliminated $db arg - $problem->{problem_seed} = $problem_seed; - $problem->{value} = -1; - $problem->{num_correct} = $num_correct; - $problem->{num_incorrect} = $num_incorrect; - $problem->{attempted} = $num_correct + $num_incorrect; - - if ( ref $problemFile ) { - $problem->{source_file} = $inputs_ref->{sourceFilePath}; - $translationOptions->{r_source} = $problemFile; - - # warn "standaloneProblemRenderer: setting source_file = $problemFile"; - } - else { - #in this case the actual source is passed - $problem->{source_file} = $problemFile; - warn "standaloneProblemRenderer: setting source_file = $problemFile"; - - # a path to the problem (relative to the course template directory?) - } - - my $pg = WeBWorK::PG->new( - $ce, - $user, - $key, - $set, - $problem, - $psvn, # by request from Tani - $inputs_ref, - $translationOptions, - $extras, - ); - - # new version of output: - my $warning_messages = ''; # for now -- set up warning trap later - my ( $internal_debug_messages, $pgwarning_messages, $pgdebug_messages ); - if ( ref( $pg->{pgcore} ) ) { - $internal_debug_messages = $pg->{pgcore}->get_internal_debug_messages; - $pgwarning_messages = $pg->{pgcore}->get_warning_messages(); - $pgdebug_messages = $pg->{pgcore}->get_debug_messages(); - } - else { - $internal_debug_messages = - ['Problem failed during render - no PGcore received.']; - } - - my $out2 = { - text => $pg->{body_text}, - header_text => $pg->{head_text}, - post_header_text => $pg->{post_header_text}, - answers => $pg->{answers}, - errors => $pg->{errors}, - WARNINGS => encode_base64( Encode::encode("UTF-8", $pg->{warnings} ) ), - PG_ANSWERS_HASH => $pg->{pgcore}->{PG_ANSWERS_HASH}, - problem_result => $pg->{result}, - problem_state => $pg->{state}, - flags => $pg->{flags}, - resources => [ keys %{$pg->{pgcore}{PG_alias}{resource_list}} ], - warning_messages => $pgwarning_messages, - debug_messages => $pgdebug_messages, - internal_debug_messages => $internal_debug_messages, - }; - $pg->free; - $out2; -} - -sub display_html_output { #display the problem in a browser - my $file_path = shift; - my $formatter = shift; - my $output_text = $formatter->formatRenderedProblem; - return $output_text; -} - -################################################## -# utilities -################################################## - -sub get_current_process_memory { - state $pt = Proc::ProcessTable->new; - my %info = map { $_->pid => $_ } @{ $pt->table }; - return $info{$$}->rss; -} - -# expects a pg/result_object and a ref to submitted formdata -# generates a sessionJWT and an answerJWT -sub generateJWTs { - my $pg = shift; - my $inputs_ref = shift; - my $sessionHash = {'answersSubmitted' => 1, 'iss' =>$ENV{SITE_HOST}}; - my $scoreHash = {}; - - # if no problemJWT exists, then why bother? - return unless $inputs_ref->{problemJWT}; - - # store the current answer/response state for each entry - foreach my $ans (keys %{$pg->{answers}}) { - # TODO: Anything else we want to add to sessionHash? - $sessionHash->{$ans} = $inputs_ref->{$ans}; - $sessionHash->{ 'previous_' . $ans } = $inputs_ref->{$ans}; - $sessionHash->{ 'MaThQuIlL_' . $ans } = $inputs_ref->{ 'MaThQuIlL_' . $ans } if ($inputs_ref->{ 'MaThQuIlL_' . $ans}); - - # $scoreHash->{ans_id} = $ans; - # $scoreHash->{answer} = unbless($pg->{answers}{$ans}) // {}, - # $scoreHash->{score} = $pg->{answers}{$ans}{score} // 0, - - # TODO see why this key is causing JWT corruption in PHP - delete( $pg->{answers}{$ans}{student_ans}); - } - $scoreHash->{answers} = unbless($pg->{answers}); - - # update the number of correct/incorrect submissions if answers were 'submitted' - $sessionHash->{numCorrect} = (defined $inputs_ref->{submitAnswers}) ? - $pg->{problem_state}{num_of_correct_ans} : ($inputs_ref->{numCorrect} // 0); - $sessionHash->{numIncorrect} = (defined $inputs_ref->{submitAnswers}) ? - $pg->{problem_state}{num_of_incorrect_ans} : ($inputs_ref->{numIncorrect} // 0); - - # include the final result of the combined scores - $scoreHash->{result} = $pg->{problem_result}{score}; - - # create and return the session JWT - # TODO swap to alg => 'PBES2-HS512+A256KW', enc => 'A256GCM' - my $sessionJWT = encode_jwt(payload => $sessionHash, auto_iat => 1, alg => 'HS256', key => $ENV{webworkJWTsecret}); - - # form answerJWT - my $responseHash = { - iss => $ENV{SITE_HOST}, - aud => $inputs_ref->{JWTanswerURL}, - score => $scoreHash, - problemJWT => $inputs_ref->{problemJWT}, - sessionJWT => $sessionJWT, - platform => 'standaloneRenderer' - }; - - # Can instead use alg => 'PBES2-HS512+A256KW', enc => 'A256GCM' for JWE - my $answerJWT = encode_jwt(payload=>$responseHash, alg => 'HS256', key => $ENV{problemJWTsecret}, auto_iat => 1); - - return ($sessionJWT, $answerJWT); -} - -sub fake_user { - my $user = { - user_id => 'Motoko_Kusanagi', - first_name => 'Motoko', - last_name => 'Kusanagi', - email_address => 'motoko.kusanagi@npsc.go.jp', - student_id => '123456789', - section => '9', - recitation => '', - comment => '', - }; - return ($user); -} - -sub fake_problem { - my $problem = { - set_id => 'Section_9', - problem_id => 1, - value => 1, - max_attempts => -1, - showMeAnother => -1, - showMeAnotherCount => 0, - problem_seed => 666, - status => 0, - sub_status => 0, - attempted => 0, - last_answer => '', - num_correct => 0, - num_incorrect => 0, - prCount => -10 - }; - - return ($problem); -} - -sub fake_set { - - # my $db = shift; - - my $set = {}; - $set->{psvn} = 666; - $set->{set_id} = "Section_9"; - $set->{open_date} = time(); - $set->{due_date} = time(); - $set->{answer_date} = time(); - $set->{visible} = 0; - $set->{enable_reduced_scoring} = 0; - $set->{hardcopy_header} = "defaultHeader"; - return ($set); -} - -# Get problem template source and adjust file_path name -sub get_source { - my $file_path = shift; - my $source; - die "Unable to read file $file_path \n" - unless $file_path eq '-' or -r $file_path; - eval { #File::Slurp would be faster (see perl monks) - local $/ = undef; - if ( $file_path eq '-' ) { - $source = ; - } else { - # To support proper behavior with UTF-8 files, we need to open them with "<:encoding(UTF-8)" - # as otherwise, the first HTML file will render properly, but when "Preview" "Submit answer" - # or "Show correct answer" is used it will make problems, as in process_problem() the - # encodeSource() method is called on a data which is still UTF-8 encoded, and leads to double - # encoding and gibberish. - # NEW: - open( FH, "<:encoding(UTF-8)", $file_path ) - or die "Couldn't open file $file_path: $!"; - - # OLD: - #open(FH, "<" ,$file_path) or die "Couldn't open file $file_path: $!"; - $source = ; #slurp input - close FH; - } - }; - die "Something is wrong with the contents of $file_path\n" if $@; - return $file_path, $source; -} - -sub pretty_print_rh { - shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); - my $rh = shift; - my $indent = shift || 0; - my $out = ""; - my $type = ref($rh); - - if ( defined($type) and $type ) { - $out .= " type = $type; "; - } - elsif ( !defined($rh) ) { - $out .= " type = UNDEFINED; "; - } - return $out . " " unless defined($rh); - - if ( ref($rh) =~ /HASH/ ) { - $out .= "{\n"; - $indent++; - foreach my $key ( sort keys %{$rh} ) { - $out .= - " " x $indent - . "$key => " - . pretty_print_rh( $rh->{$key}, $indent ) . "\n"; - } - $indent--; - $out .= "\n" . " " x $indent . "}\n"; - - } - elsif ( ref($rh) =~ /ARRAY/ or "$rh" =~ /ARRAY/ ) { - $out .= " ( "; - foreach my $elem ( @{$rh} ) { - $out .= pretty_print_rh( $elem, $indent ); - - } - $out .= " ) \n"; - } - elsif ( ref($rh) =~ /SCALAR/ ) { - $out .= "scalar reference " . ${$rh}; - } - elsif ( ref($rh) =~ /Base64/ ) { - $out .= "base64 reference " . $$rh; - } - else { - $out .= $rh; - } - - return $out . " "; -} - -sub create_course_environment { - my $self = shift; - my $courseName = $self->{courseName} || 'renderer'; - my $ce = WeBWorK::CourseEnvironment->new( - { - webwork_dir => $ENV{WEBWORK_ROOT}, - courseName => $courseName - } - ); - warn "Unable to find environment for course: |$courseName|" unless ref($ce); - return ($ce); -} - -sub writeRenderLogEntry($$$) { - my ( $function, $details, $beginEnd ) = @_; - $beginEnd = - ( $beginEnd eq "begin" ) ? ">" : ( $beginEnd eq "end" ) ? "<" : "-"; - -#writeLog($seed_ce, "render_timing", "$$ ".time." $beginEnd $function [$details]"); - local *LOG; - if ( open LOG, ">>", $path_to_log_file ) { - print LOG "[", time2str( "%a %b %d %H:%M:%S %Y", time ), - "] $$ " . time . " $beginEnd $function [$details]\n"; - close LOG; - } - else { - warn "failed to open $path_to_log_file for writing: $!"; - } -} - -1; diff --git a/lib/RenderApp/Controller/StaticFiles.pm b/lib/RenderApp/Controller/StaticFiles.pm index 0554cdea2..9b0dc9ad7 100644 --- a/lib/RenderApp/Controller/StaticFiles.pm +++ b/lib/RenderApp/Controller/StaticFiles.pm @@ -11,12 +11,12 @@ sub reply_with_file_if_readable ($c, $file) { } } -# Route requests for webwork2_files/CAPA_Graphics to render root Contrib/CAPA/CAPA_Graphics +# Route requests for pg_files/CAPA_Graphics to render root Contrib/CAPA/CAPA_Graphics sub CAPA_graphics_file ($c) { return $c->reply_with_file_if_readable($c->app->home->child('Contrib/CAPA/CAPA_Graphics', $c->stash('static'))); } -# Route requests for webwork2_files to the render root tmp. The +# Route requests for pg_files to the render root tmp. The # only requests should be for files in the temporary directory. # FIXME: Perhaps this directory should be configurable. sub temp_file ($c) { @@ -25,7 +25,11 @@ sub temp_file ($c) { # Route request to pg_files to lib/PG/htdocs. sub pg_file ($c) { - $c->reply_with_file_if_readable(path($WeBWorK::Constants::PG_DIRECTORY, 'htdocs', $c->stash('static'))); + $c->reply_with_file_if_readable(path($ENV{PG_ROOT}, 'htdocs', $c->stash('static'))); +} + +sub public_file($c) { + $c->reply_with_file_if_readable($c->app->home->child('public', $c->stash('static'))); } 1; diff --git a/lib/RenderApp/Model/Problem.pm b/lib/RenderApp/Model/Problem.pm index 55bc24ed5..653c5d12e 100644 --- a/lib/RenderApp/Model/Problem.pm +++ b/lib/RenderApp/Model/Problem.pm @@ -8,7 +8,8 @@ use Mojo::IOLoop; use Mojo::JSON qw( encode_json ); use Mojo::Base -async_await; use Time::HiRes qw( time ); -use RenderApp::Controller::RenderProblem; +use MIME::Base64 qw( decode_base64 ); +use WeBWorK::RenderProblem; ##### Problem params: ##### # = random_seed (set randomization for rendering) @@ -67,7 +68,7 @@ sub _init { # sourcecode takes precedence over reading from file path if ( $problem_contents =~ /\S/ ) { $self->source($problem_contents); - $self->{code_origin} = 'pg source (' . $self->path( $read_path, 'force' ) .')'; + $self->{code_origin} = 'pg source (' . ($self->path( $read_path, 'force' ) || 'no path provided') .')'; # set read_path without failing for !-e # this supports images in problems via editor } else { @@ -88,9 +89,12 @@ sub source { if ( scalar(@_) == 1 ) { my $contents = shift; + # recognize and decode base64 if necessary + $contents = Encode::decode( "UTF-8", decode_base64($contents) ) + if ( $contents =~ m!^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$!); + # UNIX style line-endings are required - $contents =~ s/\r\n/\n/g; - $contents =~ s/\r/\n/g; + $contents =~ s!\r\n?!\n!g; $self->{problem_contents} = $contents; } return $self->{problem_contents}; @@ -131,7 +135,8 @@ sub path { } $self->{_error} = "404 I cannot find a problem with that file path." unless ( -e $read_path || $force ); - $self->{read_path} = Mojo::File->new($read_path); + # if we objectify an empty string, it becomes truth-y -- AVOID! + $self->{read_path} = Mojo::File->new($read_path) if $read_path; } return $self->{read_path}; } @@ -162,7 +167,7 @@ sub save { my $self = shift; my $success = 0; my $write_path = - ( $self->{write_path} =~ /\S/ ) ? + ( $self->{write_path} =~ /\S/ ) ? $self->{write_path} : $self->{read_path}; @@ -217,7 +222,7 @@ sub render { my $inputs_ref = shift; $self->{action} = 'render'; my $renderPromise = Mojo::IOLoop->subprocess->run_p( sub { - return RenderApp::Controller::RenderProblem::process_pg_file( $self, $inputs_ref ); + return WeBWorK::RenderProblem::process_pg_file( $self, $inputs_ref ); })->catch(sub { $self->{exception} = Mojo::Exception->new(shift)->trace; $self->{_error} = "500 Render failed: " . $self->{exception}->message; diff --git a/lib/WeBWorK/AttemptsTable.pm b/lib/WeBWorK/AttemptsTable.pm new file mode 100644 index 000000000..46edddb8f --- /dev/null +++ b/lib/WeBWorK/AttemptsTable.pm @@ -0,0 +1,467 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2022 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 + + AttemptsTable + +=head1 SYNPOSIS + + my $tbl = WeBWorK::AttemptsTable->new( + $answers, + answersSubmitted => 1, + answerOrder => $pg->{flags}{ANSWER_ENTRY_ORDER}, + displayMode => 'MathJax', + showAnswerNumbers => 0, + showAttemptAnswers => $showAttemptAnswers && $showEvaluatedAnswers, + showAttemptPreviews => $showAttemptPreview, + showAttemptResults => $showAttemptResults, + showCorrectAnswers => $showCorrectAnswers, + showMessages => $showAttemptAnswers, # internally checks for messages + showSummary => $showSummary, + imgGen => $imgGen, # not needed if ce is present , + ce => '', # not needed if $imgGen is present + maketext => WeBWorK::Localize::getLoc("en"), + ); + $tbl->{imgGen}->render(refresh => 1) if $tbl->displayMode eq 'images'; + my $answerTemplate = $tbl->answerTemplate; + + +=head1 DESCRIPTION + +This module handles the formatting of the table which presents the results of analyzing a student's +answer to a WeBWorK problem. It is used in Problem.pm, OpaqueServer.pm, standAlonePGproblemRender + +=head2 new + + my $tbl = WeBWorK::AttemptsTable->new( + $answers, + answersSubmitted => 1, + answerOrder => $pg->{flags}{ANSWER_ENTRY_ORDER}, + displayMode => 'MathJax', + showHeadline => 1, + showAnswerNumbers => 0, + showAttemptAnswers => $showAttemptAnswers && $showEvaluatedAnswers, + showAttemptPreviews => $showAttemptPreview, + showAttemptResults => $showAttemptResults, + showCorrectAnswers => $showCorrectAnswers, + showMessages => $showAttemptAnswers, # internally checks for messages + showSummary => $showSummary, + imgGen => $imgGen, # not needed if ce is present , + ce => '', # not needed if $imgGen is present + maketext => WeBWorK::Localize::getLoc("en"), + summary =>'', + ); + + $answers -- a hash of student answers e.g. $pg->{answers} + answersSubmitted if 0 then then the attemptsTable is not displayed (???) + answerOrder -- an array indicating the order the answers appear on the page. + displayMode 'MathJax' and 'images' are the most common + + showHeadline Show the header line 'Results for this submission' + + showAnswerNumbers, showAttemptAnswers, showAttemptPreviews,showAttemptResults, + showCorrectAnswers and showMessages control the display of each column in the table. + + attemptAnswers the student's typed in answer (possibly simplified numerically) + attemptPreview the student's answer after typesetting + attemptResults "correct", "_% correct", "incorrect" or "ungraded"- links to the answer blank + correctAnswers typeset version (untypeset versions are available via popups) + messages warns of formatting typos in the answer, or + more detailed messages about a wrong answer + summary is obtained from $pg->{result}{summary}. + If this is empty then a (localized) + version of "all answers are correct" + or "at least one answer is not coorrect" + imgGen points to a prebuilt image generator objectfor "images" mode + ce points to the CourseEnvironment -- it is needed if AttemptsTable + is required to build its own imgGen object + maketext points to a localization subroutine + +=head2 Methods + +=over 4 + +=item answerTemplate + +Returns HTML which formats the analysis of the student's answers to the problem. + +=back + +=head2 Read/Write Properties + +=over 4 + +=item showMessages, + +This can be switched on or off before exporting the answerTemplate, perhaps +under instructions from the PG problem. + +=item summary + +The contents of the summary can be defined when the attemptsTable object is created. + +The summary can be defined by the PG problem grader usually returned as +$pg->{result}{summary}. + +If the summary is not explicitly defined then (localized) versions +of the default summaries are created: + + "The answer above is correct.", + "Some answers will be graded later.", + "All of the [gradeable] answers above are correct.", + "[N] of the questions remain unanswered.", + "At least one of the answers above is NOT [fully] correct.', + +Note that if this is set after initialization, you must ensure that it is a +Mojo::ByteStream object if it contains html or characters that need escaping. + +=back + +=cut + +package WeBWorK::AttemptsTable; +use Mojo::Base 'Class::Accessor', -signatures; + +use Scalar::Util 'blessed'; +use WeBWorK::Utils 'wwRound'; + +# %options may contain: displayMode, submitted, imgGen, ce +# At least one of imgGen or ce must be provided if displayMode is 'images'. +sub new ($class, $rh_answers, $c, %options) { + $class = ref $class || $class; + ref($rh_answers) =~ /HASH/ or die 'The first entry to AttemptsTable must be a hash of answers'; + $c->isa('Mojolicious::Controller') or die 'The second entry to AttemptsTable must be a Mojolicious::Controller'; + my $self = bless { + answers => $rh_answers, + c => $c, + answerOrder => $options{answerOrder} // [], + answersSubmitted => $options{answersSubmitted} // 0, + summary => undef, # summary provided by problem grader (set in _init) + displayMode => $options{displayMode} || 'MathJax', + showHeadline => $options{showHeadline} // 1, + showAnswerNumbers => $options{showAnswerNumbers} // 1, + showAttemptAnswers => $options{showAttemptAnswers} // 1, # show student answer as entered and parsed + showAttemptPreviews => $options{showAttemptPreviews} // 1, # show preview of student answer + showAttemptResults => $options{showAttemptResults} // 1, # show results of grading student answer + showMessages => $options{showMessages} // 1, # show messages generated by evaluation + showCorrectAnswers => $options{showCorrectAnswers} // 0, # show the correct answers + showSummary => $options{showSummary} // 1, # show result summary + imgGen => undef, # set or created in _init method + mtRef => $options{mtRef} // sub { return $_[0] }, + }, $class; + + # Create accessors/mutators + $self->mk_ro_accessors(qw(answers c answerOrder answersSubmitted displayMode imgGen showAnswerNumbers + showAttemptAnswers showHeadline showAttemptPreviews showAttemptResults showCorrectAnswers showSummary)); + $self->mk_accessors(qw(showMessages summary)); + + # Sanity check and initialize imgGenerator. + $self->_init(%options); + + return $self; +} + +# Verify the display mode, and build imgGen if it is not supplied. +sub _init ($self, %options) { + $self->{submitted} = $options{submitted} // 0; + $self->{displayMode} = $options{displayMode} || 'MathJax'; + + # Only show message column if there is at least one message. + my @reallyShowMessages = grep { $self->answers->{$_}{ans_message} } @{ $self->answerOrder }; + $self->showMessages($self->showMessages && !!@reallyShowMessages); + + # Only used internally. Accessors are not needed. + $self->{numCorrect} = 0; + $self->{numBlanks} = 0; + $self->{numEssay} = 0; + + if ($self->displayMode eq 'images') { + if (blessed($options{imgGen}) && $options{imgGen}->isa('WeBWorK::PG::ImageGenerator')) { + $self->{imgGen} = $options{imgGen}; + } elsif (blessed($options{ce}) && $options{ce}->isa('WeBWorK::CourseEnvironment')) { + my $ce = $options{ce}; + + $self->{imgGen} = WeBWorK::PG::ImageGenerator->new( + tempDir => $ce->{webworkDirs}{tmp}, + latex => $ce->{externalPrograms}{latex}, + dvipng => $ce->{externalPrograms}{dvipng}, + useCache => 1, + cacheDir => $ce->{webworkDirs}{equationCache}, + cacheURL => $ce->{server_root_url} . $ce->{webworkURLs}{equationCache}, + cacheDB => $ce->{webworkFiles}{equationCacheDB}, + dvipng_align => $ce->{pg}{displayModeOptions}{images}{dvipng_align}, + dvipng_depth_db => $ce->{pg}{displayModeOptions}{images}{dvipng_depth_db}, + ); + } else { + warn 'Must provide image Generator (imgGen) or a course environment (ce) to build attempts table.'; + } + } + + # Make sure that the provided summary is a Mojo::ByteStream object. + $self->summary(blessed($options{summary}) + && $options{summary}->isa('Mojo::ByteStream') ? $options{summary} : $self->c->b($options{summary} // '')); + + return; +} + +sub formatAnswerRow ($self, $rh_answer, $ans_id, $answerNumber) { + my $c = $self->c; + + my $answerString = $rh_answer->{student_ans} // ''; + my $answerPreview = $self->previewAnswer($rh_answer) // ' '; + my $correctAnswer = $rh_answer->{correct_ans} // ''; + my $correctAnswerPreview = $self->previewCorrectAnswer($rh_answer) // ' '; + + my $answerMessage = $rh_answer->{ans_message} // ''; + $answerMessage =~ s/\n/
/g; + my $answerScore = $rh_answer->{score} // 0; + $self->{numCorrect} += $answerScore >= 1; + $self->{numEssay} += ($rh_answer->{type} // '') eq 'essay'; + $self->{numBlanks}++ unless $answerString =~ /\S/ || $answerScore >= 1; + + my $feedbackMessageClass = ($answerMessage eq '') ? '' : $self->maketext('FeedbackMessage'); + + my $resultString; + my $resultStringClass; + if ($answerScore >= 1) { + $resultString = $self->maketext('correct'); + $resultStringClass = 'ResultsWithoutError'; + } elsif (($rh_answer->{type} // '') eq 'essay') { + $resultString = $self->maketext('Ungraded'); + $self->{essayFlag} = 1; + } elsif ($answerScore == 0) { + $resultStringClass = 'ResultsWithError'; + $resultString = $self->maketext('incorrect'); + } else { + $resultString = $self->maketext('[_1]% correct', wwRound(0, $answerScore * 100)); + } + my $attemptResults = $c->tag( + 'td', + class => $resultStringClass, + $c->tag('a', href => '#', data => { answer_id => $ans_id }, $self->nbsp($resultString)) + ); + + return $c->c( + $self->showAnswerNumbers ? $c->tag('td', $answerNumber) : '', + $self->showAttemptAnswers ? $c->tag('td', dir => 'auto', $self->nbsp($answerString)) : '', + $self->showAttemptPreviews ? $self->formatToolTip($answerString, $answerPreview) : '', + $self->showAttemptResults ? $attemptResults : '', + $self->showCorrectAnswers ? $self->formatToolTip($correctAnswer, $correctAnswerPreview) : '', + $self->showMessages ? $c->tag('td', class => $feedbackMessageClass, $self->nbsp($answerMessage)) : '' + )->join(''); +} + +# Determine whether any answers were submitted and create answer template if they have been. +sub answerTemplate ($self) { + my $c = $self->c; + + return '' unless $self->answersSubmitted; # Only print if there is at least one non-blank answer + + my $tableRows = $c->c; + + push( + @$tableRows, + $c->tag( + 'tr', + $c->c( + $self->showAnswerNumbers ? $c->tag('th', '#') : '', + $self->showAttemptAnswers ? $c->tag('th', $self->maketext('Entered')) : '', + $self->showAttemptPreviews ? $c->tag('th', $self->maketext('Answer Preview')) : '', + $self->showAttemptResults ? $c->tag('th', $self->maketext('Result')) : '', + $self->showCorrectAnswers ? $c->tag('th', $self->maketext('Correct Answer')) : '', + $self->showMessages ? $c->tag('th', $self->maketext('Message')) : '' + )->join('') + ) + ); + + my $answerNumber = 0; + for (@{ $self->answerOrder() }) { + push @$tableRows, $c->tag('tr', $self->formatAnswerRow($self->{answers}{$_}, $_, ++$answerNumber)); + } + + return $c->c( + $self->showHeadline + ? $c->tag('h2', class => 'attemptResultsHeader', $self->maketext('Results for this submission')) + : '', + $c->tag( + 'div', + class => 'table-responsive', + $c->tag('table', class => 'attemptResults table table-sm table-bordered', $tableRows->join('')) + ), + $self->showSummary ? $self->createSummary : '' + )->join(''); +} + +sub previewAnswer ($self, $answerResult) { + my $displayMode = $self->displayMode; + my $imgGen = $self->imgGen; + + my $tex = $answerResult->{preview_latex_string}; + + return '' unless defined $tex and $tex ne ''; + + return $tex if $answerResult->{non_tex_preview}; + + if ($displayMode eq 'plainText') { + return $tex; + } elsif (($answerResult->{type} // '') eq 'essay') { + return $tex; + } elsif ($displayMode eq 'images') { + return $imgGen->add($tex); + } elsif ($displayMode eq 'MathJax') { + return $self->c->tag('script', type => 'math/tex; mode=display', $self->c->b($tex)); + } +} + +sub previewCorrectAnswer ($self, $answerResult) { + my $displayMode = $self->displayMode; + my $imgGen = $self->imgGen; + + my $tex = $answerResult->{correct_ans_latex_string}; + + # Some answers don't have latex strings defined return the raw correct answer + # unless defined $tex and $tex contains non whitespace characters; + return $answerResult->{correct_ans} + unless defined $tex and $tex =~ /\S/; + + return $tex if $answerResult->{non_tex_preview}; + + if ($displayMode eq 'plainText') { + return $tex; + } elsif ($displayMode eq 'images') { + return $imgGen->add($tex); + } elsif ($displayMode eq 'MathJax') { + return $self->c->tag('script', type => 'math/tex; mode=display', $self->c->b($tex)); + } +} + +# Create summary +sub createSummary ($self) { + my $c = $self->c; + + my $numCorrect = $self->{numCorrect}; + my $numBlanks = $self->{numBlanks}; + my $numEssay = $self->{numEssay}; + + my $summary; + + unless (defined($self->summary) and $self->summary =~ /\S/) { + # Default messages + $summary = $c->c; + my @answerNames = @{ $self->answerOrder() }; + if (scalar @answerNames == 1) { + if ($numCorrect == scalar @answerNames) { + push( + @$summary, + $c->tag( + 'div', + class => 'ResultsWithoutError mb-2', + $self->maketext('The answer above is correct.') + ) + ); + } elsif ($self->{essayFlag}) { + push(@$summary, $c->tag('div', $self->maketext('Some answers will be graded later.'))); + } else { + push( + @$summary, + $c->tag( + 'div', + class => 'ResultsWithError mb-2', + $self->maketext('The answer above is NOT correct.') + ) + ); + } + } else { + if ($numCorrect + $numEssay == scalar @answerNames) { + if ($numEssay) { + push( + @$summary, + $c->tag( + 'div', + class => 'ResultsWithoutError mb-2', + $self->maketext('All of the gradeable answers above are correct.') + ) + ); + } else { + push( + @$summary, + $c->tag( + 'div', + class => 'ResultsWithoutError mb-2', + $self->maketext('All of the answers above are correct.') + ) + ); + } + } elsif ($numBlanks + $numEssay != scalar(@answerNames)) { + push( + @$summary, + $c->tag( + 'div', + class => 'ResultsWithError mb-2', + $self->maketext('At least one of the answers above is NOT correct.') + ) + ); + } + if ($numBlanks > $numEssay) { + my $s = ($numBlanks > 1) ? '' : 's'; + push( + @$summary, + $c->tag( + 'div', + class => 'ResultsAlert mb-2', + $self->maketext( + '[quant,_1,of the questions remains,of the questions remain] unanswered.', $numBlanks + ) + ) + ); + } + } + $summary = $summary->join(''); + } else { + $summary = $self->summary; # Summary defined by grader + } + $summary = $c->tag('div', role => 'alert', class => 'attemptResultsSummary', $summary); + $self->summary($summary); + return $summary; +} + +# Utility subroutine that prevents unwanted line breaks, and ensures that the return value is a Mojo::ByteStream object. +sub nbsp ($self, $str) { + return $self->c->b(defined $str && $str =~ /\S/ ? $str : ' '); +} + +# Note that formatToolTip output includes the wrapper. +sub formatToolTip ($self, $answer, $formattedAnswer) { + return $self->c->tag( + 'td', + $self->c->tag( + 'div', + class => 'answer-preview', + data => { + bs_toggle => 'popover', + bs_content => $answer, + bs_placement => 'bottom', + }, + $self->nbsp($formattedAnswer) + ) + ); +} + +sub maketext ($self, @args) { + return $self->{mtRef}->(@args); +} + +1; diff --git a/lib/WeBWorK/lib/WeBWorK/Form.pm b/lib/WeBWorK/Form.pm similarity index 100% rename from lib/WeBWorK/lib/WeBWorK/Form.pm rename to lib/WeBWorK/Form.pm diff --git a/lib/WeBWorK/FormatRenderedProblem.pm b/lib/WeBWorK/FormatRenderedProblem.pm new file mode 100644 index 000000000..b6904a8fb --- /dev/null +++ b/lib/WeBWorK/FormatRenderedProblem.pm @@ -0,0 +1,313 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2022 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 + +FormatRenderedProblem.pm + +=cut + +package WeBWorK::FormatRenderedProblem; + +use strict; +use warnings; + +use JSON; +use Digest::SHA qw(sha1_base64); +use Mojo::Util qw(xml_escape); +use Mojo::DOM; + +use WeBWorK::Localize; +use WeBWorK::AttemptsTable; +use WeBWorK::Utils qw(getAssetURL); +use WeBWorK::Utils::LanguageAndDirection; + +sub formatRenderedProblem { + my $c = shift; + my $rh_result = shift; + my $inputs_ref = $rh_result->{inputs_ref}; + + my $renderErrorOccurred = 0; + + my $problemText = $rh_result->{text} // ''; + $problemText .= $rh_result->{flags}{comment} if ( $rh_result->{flags}{comment} && $inputs_ref->{showComments} ); + + if ($rh_result->{flags}{error_flag}) { + $rh_result->{problem_result}{score} = 0; # force score to 0 for such errors. + $renderErrorOccurred = 1; + } + + my $SITE_URL = $inputs_ref->{baseURL} || $main::basehref; + my $FORM_ACTION_URL = $inputs_ref->{formURL} || $main::formURL; + + my $displayMode = $inputs_ref->{displayMode} // 'MathJax'; + + # HTML document language setting + my $formLanguage = $inputs_ref->{language} // 'en'; + + # Third party CSS + # The second element of each array in the following is whether or not the file is a theme file. + # customize source for bootstrap.css + my @third_party_css = map { getAssetURL($formLanguage, $_->[0]) } ( + [ 'css/bootstrap.css', ], + [ 'node_modules/jquery-ui-dist/jquery-ui.min.css', ], + [ 'node_modules/@fortawesome/fontawesome-free/css/all.min.css' ], + ); + + # Add CSS files requested by problems via ADD_CSS_FILE() in the PG file + # or via a setting of $ce->{pg}{specialPGEnvironmentVars}{extra_css_files} + # which can be set in course.conf (the value should be an anonomous array). + my @cssFiles; + # if (ref($ce->{pg}{specialPGEnvironmentVars}{extra_css_files}) eq 'ARRAY') { + # push(@cssFiles, { file => $_, external => 0 }) for @{ $ce->{pg}{specialPGEnvironmentVars}{extra_css_files} }; + # } + if (ref($rh_result->{flags}{extra_css_files}) eq 'ARRAY') { + push @cssFiles, @{ $rh_result->{flags}{extra_css_files} }; + } + my %cssFilesAdded; # Used to avoid duplicates + my @extra_css_files; + for (@cssFiles) { + next if $cssFilesAdded{ $_->{file} }; + $cssFilesAdded{ $_->{file} } = 1; + if ($_->{external}) { + push(@extra_css_files, $_); + } else { + push(@extra_css_files, { file => getAssetURL($formLanguage, $_->{file}), external => 0 }); + } + } + + # Third party JavaScript + # The second element of each array in the following is whether or not the file is a theme file. + # The third element is a hash containing the necessary attributes for the script tag. + my @third_party_js = map { [ getAssetURL($formLanguage, $_->[0]), $_->[1] ] } ( + [ 'node_modules/jquery/dist/jquery.min.js', {} ], + [ 'node_modules/jquery-ui-dist/jquery-ui.min.js', {} ], + [ 'node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js', {} ], + [ "js/apps/MathJaxConfig/mathjax-config.js", { defer => undef } ], + [ 'node_modules/mathjax/es5/tex-svg.js', { defer => undef, id => 'MathJax-script' } ], + [ 'node_modules/bootstrap/dist/js/bootstrap.bundle.min.js', { defer => undef } ], + [ "js/apps/Problem/problem.js", { defer => undef } ], + [ "js/apps/Problem/submithelper.js", { defer => undef } ], + [ "js/apps/CSSMessage/css-message.js", { defer => undef } ], + ); + + # Get the requested format. (outputFormat or outputformat) + # override to static mode if showCorrectAnswers has been set + my $formatName = $inputs_ref->{showCorrectAnswers} && !$inputs_ref->{isInstructor} + ? 'static' : $inputs_ref->{outputFormat}; + + # Add JS files requested by problems via ADD_JS_FILE() in the PG file. + my @extra_js_files; + if (ref($rh_result->{flags}{extra_js_files}) eq 'ARRAY') { + my %jsFiles; + for (@{ $rh_result->{flags}{extra_js_files} }) { + next if $jsFiles{ $_->{file} }; + $jsFiles{ $_->{file} } = 1; + my %attributes = ref($_->{attributes}) eq 'HASH' ? %{ $_->{attributes} } : (); + if ($_->{external}) { + push(@extra_js_files, $_); + } else { + push(@extra_js_files, + { file => getAssetURL($formLanguage, $_->{file}), external => 0, attributes => $_->{attributes} }); + } + } + } + + # Set up the problem language and direction + # PG files can request their language and text direction be set. If we do not have access to a default course + # language, fall back to the $formLanguage instead. + # TODO: support for right-to-left languages + my %PROBLEM_LANG_AND_DIR = + get_problem_lang_and_dir($rh_result->{flags}, 'auto:en:ltr', $formLanguage); + my $PROBLEM_LANG_AND_DIR = join(' ', map {qq{$_="$PROBLEM_LANG_AND_DIR{$_}"}} keys %PROBLEM_LANG_AND_DIR); + + # is there a reason this doesn't use the same button IDs? + my $previewMode = defined($inputs_ref->{previewAnswers}) || 0; + my $submitMode = defined($inputs_ref->{submitAnswers}) || $inputs_ref->{answersSubmitted} || 0; + my $showCorrectMode = defined($inputs_ref->{showCorrectAnswers}) || 0; + # A problemUUID should be added to the request as a parameter. It is used by PG to create a proper UUID for use in + # aliases for resources. It should be unique for a course, user, set, problem, and version. + my $problemUUID = $inputs_ref->{problemUUID} // ''; + my $problemResult = $rh_result->{problem_result} // {}; + my $showSummary = $inputs_ref->{showSummary} // 1; + my $showAnswerNumbers = $inputs_ref->{showAnswerNumbers} // 0; # default no + # allow the request to hide the results table or messages + my $showTable = $inputs_ref->{hideAttemptsTable} ? 0 : 1; + my $showMessages = $inputs_ref->{hideMessages} ? 0 : 1; + # allow the request to override the display of partial correct answers + my $showPartialCorrectAnswers = $inputs_ref->{showPartialCorrectAnswers} + // $rh_result->{flags}{showPartialCorrectAnswers}; + + # Attempts table + my $answerTemplate = ''; + + # Do not produce an AttemptsTable when we had a rendering error. + if (!$renderErrorOccurred && $submitMode && $showTable) { + my $tbl = WeBWorK::AttemptsTable->new( + $rh_result->{answers} // {}, $c, + answersSubmitted => 1, + answerOrder => $rh_result->{flags}{ANSWER_ENTRY_ORDER} // [], + displayMode => $displayMode, + showAnswerNumbers => $showAnswerNumbers, + showAttemptAnswers => 0, + showAttemptPreviews => 1, + showAttemptResults => $showPartialCorrectAnswers && !$previewMode, + showCorrectAnswers => $showCorrectMode, + showMessages => $showMessages, + showSummary => $showSummary && !$previewMode, + mtRef => WeBWorK::Localize::getLoc($formLanguage), + summary => $problemResult->{summary} // '', # can be set by problem grader + ); + $answerTemplate = $tbl->answerTemplate; + # $tbl->imgGen->render(refresh => 1) if $tbl->displayMode eq 'images'; + } + + # Answer hash in XML format used by the PTX format. + my $answerhashXML = ''; + if ($formatName eq 'ptx') { + my $dom = Mojo::DOM->new->xml(1); + for my $answer (sort keys %{ $rh_result->{answers} }) { + $dom->append_content($dom->new_tag( + $answer, + map { $_ => ($rh_result->{answers}{$answer}{$_} // '') } keys %{ $rh_result->{answers}{$answer} } + )); + } + $dom->wrap_content(''); + $answerhashXML = $dom->to_string; + } + + # Make sure this is defined and is an array reference as saveGradeToLTI might add to it. + $rh_result->{debug_messages} = [] unless defined $rh_result && ref $rh_result->{debug_messages} eq 'ARRAY'; + + # Execute and return the interpolated problem template + + # Raw format + # This format returns javascript object notation corresponding to the perl hash + # with everything that a client-side application could use to work with the problem. + # There is no wrapping HTML "_format" template. + if ($formatName eq 'raw') { + my $output = {}; + + # Everything that ships out with other formats can be constructed from these + $output->{rh_result} = $rh_result; + $output->{inputs_ref} = $inputs_ref; + # $output->{input} = $ws->{input}; + + # The following could be constructed from the above, but this is a convenience + $output->{answerTemplate} = $answerTemplate->to_string if ($answerTemplate); + $output->{lang} = $PROBLEM_LANG_AND_DIR{lang}; + $output->{dir} = $PROBLEM_LANG_AND_DIR{dir}; + $output->{extra_css_files} = \@extra_css_files; + $output->{extra_js_files} = \@extra_js_files; + + # Include third party css and javascript files. Only jquery, jquery-ui, mathjax, and bootstrap are needed for + # PG. See the comments before the subroutine definitions for load_css and load_js in pg/macros/PG.pl. + # The other files included are only needed to make themes work in the webwork2 formats. + $output->{third_party_css} = \@third_party_css; + $output->{third_party_js} = \@third_party_js; + + # Say what version of WeBWorK this is + # $output->{ww_version} = $ce->{WW_VERSION}; + # $output->{pg_version} = $ce->{PG_VERSION}; + + # Convert to JSON and render. + return $c->render(data => JSON->new->utf8(1)->encode($output)); + } + + # Setup and render the appropriate template in the templates/RPCRenderFormats folder depending on the outputformat. + # "ptx" has a special template. "json" uses the default json template. All others use the default html template. + my %template_params = ( + template => $formatName eq 'ptx' ? 'RPCRenderFormats/ptx' : 'RPCRenderFormats/default', + $formatName eq 'json' ? (format => 'json') : (), + formatName => $formatName, + lh => WeBWorK::Localize::getLangHandle($inputs_ref->{language} // 'en'), + rh_result => $rh_result, + SITE_URL => $SITE_URL, + FORM_ACTION_URL => $FORM_ACTION_URL, + COURSE_LANG_AND_DIR => get_lang_and_dir($formLanguage), + PROBLEM_LANG_AND_DIR => $PROBLEM_LANG_AND_DIR, + third_party_css => \@third_party_css, + extra_css_files => \@extra_css_files, + third_party_js => \@third_party_js, + extra_js_files => \@extra_js_files, + problemText => $problemText, + extra_header_text => $inputs_ref->{extra_header_text} // '', + answerTemplate => $answerTemplate, + showScoreSummary => $submitMode && !$renderErrorOccurred && !$previewMode && $problemResult, + answerhashXML => $answerhashXML, + showPreviewButton => $inputs_ref->{hidePreviewButton} ? '0' : '', + showCheckAnswersButton => $inputs_ref->{hideCheckAnswersButton} ? '0' : '', + showCorrectAnswersButton => $inputs_ref->{showCorrectAnswersButton} // $inputs_ref->{isInstructor} ? '' : '0', + showFooter => $inputs_ref->{showFooter} // '0', + pretty_print => \&pretty_print, + ); + + return $c->render(%template_params) if $formatName eq 'json' && !$inputs_ref->{send_pg_flags}; + $rh_result->{renderedHTML} = $c->render_to_string(%template_params)->to_string; + return $c->respond_to( + html => { text => $rh_result->{renderedHTML} }, + json => { json => $rh_result }); +} + +# Nice output for debugging +sub pretty_print { + my ($r_input, $level) = @_; + $level //= 4; + $level--; + return '' unless $level > 0; # Only print three levels of hashes (safety feature) + my $out = ''; + if (!ref $r_input) { + $out = $r_input if defined $r_input; + $out =~ s/}; + + for my $key (sort keys %$r_input) { + # Safety feature - we do not want to display the contents of %seed_ce which + # contains the database password and lots of other things, and explicitly hide + # certain internals of the CourseEnvironment in case one slips in. + next + if (($key =~ /database/) + || ($key =~ /dbLayout/) + || ($key eq "ConfigValues") + || ($key eq "ENV") + || ($key eq "externalPrograms") + || ($key eq "permissionLevels") + || ($key eq "seed_ce")); + $out .= "$key=> " . pretty_print($r_input->{$key}, $level) . ""; + } + $out .= ''; + } elsif (ref $r_input eq 'ARRAY') { + my @array = @$r_input; + $out .= '( '; + while (@array) { + $out .= pretty_print(shift @array, $level) . ' , '; + } + $out .= ' )'; + } elsif (ref $r_input eq 'CODE') { + $out = "$r_input"; + } else { + $out = $r_input; + $out =~ s/catfile($path, '*.[pm]o'); -my $decode = 1; -my $encoding = undef; +my $path = "$ENV{RENDER_ROOT}/lib/WeBWorK/Localize"; +my $pattern = File::Spec->catfile($path, '*.[pm]o'); +my $decode = 1; +my $encoding = undef; eval " package WeBWorK::Localize::I18N; diff --git a/lib/WeBWorK/lib/WeBWorK/Localize/en.po b/lib/WeBWorK/Localize/en.po similarity index 100% rename from lib/WeBWorK/lib/WeBWorK/Localize/en.po rename to lib/WeBWorK/Localize/en.po diff --git a/lib/WeBWorK/lib/WeBWorK/Localize/heb.po b/lib/WeBWorK/Localize/heb.po similarity index 100% rename from lib/WeBWorK/lib/WeBWorK/Localize/heb.po rename to lib/WeBWorK/Localize/heb.po diff --git a/lib/WeBWorK/lib/WeBWorK/Localize/standalone.pot b/lib/WeBWorK/Localize/standalone.pot similarity index 100% rename from lib/WeBWorK/lib/WeBWorK/Localize/standalone.pot rename to lib/WeBWorK/Localize/standalone.pot diff --git a/lib/WeBWorK/RenderProblem.pm b/lib/WeBWorK/RenderProblem.pm new file mode 100644 index 000000000..a15c846f6 --- /dev/null +++ b/lib/WeBWorK/RenderProblem.pm @@ -0,0 +1,433 @@ +package WeBWorK::RenderProblem; + +use strict; +use warnings; + +use Time::HiRes qw/time/; +use Date::Format; +use MIME::Base64 qw(encode_base64 decode_base64); +use File::Find; +use FileHandle; +use File::Path; +use File::Basename; + +#use File::Temp qw/tempdir/; +use String::ShellQuote; +use Cwd 'abs_path'; +use JSON::XS; +use Crypt::JWT qw( encode_jwt ); +use Digest::MD5 qw( md5_hex ); + +use lib "$ENV{PG_ROOT}/lib"; + +use Proc::ProcessTable; # use for log memory use +use WeBWorK::PG; +use WeBWorK::Utils::Tags; +use WeBWorK::Localize; +use WeBWorK::FormatRenderedProblem; + +# use 5.10.0; +# $Carp::Verbose = 1; + +### verbose output when UNIT_TESTS_ON =1; +our $UNIT_TESTS_ON = 0; + +#our @path_list; + +################################################## +# create log files :: expendable +################################################## + +my $path_to_log_file = "$ENV{RENDER_ROOT}/logs/standalone_results.log"; + +eval { # attempt to create log file + local (*FH); + open( FH, '>>:encoding(UTF-8)', $path_to_log_file ) + or die "Can't open file $path_to_log_file for writing"; + close(FH); +}; + +die +"You must first create an output file at $path_to_log_file with permissions 777 " + unless -w $path_to_log_file; + +################################################## +# define universal TO_JSON for JSON::XS unbless +################################################## + +sub UNIVERSAL::TO_JSON { + my ($self) = shift; + + use Storable qw(dclone); + use Data::Structure::Util qw(unbless); + + my $clone = unbless( dclone($self) ); + + $clone; +} + +########################################################## +# END MAIN :: BEGIN SUBROUTINES +########################################################## + +####################################################################### +# Process the pg file +####################################################################### + +sub process_pg_file { + my $problem = shift; + my $inputs_ref = shift; + + # just make sure we have the fundamentals covered... + $inputs_ref->{displayMode} ||= 'MathJax'; + $inputs_ref->{outputFormat} ||= $inputs_ref->{outputformat} || 'default'; + $inputs_ref->{language} ||= 'en'; + $inputs_ref->{isInstructor} //= ($inputs_ref->{permissionLevel} // 0) >= 10; + # HACK: required for problemRandomize.pl + $inputs_ref->{effectiveUser} = 'red.ted'; + $inputs_ref->{user} = 'red.ted'; + + my $pg_start = time; + my $memory_use_start = get_current_process_memory(); + + my ( $return_object, $error_flag, $error_string ) = + process_problem( $problem, $inputs_ref ); + + my $pg_stop = time; + my $pg_duration = $pg_stop - $pg_start; + my $log_file_path = $problem->path() || 'source provided without path'; + my $memory_use_end = get_current_process_memory(); + my $memory_use = $memory_use_end - $memory_use_start; + writeRenderLogEntry( + sprintf( "(duration: %.3f sec) ", $pg_duration ) + . sprintf( "{memory: %6d bytes} ", $memory_use ) + . "file: $log_file_path" + ); + + # format result + # my $html = $formatter->formatRenderedProblem; + # my $pg_obj = $formatter->{return_object}; + # my $json_rh = { + # renderedHTML => $html, + # answers => $pg_obj->{answers}, + # debug => { + # perl_warn => $pg_obj->{WARNINGS}, + # pg_warn => $pg_obj->{warning_messages}, + # debug => $pg_obj->{debug_messages}, + # internal => $pg_obj->{internal_debug_messages} + # }, + # problem_result => $pg_obj->{problem_result}, + # problem_state => $pg_obj->{problem_state}, + # flags => $pg_obj->{flags}, + # resources => { + # regex => $pg_obj->{pgResources}, + # alias => $pg_obj->{resources}, + # js => $pg_obj->{js}, + # css => $pg_obj->{css}, + # }, + # form_data => $inputs_ref, + # raw_metadata_text => $pg_obj->{raw_metadata_text}, + # JWT => { + # problem => $inputs_ref->{problemJWT}, + # session => $pg_obj->{sessionJWT}, + # answer => $pg_obj->{answerJWT} + # }, + # }; + + # havoc caused by problemRandomize.pl inserting CODE ref into pg->{flags} + # HACK: remove flags->{problemRandomize} if it exists -- cannot include CODE refs + delete $return_object->{flags}{problemRandomize} + if $return_object->{flags}{problemRandomize}; + # similar things happen with compoundProblem -- delete CODE refs + delete $return_object->{flags}{compoundProblem}{grader} + if $return_object->{flags}{compoundProblem}{grader}; + + + $return_object->{tags} = WeBWorK::Utils::Tags->new($inputs_ref->{sourceFilePath}, $problem->source) if ( $inputs_ref->{includeTags} ); + $return_object->{inputs_ref} = $inputs_ref; + my $coder = JSON::XS->new->ascii->pretty->allow_unknown->convert_blessed; + my $json = $coder->encode($return_object); + return $json; +} + +####################################################################### +# Process Problem +####################################################################### + +sub process_problem { + my $problem = shift; + my $inputs_ref = shift; + my $source = $problem->{problem_contents}; + my $file_path = $inputs_ref->{sourceFilePath}; + + $inputs_ref->{problemUUID} = md5_hex(Encode::encode_utf8($source)); + + # external dependencies on pg content is not recorded by PGalias + # record the dependency separately -- TODO: incorporate into PG.pl or PGcore? + my @pgResources; + while ($source =~ m/includePG(?:problem|file)\(["'](.*)["']\);/g ) + { + warn "PG asset reference found: $1\n" if $UNIT_TESTS_ON; + push @pgResources, $1; + } + + ################################################## + # Process the pg file + ################################################## + our ( $return_object, $error_flag, $error_string ); + $error_flag = 0; + $error_string = ''; + + # can include @args as third input below + $return_object = standaloneRenderer( \$source, $inputs_ref ); + + # stash assets list in $return_object + $return_object->{pgResources} = \@pgResources; + + # generate sessionJWT to store session data and answerJWT to update grade store + my ($sessionJWT, $answerJWT) = generateJWTs($return_object, $inputs_ref); + $return_object->{sessionJWT} = $sessionJWT // ''; + $return_object->{answerJWT} = $answerJWT // ''; + + ####################################################################### + # Handle errors + ####################################################################### + + print "\n\n Result of renderProblem \n\n" if $UNIT_TESTS_ON; + print pretty_print_rh($return_object) if $UNIT_TESTS_ON; + if ( not defined $return_object ) + { + $error_string = "0\t Could not process $file_path problem file \n"; + } + elsif ( defined( $return_object->{flags}->{error_flag} ) + and $return_object->{flags}->{error_flag} ) + { + $error_string = "0\t $file_path has errors\n"; + } + elsif ( defined( $return_object->{errors} ) and $return_object->{errors} ) { + $error_string = "0\t $file_path has syntax errors\n"; + } + $error_flag = 1 if $return_object->{errors}; + + ####################################################################### + # End processing of the pg file + ####################################################################### + + return $return_object, $error_flag, $error_string; +} + +########################################### +# standalonePGproblemRenderer +########################################### + +sub standaloneRenderer { + my $problemFile = shift // ''; + my $inputs_ref = shift // {}; + my %args = @_; + + # default to 1, explicitly avoid generating answer components + my $processAnswers = $inputs_ref->{processAnswers} // 1; + print "NOT PROCESSING ANSWERS" unless $processAnswers == 1; + + my $pg = WeBWorK::PG->new( + sourceFilePath => $inputs_ref->{sourceFilePath} // '', + r_source => $problemFile, + problemSeed => $inputs_ref->{problemSeed}, + processAnswers => $processAnswers, + showHints => $inputs_ref->{showHints}, # default is to showHint (set in PG.pm) + showSolutions => $inputs_ref->{showSolutions} // $inputs_ref->{isInstructor} ? 1 : 0, + problemNumber => $inputs_ref->{problemNumber}, # ever even relevant? + num_of_correct_ans => $inputs_ref->{numCorrect} || 0, + num_of_incorrect_ans => $inputs_ref->{numIncorrect} || 0, + displayMode => $inputs_ref->{displayMode}, + useMathQuill => !defined $inputs_ref->{entryAssist} || $inputs_ref->{entryAssist} eq 'MathQuill', + answerPrefix => $inputs_ref->{answerPrefix}, + isInstructor => $inputs_ref->{isInstructor}, + forceScaffoldsOpen => $inputs_ref->{forceScaffoldsOpen}, + psvn => $inputs_ref->{psvn}, + problemUUID => $inputs_ref->{problemUUID}, + language => $inputs_ref->{language} // 'en', + language_subroutine => WeBWorK::Localize::getLoc($inputs_ref->{language} // 'en'), + inputs_ref => {%$inputs_ref}, # Copy the inputs ref so the original can be relied on after rendering. + templateDirectory => "$ENV{RENDER_ROOT}/", + htmlURL => 'pg_files/', + tempURL => 'pg_files/tmp/', + debuggingOptions => { + show_resource_info => $inputs_ref->{show_resource_info}, + view_problem_debugging_info => $inputs_ref->{view_problem_debugging_info} // $inputs_ref->{isInstructor}, + show_pg_info => $inputs_ref->{show_pg_info}, + show_answer_hash_info => $inputs_ref->{show_answer_hash_info}, + show_answer_group_info => $inputs_ref->{show_answer_group_info} + } + ); + + # new version of output: + my ( $internal_debug_messages, $pgwarning_messages, $pgdebug_messages ); + if ( ref( $pg->{pgcore} ) ) { + $internal_debug_messages = $pg->{pgcore}->get_internal_debug_messages; + $pgwarning_messages = $pg->{pgcore}->get_warning_messages; + $pgdebug_messages = $pg->{pgcore}->get_debug_messages; + } + else { + $internal_debug_messages = + ['Problem failed during render - no PGcore received.']; + } + + my $out2 = { + text => $pg->{body_text}, + header_text => $pg->{head_text}, + post_header_text => $pg->{post_header_text}, + answers => $pg->{answers}, + errors => $pg->{errors}, + pg_warnings => $pg->{warnings}, + # PG_ANSWERS_HASH => $pg->{pgcore}->{PG_ANSWERS_HASH}, + problem_result => $pg->{result}, + problem_state => $pg->{state}, + flags => $pg->{flags}, + resources => [ keys %{$pg->{pgcore}{PG_alias}{resource_list}} ], + warning_messages => $pgwarning_messages, + debug_messages => $pgdebug_messages, + internal_debug_messages => $internal_debug_messages, + }; + $pg->free; + $out2; +} + +################################################## +# utilities +################################################## + +sub get_current_process_memory { + CORE::state $pt = Proc::ProcessTable->new; + my %info = map { $_->pid => $_ } @{ $pt->table }; + return $info{$$}->rss; +} + +# expects a pg/result_object and a ref to submitted formdata +# generates a sessionJWT and an answerJWT +sub generateJWTs { + my $pg = shift; + my $inputs_ref = shift; + my $sessionHash = {'answersSubmitted' => 1, 'iss' =>$ENV{SITE_HOST}, problemJWT => $inputs_ref->{problemJWT}}; + my $scoreHash = {}; + + # TODO: sometimes student_ans causes JWT corruption in PHP - why? + # proposed restructuring of the answerJWT -- prepare with LibreTexts + # my %studentKeys = qw(student_value value student_formula formula student_ans answer original_student_ans original); + # my %previewKeys = qw(preview_text_string text preview_latex_string latex); + # my %correctKeys = qw(correct_value value correct_formula formula correct_ans ans); + # my %messageKeys = qw(ans_message answer error_message error); + # my @resultKeys = qw(score weight); + my %answers = %{unbless($pg->{answers})}; + + # once the correct answers are shown, this setting is permanent + $sessionHash->{showCorrectAnswers} = 1 if $inputs_ref->{showCorrectAnswers} && !$inputs_ref->{isInstructor}; + + # store the current answer/response state for each entry + foreach my $ans (keys %{$pg->{answers}}) { + # TODO: Anything else we want to add to sessionHash? + $sessionHash->{$ans} = $inputs_ref->{$ans}; + $sessionHash->{ 'previous_' . $ans } = $inputs_ref->{$ans}; + $sessionHash->{ 'MaThQuIlL_' . $ans } = $inputs_ref->{ 'MaThQuIlL_' . $ans } if ($inputs_ref->{ 'MaThQuIlL_' . $ans}); + + # More restructuring -- confirm with LibreTexts + # $scoreHash->{$ans}{student} = { map {exists $answers{$ans}{$_} ? ($studentKeys{$_} => $answers{$ans}{$_}) : ()} keys %studentKeys }; + # $scoreHash->{$ans}{preview} = { map {exists $answers{$ans}{$_} ? ($previewKeys{$_} => $answers{$ans}{$_}) : ()} keys %previewKeys }; + # $scoreHash->{$ans}{correct} = { map {exists $answers{$ans}{$_} ? ($correctKeys{$_} => $answers{$ans}{$_}) : ()} keys %correctKeys }; + # $scoreHash->{$ans}{message} = { map {exists $answers{$ans}{$_} ? ($messageKeys{$_} => $answers{$ans}{$_}) : ()} keys %messageKeys }; + # $scoreHash->{$ans}{result} = { map {exists $answers{$ans}{$_} ? ($_ => $answers{$ans}{$_}) : ()} @resultKeys }; + } + $scoreHash->{answers} = unbless($pg->{answers}); + + # update the number of correct/incorrect submissions if answers were 'submitted' + # but don't update either if the problem was already correct + $sessionHash->{numCorrect} = (defined $inputs_ref->{submitAnswers} && $inputs_ref->{numCorrect} == 0) ? + $pg->{problem_state}{num_of_correct_ans} : ($inputs_ref->{numCorrect} // 0); + $sessionHash->{numIncorrect} = (defined $inputs_ref->{submitAnswers} && $inputs_ref->{numCorrect} == 0) ? + $pg->{problem_state}{num_of_incorrect_ans} : ($inputs_ref->{numIncorrect} // 0); + + # include the final result of the combined scores + $scoreHash->{result} = $pg->{problem_result}{score}; + + # create and return the session JWT + # TODO swap to alg => 'PBES2-HS512+A256KW', enc => 'A256GCM' + my $sessionJWT = encode_jwt(payload => $sessionHash, auto_iat => 1, alg => 'HS256', key => $ENV{webworkJWTsecret}); + + # form answerJWT + my $responseHash = { + iss => $ENV{SITE_HOST}, + aud => $inputs_ref->{JWTanswerURL}, + score => $scoreHash, + # problemJWT => $inputs_ref->{problemJWT}, + sessionJWT => $sessionJWT, + platform => 'standaloneRenderer' + }; + + # Can instead use alg => 'PBES2-HS512+A256KW', enc => 'A256GCM' for JWE + my $answerJWT = encode_jwt(payload=>$responseHash, alg => 'HS256', key => $ENV{problemJWTsecret}, auto_iat => 1); + return ($sessionJWT, $answerJWT); +} + +sub pretty_print_rh { + shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); + my $rh = shift; + my $indent = shift || 0; + my $out = ""; + my $type = ref($rh); + + if ( defined($type) and $type ) { + $out .= " type = $type; "; + } + elsif ( !defined($rh) ) { + $out .= " type = UNDEFINED; "; + } + return $out . " " unless defined($rh); + + if ( ref($rh) =~ /HASH/ ) { + $out .= "{\n"; + $indent++; + foreach my $key ( sort keys %{$rh} ) { + $out .= + " " x $indent + . "$key => " + . pretty_print_rh( $rh->{$key}, $indent ) . "\n"; + } + $indent--; + $out .= "\n" . " " x $indent . "}\n"; + + } + elsif ( ref($rh) =~ /ARRAY/ or "$rh" =~ /ARRAY/ ) { + $out .= " ( "; + foreach my $elem ( @{$rh} ) { + $out .= pretty_print_rh( $elem, $indent ); + + } + $out .= " ) \n"; + } + elsif ( ref($rh) =~ /SCALAR/ ) { + $out .= "scalar reference " . ${$rh}; + } + elsif ( ref($rh) =~ /Base64/ ) { + $out .= "base64 reference " . $$rh; + } + else { + $out .= $rh; + } + + return $out . " "; +} + +sub writeRenderLogEntry($) { + my $message = shift; + + local *LOG; + if ( open LOG, ">>", $path_to_log_file ) { + print LOG "[", time2str( "%a %b %d %H:%M:%S %Y", time ), + "] $message\n"; + close LOG; + } else { + warn "failed to open $path_to_log_file for writing: $!"; + } +} + +1; diff --git a/lib/WeBWorK/Utils.pm b/lib/WeBWorK/Utils.pm new file mode 100644 index 000000000..3665bc6a2 --- /dev/null +++ b/lib/WeBWorK/Utils.pm @@ -0,0 +1,154 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2022 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 WeBWorK::Utils; +use base qw(Exporter); + +use strict; +use warnings; + +use JSON; + +our @EXPORT_OK = qw( + wwRound + getAssetURL +); + +# usage wwRound($places,$float) +# return $float rounded up to number of decimal places given by $places +sub wwRound(@) { + my $places = shift; + my $float = shift; + my $factor = 10**$places; + return int($float * $factor + 0.5) / $factor; +} + +my $staticWWAssets; +my $staticPGAssets; +my $thirdPartyWWDependencies; +my $thirdPartyPGDependencies; + +sub readJSON { + my $fileName = shift; + + return unless -r $fileName; + + open(my $fh, "<:encoding(UTF-8)", $fileName) or die "FATAL: Unable to open '$fileName'!"; + local $/; + my $data = <$fh>; + close $fh; + + return JSON->new->decode($data); +} + +sub getThirdPartyAssetURL { + my ($file, $dependencies, $baseURL, $useCDN) = @_; + + for (keys %$dependencies) { + if ($file =~ /^node_modules\/$_\/(.*)$/) { + if ($useCDN && $1 !~ /mathquill/) { + return + "https://cdn.jsdelivr.net/npm/$_\@" + . substr($dependencies->{$_}, 1) . '/' + . ($1 =~ s/(?:\.min)?\.(js|css)$/.min.$1/gr); + } else { + return Mojo::URL->new("${baseURL}$file")->query(version => $dependencies->{$_} =~ s/#/@/gr); + } + } + } + return; +} + +# Get the url for static assets. +sub getAssetURL { + my ($language, $file) = @_; + + # Load the static files list generated by `npm install` the first time this method is called. + unless ($staticWWAssets) { + my $staticAssetsList = "$ENV{RENDER_ROOT}/public/static-assets.json"; + $staticWWAssets = readJSON($staticAssetsList); + unless ($staticWWAssets) { + warn "ERROR: '$staticAssetsList' not found or not readable!\n" + . "You may need to run 'npm install' from '$ENV{RENDER_ROOT}/public'."; + $staticWWAssets = {}; + } + } + + unless ($staticPGAssets) { + my $staticAssetsList = "$ENV{PG_ROOT}/htdocs/static-assets.json"; + $staticPGAssets = readJSON($staticAssetsList); + unless ($staticPGAssets) { + warn "ERROR: '$staticAssetsList' not found or not readable!\n" + . "You may need to run 'npm install' from '$ENV{PG_ROOT}/htdocs'."; + $staticPGAssets = {}; + } + } + + unless ($thirdPartyWWDependencies) { + my $packageJSON = "$ENV{RENDER_ROOT}/public/package.json"; + my $data = readJSON($packageJSON); + warn "ERROR: '$packageJSON' not found or not readable!\n" unless $data && defined $data->{dependencies}; + $thirdPartyWWDependencies = $data->{dependencies} // {}; + } + + unless ($thirdPartyPGDependencies) { + my $packageJSON = "$ENV{PG_ROOT}/htdocs/package.json"; + my $data = readJSON($packageJSON); + warn "ERROR: '$packageJSON' not found or not readable!\n" unless $data && defined $data->{dependencies}; + $thirdPartyPGDependencies = $data->{dependencies} // {}; + } + + # Check to see if this is a third party asset file in node_modules (either in webwork2/htdocs or pg/htdocs). + # If so, then either serve it from a CDN if requested, or serve it directly with the library version + # appended as a URL parameter. + if ($file =~ /^node_modules/) { + my $wwFile = getThirdPartyAssetURL( + $file, $thirdPartyWWDependencies, + '', + 0 + ); + return $wwFile if $wwFile; + + my $pgFile = + getThirdPartyAssetURL($file, $thirdPartyPGDependencies, 'pg_files/', 1); + return $pgFile if $pgFile; + } + + # If a right-to-left language is enabled (Hebrew or Arabic) and this is a css file that is not a third party asset, + # then determine the rtl varaint file name. This will be looked for first in the asset lists. + my $rtlfile = + ($language =~ /^(he|ar)/ && $file !~ /node_modules/ && $file =~ /\.css$/) + ? $file =~ s/\.css$/.rtl.css/r + : undef; + + # First check to see if this is a file in the webwork htdocs location with a rtl variant. + return "$staticWWAssets->{$rtlfile}" + if defined $rtlfile && defined $staticWWAssets->{$rtlfile}; + + # Next check to see if this is a file in the webwork htdocs location. + return "$staticWWAssets->{$file}" if defined $staticWWAssets->{$file}; + + # Now check to see if this is a file in the pg htdocs location with a rtl variant. + return "pg_files/$staticPGAssets->{$rtlfile}" if defined $rtlfile && defined $staticPGAssets->{$rtlfile}; + + # Next check to see if this is a file in the pg htdocs location. + return "pg_files/$staticPGAssets->{$file}" if defined $staticPGAssets->{$file}; + + # If the file was not found in the lists, then just use the given file and assume its path is relative to the + # render app public folder. + return "$file"; +} + +1; diff --git a/lib/WeBWorK/lib/WeBWorK/Utils/LanguageAndDirection.pm b/lib/WeBWorK/Utils/LanguageAndDirection.pm similarity index 99% rename from lib/WeBWorK/lib/WeBWorK/Utils/LanguageAndDirection.pm rename to lib/WeBWorK/Utils/LanguageAndDirection.pm index 8faf84a15..f0e8da8a1 100644 --- a/lib/WeBWorK/lib/WeBWorK/Utils/LanguageAndDirection.pm +++ b/lib/WeBWorK/Utils/LanguageAndDirection.pm @@ -38,12 +38,8 @@ language. use strict; use warnings; -use Carp; -use WeBWorK::PG; -use WeBWorK::Debug; our @EXPORT = qw(get_lang_and_dir get_problem_lang_and_dir); -our @EXPORT_OK = (); =head1 FUNCTIONS diff --git a/lib/WeBWorK/lib/WeBWorK/Utils/Tags.pm b/lib/WeBWorK/Utils/Tags.pm similarity index 99% rename from lib/WeBWorK/lib/WeBWorK/Utils/Tags.pm rename to lib/WeBWorK/Utils/Tags.pm index 9b1182a60..e19deeb98 100644 --- a/lib/WeBWorK/lib/WeBWorK/Utils/Tags.pm +++ b/lib/WeBWorK/Utils/Tags.pm @@ -249,7 +249,6 @@ sub new { if ($source) { @lines = split "\n", $source; - $name = ""; } else { if ( $name !~ /pg$/ && $name !~ /\.pg\.[-a-zA-Z0-9_.@]*\.tmp$/ ) { warn "Not a pg file"; #print caused trouble with XMLRPC @@ -263,7 +262,7 @@ sub new { } my $lineno = 0; - $self->{file} = "$name"; + $self->{file} = $name; # Initialize some values for my $tagname ( BASIC ) { @@ -526,7 +525,7 @@ sub write { next; } next if istagline($line); - print $fh $line; + print $fh $line unless $lineno < $self->{lasttagline}; } $fh->close(); diff --git a/lib/WeBWorK/conf/defaults.config b/lib/WeBWorK/conf/defaults.config deleted file mode 100644 index faa2ea1a2..000000000 --- a/lib/WeBWorK/conf/defaults.config +++ /dev/null @@ -1,2168 +0,0 @@ -#!perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2022 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. -# All package variables set in this file are added to the course environment. -# If you wish to set a variable here but omit it from the course environment, -# use the "my" keyword. The $webwork_dir variable is set in the WeBWorK Apache -# configuration file (webwork.apache-config) and is available for use here. In -# addition, the $courseName variable holds the name of the current course. - -# YOU SHOULD NOT NEED TO EDIT THIS FILE!! -# All site-specific settings such as file locations and web addresses are -# configured in site.conf. If you want to override any settings in this -# file, you can put a directive in localOverrides.conf. - -include("conf/site.conf"); - -################################################################################ -# site.conf should contain basic information about directories and URLs on -# your server -################################################################################ - -################################################################################ -# Mail Settings -################################################################################ - -# AllowedRecipients defines addresses that the PG system is allowed to send mail -# to. this prevents subtle PG exploits. This should be set in course.conf to the -# addresses of professors of each course. Sending mail from the PG system (i.e. -# questionaires, essay questions) will fail if this is not set somewhere (either -# here or in course.conf). -$mail{allowedRecipients} = [ - #'prof1@yourserver.yourdomain.edu', - #'prof2@yourserver.yourdomain.edu', -]; - -# By default, feedback is sent to all users who have permission to -# receive_feedback. If this list is non-empty, feedback is also sent to the -# addresses specified here. -# -# * If you want to disable feedback altogether, leave this empty and set -# submit_feedback => $nobody in %permissionLevels below. This will cause the -# feedback button to go away as well. -# -# * If you want to send email ONLY to addresses in this list, set -# receive_feedback => $nobody in %permissionLevels below. -# -# It's often useful to set this in the course.conf to change the behavior of -# feedback for a specific course. -# -# Items in this list may be bare addresses, or RFC822 mailboxes, like: -# 'Joe User ' -# The advantage of this form is that the resulting email will include the name -# of the recipient in the "To" field of the email. -# -$mail{feedbackRecipients} = [ - #'prof1@yourserver.yourdomain.edu', - #'prof2@yourserver.yourdomain.edu', -]; - -# Feedback subject line -- the following escape sequences are recognized: -# -# %c = course ID -# %u = user ID -# %s = set ID -# %p = problem ID -# %x = section -# %r = recitation -# %% = literal percent sign -# - -$mail{feedbackSubjectFormat} = "[WWfeedback] course:%c user:%u set:%s prob:%p sec:%x rec:%r"; - -# feedbackVerbosity: -# 0: send only the feedback comment and context link -# 1: as in 0, plus user, set, problem, and PG data -# 2: as in 1, plus the problem environment (debugging data) -$mail{feedbackVerbosity} = 1; - -# Should the studentID be included in the feedback email when feedbackVerbosity > 0: -# The default is yes -$blockStudentIDinFeedback = 0; - -# Defines the size of the Mail Merge editor window -# FIXME: should this be here? it's UI, not mail -# FIXME: replace this with the auto-size method that TWiki uses -$mail{editor_window_rows} = 15; -$mail{editor_window_columns} = 100; - -################################################################################## -# Customizing the action of the "Email your instructor" button -################################################################################## - -# Customizing the behavior for this button is best done by copying the lines below, entering them -# in the localOverrides.conf file and making the desired modifications. -# Then you will not need to update these modifications -# when you download a new version of WeBWorK. - -# Use this to customize the text of the feedback button. -$feedback_button_name = "Email Instructor"; - -# If this value is true, feedback will only be sent to users with the same -# section as the user initiating the feedback. -$feedback_by_section = 0; - -# If the variable below is set to a non-empty value (i.e. in course.conf), WeBWorK's usual -# email feedback mechanism will be replaced with a link to the given URL. -# See also $feedback_button_name, above. - -$courseURLs{feedbackURL} = ""; - -# If the variable below is set to a non-empty value (i.e. in course.conf), -# WeBWorK's usual email feedback mechanism will be replaced with a link to the given URL and -# a POST request with information about the problem including the HTML rendering -# of the problem will be sent to that URL. -# See also $feedback_button_name, above. - -#$courseURLs{feedbackFormURL} = "http://www.mathnerds.com/MathNerds/mmn/SDS/askQuestion.aspx"; #"http://www.tipjar.com/cgi-bin/test"; -$courseURLs{feedbackFormURL} = ""; - -################################################################################ -# Repository Information -################################################################################ -# This is where you put your remote and branch for your WeBWorK, PG and OPL -# github repositories. - -# Note: This process uses git ls-remote which can be very slow on some -# systems. If your course list page in the admin course is very slow -# consider disabling this option. - -$enableGitUpgradeNotifier = 0; - -$gitWeBWorKRemoteName = "origin"; -$gitWeBWorKBranchName = "main"; -$gitPGRemoteName = "origin"; -$gitPGBranchName = "main"; -$gitLibraryRemoteName = "origin"; -$gitLibraryBranchName = "main"; - -################################################################################ -# Theme -################################################################################ - -$defaultTheme = "math4"; -$defaultThemeTemplate = "system"; - -# The institution logo should be an image file in the theme's images folder -$institutionLogo = 'maa_logo.png'; -$institutionURL = 'http://www.maa.org'; -$institutionName = 'MAA (Mathematical Association of America)'; - -################################################################################ -# Hardcopy Theme -################################################################################ - -# Available Hardcopy themes (located in snippets) -# (their "internal" name and their pretty name for end users) -$hardcopyThemeNames = { - oneColumn => 'One Column', - twoColumn => 'Two Columns', - #XeLaTeX-oneColumn => 'English - one Column', - #XeLaTeX-twoColumn => 'English - two Columns', - #XeLaTeX-Hebrew-oneColumn => 'Hebrew/English - one Column', - #XeLaTeX-Hebrew-twoColumn => 'Hebrew/English - two Columns', -}; - -# This is needed to enforce an order that the options are presented when making a hardcopy -$hardcopyThemes = [ - 'oneColumn', - 'twoColumn', - #'XeLaTeX-oneColumn', - #'XeLaTeX-twoColumn', - #'XeLaTeX-Hebrew-oneColumn', - #'XeLaTeX-Hebrew-twoColumn', -]; - -# Default Hardcopy theme -$hardcopyTheme = "twoColumn"; - -################################################################################ -# Achievements -################################################################################ -$achievementsEnabled = 0; -$achievementItemsEnabled = 0; -$achievementPointsPerProblem = 5; -$achievementPreambleFile = "preamble.at"; - -################################################################################ -# Achievements -################################################################################ -$showCourseHomeworkTotals = 1; - -################################################################################ -# Editor -################################################################################ -$editor{author} = ''; -$editor{authorInstitute} = ''; -$editor{textTitle} = ''; -$editor{textEdition} = ''; -$editor{textAuthor} = ''; - -################################################################################ -# showMeAnother -################################################################################ -# switch to enable showMeAnother button -$pg{options}{enableShowMeAnother} = 0; -# default number of attempts before enabling show me another -$pg{options}{showMeAnotherDefault} = -1; -# maximum number of times (per problem) the button can be used -$pg{options}{showMeAnotherMaxReps} = 3; -# maximum number of attempts to check if changing the problem seed -# changes the problem -$pg{options}{showMeAnotherGeneratesDifferentProblem} = 5; -# showMeAnother options -$pg{options}{showMeAnother}=[ -"SMAcheckAnswers", -"SMAshowSolutions", -"SMAshowCorrect", -"SMAshowHints", -]; - -############################################################################### -# periodicRandomization -################################################################################ -# switch to enable periodic re-randomization -$pg{options}{enablePeriodicRandomization} = 0; -# course-wide default period for re-randomization, should be an integer -# the value of 0 disables re-randomization -$pg{options}{periodicRandomizationPeriod} = 5; -# Show the correct answer after a student's last attempt at the current version -# and before a new version is requested. -$pg{options}{showCorrectOnRandomize} = 0; - -################################################################################ -# Waive Explanations -################################################################################ -# switch to remove explanation essay block from questions that have one -$pg{specialPGEnvironmentVars}{waiveExplanations} = 0; - -################################################################################ -# Language -################################################################################ - -$language = "en"; # tr = turkish, en=english - -# $perProblemLangAndDirSettingMode controls how and whether LANG and/or DIR -# attributes are added to the DIV element enveloping a problem -# which helps handle proper display of problems with a text direction -# different from that used course-wide. Ex: enables English problems to -# be displayed properly in a Hebrew course site, which helps select problems -# to be translated to Hebrew. -$perProblemLangAndDirSettingMode = "force::ltr"; - -################################################################################ -# System-wide locations (directories and URLs) -################################################################################ - -# The root directory, set by webwork_root variable in Apache configuration. -$webworkDirs{root} = "$webwork_dir"; - -# Location of system-wide data files. -$webworkDirs{DATA} = "$webworkDirs{root}/DATA"; - -# Used for temporary storage of uploaded files. -$webworkDirs{uploadCache} = "$webworkDirs{DATA}/uploads"; - -# Location of utility programs. -$webworkDirs{bin} = "$webworkDirs{root}/bin"; - -# Location of configuration files, templates, snippets, etc. -$webworkDirs{conf} = "$webworkDirs{root}/conf"; - -# Location of course directories. -$webworkDirs{courses} = "$webwork_courses_dir" || "$webworkDirs{root}/courses"; - -# Contains log files. -$webworkDirs{logs} = "$ENV{RENDER_ROOT}/logs"; - -# Contains non-web-accessible temporary files, such as TeX working directories. -$webworkDirs{tmp} = "$ENV{RENDER_ROOT}/tmp"; - -# The (absolute) destinations of symbolic links that are OK for the FileManager to follow. -# (any subdirectory of these is a valid target for a symbolic link.) -# For example: -# $webworkDirs{valid_symlinks} = ["$webworkDirs{courses}/modelCourse/templates","/ww2/common/sets"]; -$webworkDirs{valid_symlinks} = []; - -# Location of the common tex input files packages.tex, CAPA.tex, and PGML.tex -# used for hardcopy generation. -$webworkDirs{texinputs_common} = "$webworkDirs{root}/conf/snippets/hardcopyThemes/common"; - -################################################################################ -##### The following locations are web-accessible. -################################################################################ - -# The root URL (usually /webwork2), set by in Apache configuration. -$webworkURLs{root} = "$webwork_url"; - -# Location of system-wide web-accessible files, such as equation images, and -# help files. -$webworkDirs{htdocs} = "$webwork_htdocs_dir" || "$ENV{RENDER_ROOT}/tmp"; -$webworkURLs{htdocs} = "$webwork_htdocs_url"; - -# Location of web-accessible temporary files, such as equation images. -# These two should be set in localOverrides.conf -- not here since this can be overwritten by new versions. -$webworkDirs{htdocs_temp} = "$ENV{RENDER_ROOT}/tmp"; -$webworkURLs{htdocs_temp} = "$webworkURLs{htdocs}/tmp"; - -# Location of cached equation images. -$webworkDirs{equationCache} = "$webworkDirs{htdocs_temp}/equations"; -$webworkURLs{equationCache} = "$webworkURLs{htdocs_temp}/equations"; - -# Contains context-sensitive help files. -$webworkDirs{local_help} = "$webworkDirs{htdocs}/helpFiles"; -$webworkURLs{local_help} = "$webworkURLs{htdocs}/helpFiles"; - -# Location of theme templates. -$webworkDirs{themes} = "$webworkDirs{htdocs}/themes"; - -# Location of localization directory. -$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"; - -# URL of WeBWorK on GitHub -$webworkURLs{GitHub} = "https://github.com/openwebwork"; - -# Registration form URL, security signup mail address -$webworkURLs{serverRegForm} = "https://forms.gle/vrqpKiRHritnQNf37"; -$webworkURLs{wwSecurityAnnounce} = "https://groups.google.com/g/ww-security-announce"; -$webworkSecListManagers = 'ww-security-announce+managers@googlegroups.com'; - -# URLs in the Wiki -$webworkURLs{WikiMain} = "https://webwork.maa.org/wiki/"; -$webworkURLs{SiteMap} = "https://webwork.maa.org/wiki/WeBWorK_Sites"; - -#URL for help buttons on the PGProblemEditor pages: -$webworkURLs{problemTechniquesHelpURL}='https://webwork.maa.org/wiki/Category:Problem_Techniques'; -$webworkURLs{MathObjectsHelpURL} ='https://webwork.maa.org/wiki/Category:MathObjects'; -$webworkURLs{PODHelpURL} ='https://webwork.maa.org/pod/'; -$webworkURLs{PGLabHelpURL} ='https://demo.webwork.rochester.edu/webwork2/wikiExamples/MathObjectsLabs2/2?login_practice_user=true'; -$webworkURLs{PGMLHelpURL} ='https://demo.webwork.rochester.edu/webwork2/cervone_course/PGML/1?login_practice_user=true'; -$webworkURLs{AuthorHelpURL} ='https://webwork.maa.org/wiki/Category:Authors'; - -# Location of CSS -# $webworkURLs{stylesheet} = "$webworkURLs{htdocs}/css/${defaultTheme}.css"; -# this is never used -- changing the theme from the config panel -# doesn't appear to reset the theme in time? -# It's better to refer directly to the .css file in the system.template -# /css/math.css"/> - -################################################################################ -# Defaults for course-specific locations (directories and URLs) -################################################################################ - -# The root directory of the current course. (The ID of the current course is -# available in $courseName.) -$courseDirs{root} = "$webworkDirs{courses}/$courseName"; - -# Location of course-specific data files. -$courseDirs{DATA} = "$courseDirs{root}/DATA"; - -# Location of course HTML files, passed to PG. -$courseDirs{html} = "$courseDirs{root}/html"; -$courseURLs{html} = "$webwork_courses_url/$courseName"; - -# Location of course image files, passed to PG. -$courseDirs{html_images} = "$courseDirs{html}/images"; - -# Location of web-accessible, course-specific temporary files, like static and -# dynamically-generated PG graphics. -$courseDirs{html_temp} = "$webworkDirs{htdocs_temp}/$courseName"; -$courseURLs{html_temp} = "$webworkURLs{htdocs_temp}/$courseName"; - -# Location of course-specific logs, like the transaction log. -$courseDirs{logs} = "$courseDirs{root}/logs"; - -# Location of scoring files. -$courseDirs{scoring} = "$courseDirs{root}/scoring"; - -# Location of PG templates and set definition files. -$courseDirs{templates} = $ENV{RENDER_ROOT}; - -# Location of course achievement files. -$courseDirs{achievements} = "$courseDirs{templates}/achievements"; -$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"; - -# Location of mail-merge templates. -$courseDirs{email} = "$courseDirs{templates}/email"; - -# Location of temporary editing files. -$courseDirs{tmpEditFileDir} = "$courseDirs{templates}/tmpEdit"; - -# mail merge status directory -$courseDirs{mailmerge} = "$courseDirs{DATA}/mailmerge"; - -################################################################################ -# System-wide files -################################################################################ - -# Location of this file. -$webworkFiles{environment} = "$webworkDirs{conf}/defaults.conf"; - -# Flat-file database used to protect against MD5 hash collisions. TeX equations -# are hashed to determine the name of the image file. There is a tiny chance of -# a collision between two TeX strings. This file allows for that. However, this -# is slow, so most people chose not to worry about it. Set this to "" if you -# don't want to use the equation cache file. -$webworkFiles{equationCacheDB} = ""; # "$webworkDirs{DATA}/equationcache"; - -################################################################################ -# Hardcopy snippets are used in constructing a TeX file for hardcopy output. -# They should contain TeX code unless otherwise noted. -################################################################################ - -# The setHeader precedes each set in hardcopy output. It is a PG file. -# This is the default file which is used if a specific files is not selected -$webworkFiles{hardcopySnippets}{setHeader} = "$webworkDirs{conf}/snippets/ASimpleCombinedHeaderFile.pg"; - -################################################################################ -# In general the following files are determined by which hardcopy theme you -# are using however you can set these variables to override the theme settings. -# Note: These overrides are perminant across all themes. -################################################################################ - -# The preamble is the first thing in the TeX file. -#$webworkFiles{hardcopySnippets}{preamble} = "$webworkDirs{conf}/snippets/hardcopyPreamble.tex"; - -# The problem divider goes between problems. -#$webworkFiles{hardcopySnippets}{problemDivider} = "$webworkDirs{conf}/snippets/hardcopyProblemDivider.tex"; - -# The set footer goes after each set. Is is a PG file. -#$webworkFiles{hardcopySnippets}{setFooter} = "$webworkDirs{conf}/snippets/hardcopySetFooter.pg"; - -# The set divider goes between sets (in multiset output). -#$webworkFiles{hardcopySnippets}{setDivider} = "$webworkDirs{conf}/snippets/hardcopySetDivider.tex"; - -# The user divider does between users (in multiuser output). -#$webworkFiles{hardcopySnippets}{userDivider} = "$webworkDirs{conf}/snippets/hardcopyUserDivider.tex"; - -# The postamble is the last thing in the TeX file. -#$webworkFiles{hardcopySnippets}{postamble} = "$webworkDirs{conf}/snippets/hardcopyPostamble.tex"; - -################################################################################ -##### Screen snippets are used when displaying problem sets on the screen. -################################################################################ - -# The set header is displayed on the problem set page. It is a PG file. -# This is the default file which is used if a specific files is not selected -$webworkFiles{screenSnippets}{setHeader} = "$webworkDirs{conf}/snippets/ASimpleCombinedHeaderFile.pg"; - -# A PG template for creation of new problems. -$webworkFiles{screenSnippets}{blankProblem} = "$webworkDirs{conf}/snippets/blankProblem2.pg"; # screenSetHeader.pg" - -# A site info "message of the day" file -$webworkFiles{site_info} = "$webworkDirs{htdocs}/site_info.txt"; - -################################################################################ -# Course-specific files -################################################################################ - -# The course configuration file. -$courseFiles{environment} = "$courseDirs{root}/course.conf"; - -# The course simple configuration file (holds web-based configuration). -$courseFiles{simpleConfig} = "$courseDirs{root}/simple.conf"; - -# File contents are displayed after login, on the problem sets page. Path given -# here is relative to the templates directory. -$courseFiles{course_info} = "course_info.txt"; - -# File contents are displayed on the login page. Path given here is relative to -# the templates directory. -$courseFiles{login_info} = "login_info.txt"; - -# These course specific files cannot be edited from the File Manager for safety reasons except -# by an administrator. These are paths relative to the course directory. -$uneditableCourseFiles = [ - 'simple.conf', - 'course.conf', -]; - -# Additional library buttons can be added to the Library Browser (SetMaker.pm) -# by adding the libraries you want to the following line. For each key=>value -# in the list, if a directory (or link to a directory) with name 'key' appears -# in the templates directory, then a button with name 'value' will be placed at -# the top of the problem browser. (No button will appear if there is no -# directory or link with the given name in the templates directory.) For -# example, -# -# $courseFiles{problibs} = {rochester => "Rochester", asu => "ASU"}; -# -# would add two buttons, one for the Rochester library and one for the ASU -# library, provided templates/rochester and templates/asu exists either as -# subdirectories or links to other directories. The "OPL Directory" button -# activated below gives access to all the directories in the National -# Problem Library. -# -$courseFiles{problibs} = { - Library => "OPL Directory", - Contrib => "Contrib", # remember to create link from Contrib to Contrib - # library directory - capaLibrary => "CAPA", # remember to create link from capaLibrary to CAPA - # in contrib -}; - -################################################################################ -# Status system -################################################################################ - -# This is the default status given to new students and students with invalid -# or missing statuses. -$default_status = "Enrolled"; - -# The first abbreviation in the abbreviations list is the canonical -# abbreviation, and will be used when setting the status value in a user record -# or an exported classlist file. -# -# Results are undefined if more than one status has the same abbreviation. -# -# The four behaviors that are controlled by status are: -# allow_course_access => is this user allowed to log in? -# include_in_assignment => is this user included when assigning as set to "all" users? -# include_in_stats => is this user included in statistical reports? -# include_in_email => is this user included in emails sent to the class? -# include_in_scoring => is this user included in score reports? - -%statuses = ( - Enrolled => { - abbrevs => [qw/ C c current enrolled /], - behaviors => [qw/ allow_course_access include_in_assignment include_in_stats include_in_email include_in_scoring /], - }, - Audit => { - abbrevs => [qw/ A a audit /], - behaviors => [qw/ allow_course_access include_in_assignment include_in_stats include_in_email /], - }, - Drop => { - abbrevs => [qw/ D d drop withdraw /], - behaviors => [qw/ /], - }, - Proctor => { - abbrevs => [qw/ P p proctor /], - behaviors => [qw/ /], - }, -); - -################################################################################ -# 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} = "$ENV{RENDER_ROOT}/webwork-open-problem-library/OpenProblemLibrary"; -$contribLibrary{root} = "$ENV{RENDER_ROOT}/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; - -################################################################################ -# Logs -################################################################################ - - -# Logs data about how long it takes to process problems. (Do not confuse this -# with the /other/ timing log which can be set by WeBWorK::Timing and is used -# for benchmarking system performance in general. At some point, this timing -# mechanism will be deprecated in favor of the WeBWorK::Timing mechanism.) -$webworkFiles{logs}{timing} = "$webworkDirs{logs}/timing.log"; - -# Logs data about how long it takes to process problems. (Do not confuse this -# with the /other/ timing log which can be set by WeBWorK::Timing and is used -# for benchmarking system performance in general. At some point, this timing -# mechanism will be deprecated in favor of the WeBWorK::Timing mechanism.) -$webworkFiles{logs}{render_timing} = "$webworkDirs{logs}/render_timing.log"; - -# Logs courses created via the web-based Course Administration module. -$webworkFiles{logs}{hosted_courses} = "$webworkDirs{logs}/hosted_courses.log"; - -# The transaction log contains data from each recorded answer submission. This -# is useful if the database becomes corrupted. -$webworkFiles{logs}{transaction} = "$webworkDirs{logs}/${courseName}_transaction.log"; - -# The answer log stores a history of all users' submitted answers. -$courseFiles{logs}{answer_log} = "$courseDirs{logs}/answer.log"; - -# Log logins. -$courseFiles{logs}{login_log} = "$courseDirs{logs}/login.log"; - -# Log for almost every click. By default it is the empty string, which -# turns this log off. If you want it turned on, we suggest -# "$courseDirs{logs}/activity.log" -# When turned on, this log can get quite large. -$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"; - -# Provide a list of model courses which are not real courses, but from which -# the templates for a new course can be copied. -$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", -}; - -# Select the authentication module to use for proctor logins. -# -# A string or a hash is accepted, as above. -# -$authen{proctor_module} = "WeBWorK::Authen::Proctor"; -$authen{xmlrpc_module} = "WeBWorK::Authen::XMLRPC"; -################################################################################ -# Authorization system (Make local overrides in localOverrides.conf ) -################################################################################ - -# this section lets you define which groups of users can perform which actions. - -# this hash maps a numeric permission level to the name of a role. the number -# assigned to a role is significant -- roles with higher numbers are considered -# "more privileged", and are included when that role is listed for a privilege -# below. -# -%userRoles = ( - guest => -5, - student => 0, - login_proctor => 2, - grade_proctor => 3, - ta => 5, - professor => 10, - admin => 20, - nobody => 99999999, # insure that nobody comes at the end -); - -# this hash maps operations to the roles that are allowed to perform those -# operations. the role listed and any role with a higher permission level (in -# the %userRoles hash) will be allowed to perform the operation. If the role -# is undefined, no users will be allowed to perform the operation. -# -%permissionLevels = ( - login => "guest", - navigation_allowed => "guest", - report_bugs => "ta", - submit_feedback => "student", - change_password => "student", - change_email_address => "student", - change_pg_display_settings => "student", - - proctor_quiz_login => "login_proctor", - proctor_quiz_grade => "grade_proctor", - view_proctored_tests => "student", - view_hidden_work => "ta", - - view_multiple_sets => "ta", - view_unopened_sets => "ta", - view_hidden_sets => "ta", - view_answers => "ta", - view_ip_restricted_sets => "ta", - - become_student => "professor", - access_instructor_tools => "ta", - score_sets => "professor", - send_mail => "professor", - receive_feedback => "ta", - - create_and_delete_problem_sets => "professor", - assign_problem_sets => "professor", - modify_problem_sets => "professor", - modify_student_data => "professor", - modify_classlist_files => "professor", - modify_set_def_files => "professor", - modify_scoring_files => "professor", - modify_problem_template_files => "professor", - manage_course_files => "professor", - edit_achievements => "professor", - create_and_delete_courses => "professor", - fix_course_databases => "professor", - modify_tags => "admin", - edit_restricted_files => "admin", - - ##### Behavior of the interactive problem processor ##### - show_resource_info => "admin", - show_correct_answers_before_answer_date => "ta", - show_solutions_before_answer_date => "ta", - avoid_recording_answers => "nobody", # record the grade/status/state of everyone's entries. - # Setting this to "ta" would record grade/status/state - # for students but not TA's or professors; - # TA's and above could avoid having their answers recorded. - - # controls if old answers can be shown - can_show_old_answers => "student", - check_answers_before_open_date => "ta", - check_answers_after_open_date_with_attempts => "guest", - check_answers_after_open_date_without_attempts => "guest", - check_answers_after_due_date => "guest", - check_answers_after_answer_date => "guest", - can_check_and_submit_answers => "ta", - can_use_show_me_another_early => "ta", - create_new_set_version_when_acting_as_student => undef, - print_path_to_problem => "professor", # see "Special" PG environment variables - always_show_hint => "professor", # see "Special" PG environment variables - always_show_solution => "professor", # see "Special" PG environment variables - record_set_version_answers_when_acting_as_student => undef, - record_answers_when_acting_as_student => undef, - # "record_answers_when_acting_as_student" takes precedence - # over the following for professors acting as students: - record_answers_before_open_date => undef, - record_answers_after_open_date_with_attempts => "student", - record_answers_after_open_date_without_attempts => undef, - record_answers_after_due_date => undef, - record_answers_after_answer_date => undef, - dont_log_past_answers => undef, - # controls logging of the responses to a question - # in the past answer data base - # and in the myCourse/logs/answer_log file. - # Activities of users with this permission enabled are not entered - # in these logs. This might be used when collecting student data - # to avoid contaminating the data with TA and instructor activities. - # The professor setting means that professor's answers are not logged or - # saved in the past answer database. - view_problem_debugging_info => "ta", - show_pg_info_checkbox => "admin", - show_answer_hash_info_checkbox => "admin", - show_answer_group_info_checkbox => "admin", - ##### Behavior of the Hardcopy Processor ##### - - download_hardcopy_multiuser => "ta", - download_hardcopy_multiset => "ta", - download_hardcopy_view_errors =>"professor", - download_hardcopy_format_pdf => "guest", - download_hardcopy_format_tex => "ta", - download_hardcopy_change_theme=> "ta", -); - -# This is the default permission level given to new students and students with -# invalid or missing permission levels. -$default_permission_level = $userRoles{student}; - -################################################################################ -# Session options -################################################################################ - -# $sessionKeyTimeout defines seconds of inactivity before a key expires -$sessionKeyTimeout = 60*30; - -# $sessionKeyLength defines the length (in characters) of the session key -$sessionKeyLength = 32; - -# @sessionKeyChars lists the legal session key characters -@sessionKeyChars = ('A'..'Z', 'a'..'z', '0'..'9'); - -# Practice users are users who's names start with $practiceUser -# (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; - -################################################################################ -# 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, LTIBasic, 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_via = "session_cookie"; -#$session_management_via = "key"; - -################################################################################ -# WeBWorK Caliper -################################################################################ - -# Caliper is disabled by default. See localOverrides.conf.dist for configuration -# options when enabling Caliper. -$caliper{enabled} = 0; - -################################################################################ -# 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 - - -# 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. - -$CookieSameSite = "Lax"; - -# Set the value of the secure cookie attribute: -$CookieSecure = 0; # Default is 0 here, as 1 will not work without https - -# At present the CookieLifeTime setting only effect how long the -# browser is to supposed to retain the cookie. -# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie -$CookieLifeTime = "+7d"; - -# NOTE: In general the cookie lifespan settings use the CGI.pm relative time settings. -# Search for "30 seconds from now" at https://metacpan.org/pod/CGI to see the various options. -# A WW option is to set it to "session", in which case the Cookie will expire when the -# browser session ends (a "session cookie"). - -################################################################################ -# PG subsystem options -################################################################################ - -# 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. -$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 - "images", # display math expressions as images generated by dvipng -# "plainText", # display raw TeX for math expressions -]; - - -########################################################################################## -#### Default settings for homework editor pages -########################################################################################## - -# 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. -$options{setDefSearchDepth} = 0; - -# 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} = 0; -$options{useContribDefFiles} = 0; - - -########################################################################################## -#### Default settings for the problem grader page -########################################################################################## - -# gap betweens cores in score dropdown - e.g. 5 gives 5, 10, 15, 20 -$options{problemGraderScoreDelta} = 5; - -########################################################################################## -#### Default settings for the problem editor pages -########################################################################################## - -# This sets if the PG editor should use a js based "codemirror" editor or -# just a textarea -$options{PGCodeMirror} = 1; - -# This sets if mathview is available on the PG editor for use as a minimal latex equation editor -$options{PGMathView} = 0; -# This sets if WirisEditor is available on the PG editor for use as a minimal latex equation editor -$options{PGWirisEditor}= 0; -# This sets if MathQuill is available on the PG editor for use as a minimal latex equation editor -$options{PGMathQuill}= 0; - - -########################################################################################### -#### Default settings for the PG translator -#### This section controls the display of equations, HINTS, answers, SOLUTIONS, -#### "sticky_answers" a.k.a. showOldAnswers, -#### coloring of answer blanks and the numeric display of entered answers. -########################################################################################### - -# Default display mode. Should be listed above (uncomment only one). -$pg{options}{displayMode} = "MathJax"; - -# The default grader to use, if a problem doesn't specify. -$pg{options}{grader} = "avg_problem_grader"; - -# Fill in answer blanks with the student's last answer by default? -$pg{options}{showOldAnswers} = 1; - -# Default for showing the MathView preview system. To completely disable MathView you need to change the PG special environment variable. -$pg{options}{useMathView} = 1; -# This is the operations file to use for mathview, each contains a different locale. -$pg{options}{mathViewLocale} = "mv_locale_us.js"; - -# Default for showing the WirisEditor preview system. To completely disable WirisEditor you need to change the PG special environment variable. -$pg{options}{useWirisEditor} = 1; - - -# Default for showing the MathQuill preview system. To completely disable MathQuill you need to change the PG special environment variable. -$pg{options}{useMathQuill} = 1; - -# Show correct answers (when allowed) by default? -$pg{options}{showCorrectAnswers} = 0; # this is a backup value use when nothing else has been set. I can think of no case where it should anything but zero. - -# Customize hints and solutions -# Show hints (when allowed) by default? -$pg{options}{showHints} = 0; # this is a backup value use when nothing else has been set. I can think of no case where it should anything but zero. - - -# Show solutions (when allowed) by default? -$pg{options}{showSolutions} = 0; # this is a backup value use when nothing else has been set. I can think of no case where it should anything but zero. -$pg{options}{showAnsGroupInfo} = 0; -$pg{options}{showAnsHashInfo} = 0; -$pg{options}{showPGInfo} = 0; - -# By default hints and solutions are ALWAYS shown for professors (and above) so that they -# can check hints and solutions easily and make corrections. -# To disable this feature set the permission levels -# always_show_hint to "nobody" (by default this is "professor") -# and always_show_solution to "nobody" (by default this is "professor") -# This is done in the %permissions section above. - -# If always_show_hint is set to "nobody" then hints are shown, even to professors, only after -# a certain number of submissions have occurred. This number is set in each problem with -# the variable $main::showHints - - -# Use knowls for hints -$pg{options}{use_knowls_for_hints} = 1; - -# Use knowls for solutions -$pg{options}{use_knowls_for_solutions} = 1; - -# The buttons below are active only if knowls are being used. If set to 1 then the hints (and solutions) -# checkboxes are shown and when these are checked and the problem resubmitted THEN the knowls outline -# appears. I can't immediately think of a useful case where these should be set to 1. If knowls are not being -# used then these checkboxes are ALWAYS shown when a hint or solution is available and the value -# of these two options is ignored. - -# Show solution checkbox -$pg{options}{show_solution_checkbox} = 0; - -# Show hint checkbox -$pg{options}{show_hint_checkbox} = 0; - -# 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 0, e.g. to save space in the response area, the student can still see their evaluated answer by hovering -# the mouse pointer over the typeset version of their answer -$pg{options}{showEvaluatedAnswers} = 1; - -# Catch translation warnings internally by default? (We no longer need to do -# this, since there is a global warnings handler. So this should be off.) -$pg{options}{catchWarnings} = 0; - -# 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 - -##### Currently-selected renderer - -# Only the local renderer is supported in this version. -$pg{renderer} = "WeBWorK::PG::Local"; - -# The remote renderer connects to an XML-RPC PG rendering server. -#$pg{renderer} = "WeBWorK::PG::Remote"; - -##### Renderer-dependent options - -# The remote renderer has one option: -$pg{renderers}{"WeBWorK::PG::Remote"} = { - # The "proxy" server to connect to for remote rendering. - proxy => "http://localhost:21000/RenderD", -}; - -##### Settings for various display modes - -# "images" mode has several settings: -$pg{displayModeOptions}{images} = { - # Determines the method used to align images in output. Can be - # "baseline", "absmiddle", or "mysql". - dvipng_align => 'mysql', - - # If mysql is chosen, this information indicates which database contains the - # 'depths' table. Since 2.3.0, the depths table is kept in the main webwork - # database. (If you are upgrading from an earlier version of webwork, and - # used the mysql method in the past, you should move your existing 'depths' - # table to the main database.) - dvipng_depth_db => { - dbsource => $database_dsn, - user => $database_username, - passwd => $database_password, - }, -}; - -##### Directories used by PG - -# The root of the PG directory tree (from pg_root in Apache config). -$pg{directories}{root} = "$ENV{RENDER_ROOT}/lib/PG"; # "$pg_dir"; -$pg{directories}{lib} = "$pg{directories}{root}/lib"; -$pg{directories}{macros} = "$pg{directories}{root}/macros"; - -# -# The macro file search path. Each directory in this list is seached -# (in this order) by loadMacros() when it looks for a .pl file. -# -$pg{directories}{macrosPath} = [ - ".", # search the problem file's directory - #$courseDirs{macros}, - $pg{directories}{macros}, - "$problemLibrary{root}/macros/Alfred", - "$problemLibrary{root}/macros/BrockPhysics", - "$problemLibrary{root}/macros/CollegeOfIdaho", - "$problemLibrary{root}/macros/Dartmouth", - "$problemLibrary{root}/macros/FortLewis", - "$problemLibrary{root}/macros/Hope", - "$problemLibrary{root}/macros/LaTech", - "$problemLibrary{root}/macros/MC", - "$problemLibrary{root}/macros/Michigan", - "$problemLibrary{root}/macros/Mizzou", - "$problemLibrary{root}/macros/NAU", - "$problemLibrary{root}/macros/PCC", - "$problemLibrary{root}/macros/TCNJ", - "$problemLibrary{root}/macros/UBC", - "$problemLibrary{root}/macros/UMass-Amherst", - "$problemLibrary{root}/macros/UW-Stout", - "$problemLibrary{root}/macros/UniSiegen", - "$problemLibrary{root}/macros/Union", - "$problemLibrary{root}/macros/WHFreeman", - "$problemLibrary{root}/macros/Wiley", -]; - -# The applet search path. If a full URL is given, it is used unmodified. If an -# absolute path is given, the URL of the local server is prepended to it. -# -# For example, if an item is "/math/applets", -# and the local server is "https://math.yourschool.edu", -# then the URL "https://math.yourschool.edu/math/applets" will be used. -# - -$pg{directories}{appletPath} = [ # paths to search for applets (requires full url) - "$webworkURLs{htdocs}/applets", - "$webworkURLs{htdocs}/applets/geogebra_stable", - "$courseURLs{html}/applets", - "$webworkURLs{htdocs}/applets/Xgraph", - "$webworkURLs{htdocs}/applets/PointGraph", - "$webworkURLs{htdocs}/applets/Xgraph", - "$webworkURLs{htdocs}/applets/liveJar", - "$webworkURLs{htdocs}/applets/Image_and_Cursor_All", -]; - - -$pg{directories}{htmlPath} = [ # paths to search for auxiliary html files (requires full url) - ".", - "$courseURLs{html}", - "$webworkURLs{htdocs}", -]; -$pg{directories}{imagesPath} = [ # paths to search for image files (requires full url) - ".", - "$courseURLs{html}/images", - "$webworkURLs{htdocs}/images", -]; -$pg{directories}{pdfPath} = [ # paths to search for pdf files (requires full url) - ".", - "$courseURLs{html}/pdf", - "$webworkURLs{htdocs}/pdf", -]; -##### "Special" PG environment variables. (Stuff that doesn't fit in anywhere else.) - -# Users for whom to print the file name of the PG file being processed. -$pg{specialPGEnvironmentVars}{PRINT_FILE_NAMES_FOR} = [ "professor", ]; - # ie file paths are printed for 'gage' -$pg{specialPGEnvironmentVars}{PRINT_FILE_NAMES_PERMISSION_LEVEL} = - $userRoles{ $permissionLevels{print_path_to_problem} }; - # (file paths are also printed for anyone with this permission or higher) -$pg{specialPGEnvironmentVars}{ALWAYS_SHOW_HINT_PERMISSION_LEVEL} = - $userRoles{ $permissionLevels{always_show_hint} }; - # (hints are automatically shown to anyone with this permission or higher) -$pg{specialPGEnvironmentVars}{ALWAYS_SHOW_SOLUTION_PERMISSION_LEVEL} = - $userRoles{ $permissionLevels{always_show_solution} }; - # (solutions are automatically shown to anyone with this permission or higher) -$pg{specialPGEnvironmentVars}{VIEW_PROBLEM_DEBUGGING_INFO} = - $userRoles{ $permissionLevels{view_problem_debugging_info} }; - # (variable whether to show the debugging info from a problem to a student) - -$pg{specialPGEnvironmentVars}{use_knowls_for_hints} = $pg{options}{use_knowls_for_hints}; -$pg{specialPGEnvironmentVars}{use_knowls_for_solutions} = $pg{options}{use_knowls_for_solutions}; - -# whether to use javascript for rendering Live3D graphs -$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 'dvipdfm'. -$pg{specialPGEnvironmentVars}{latexImageSVGMethod} = "pdf2svg"; - -# 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. -# convert will be called as: -# convert file.ext1 file.ext2 -$pg{specialPGEnvironmentVars}{latexImageConvertOptions} = {input => {density => 300}, output => {quality => 100}}; - - # set the flags immediately above in the $pg{options} section above -- not here. - -# Locations of CAPA resources. (Only necessary if you need to use converted CAPA -# problems.) -################################################################################ -# "Special" PG environment variables. (Stuff that doesn't fit in anywhere else.) -################################################################################ - - $pg{specialPGEnvironmentVars}{CAPA_Tools} = "$courseDirs{templates}/Contrib/CAPA/macros/CAPA_Tools/", - $pg{specialPGEnvironmentVars}{CAPA_MCTools} = "$courseDirs{templates}/Contrib/CAPA/macros/CAPA_MCTools/", - $pg{specialPGEnvironmentVars}{CAPA_GraphicsDirectory} = "$courseDirs{templates}/Contrib/CAPA/CAPA_Graphics/", - $pg{specialPGEnvironmentVars}{CAPA_Graphics_URL} = "$webworkURLs{htdocs}/CAPA_Graphics/", - - # The link Contrib in the course templates directory should point to ../webwork-open-problem-library/Contrib - # The link webwork2/htdocs/CAPA_Graphics should point to ../webwork-open-problem-library/Contrib/CAPA/macros/CAPA_graphics -# Size in pixels of dynamically-generated images, i.e. graphs. -$pg{specialPGEnvironmentVars}{onTheFlyImageSize} = 400, - -# To disable the Parser-based versions of num_cmp and fun_cmp, and use the -# original versions instead, set this value to 1. -$pg{specialPGEnvironmentVars}{useOldAnswerMacros} = 0; - -# Determines whether or not MathObjects contexts will parse the alternative tokens -# listed in the "alternatives" property (mostly for unicode alternatives for parse tokens). -$pg{specialPGEnvironmentVars}{parseAlternatives} = 0; - -# Determines whether or not the MathObjects parser will convert the Full Width Unicode block -# (U+FF01 to U+FF5E) to their corresponding ASCII characters (U+0021 to U+007E) automatically. -$pg{specialPGEnvironmentVars}{convertFullWidthCharacters} = 0; - - -# Strings to insert at the start and end of the body of a problem -# (at beginproblem() and ENDDOCUMENT) in various modes. More display modes -# can be added if different behaviours are desired (e.g., HTML_dpng, -# HTML_asciimath, etc.). These parts are not used in the Library browser. - -$pg{specialPGEnvironmentVars}{problemPreamble} = { TeX => '', HTML=> '' }; -$pg{specialPGEnvironmentVars}{problemPostamble} = { TeX => '', HTML=>'' }; - -# this snippet checks to see if Moodle has already called MathJax -# $pg{specialPGEnvironmentVars}{problemPreamble} = { TeX => '', HTML=> < -# if (MathJax.Hub.Startup.params.config && MathJax.Hub.config.config.length) { -# MathJax.Hub.Config({ -# config: [], -# skipStartupTypeset: false -# }); -# } -# -# END_PREAMBLE - -# -# $pg{specialPGEnvironmentVars}{problemPostamble} = { TeX => '', HTML=> <\n!; -#$pg{specialPGEnvironmentVars}{problemPostamble}{HTML}= qq!\n!; - -# To have the problem body indented and boxed, uncomment: -# $pg{specialPGEnvironmentVars}{problemPreamble}{HTML} = '
-#
'; -# $pg{specialPGEnvironmentVars}{problemPostamble}{HTML} = '
-#
'; - -##### PG modules to load - -# The first item of each list is the module to load. The remaining items are -# additional packages to import. -# -# That is: If you wish to include a module MyModule.pm which depends -# on additional modules Dependency1.pm and Dependency2.pm, these -# should appear as [qw(Mymodule.pm, Dependency1.pm, Dependency2.pm)] - -${pg}{modules} = [ - [qw(Encode)], - [qw(Encode::Encoding)], - [qw(HTML::Parser)], - [qw(HTML::Entities)], - [qw(DynaLoader)], - [qw(Encode)], - [qw(Exporter )], - [qw(GD)], - [qw(AlgParser AlgParserWithImplicitExpand Expr ExprWithImplicitExpand utf8)], - [qw(AnswerHash AnswerEvaluator)], - [qw(LaTeXImage)], - [qw(WWPlot)], # required by Circle (and others) - [qw(Circle)], - [qw(Class::Accessor)], - [qw(Complex)], - [qw(Complex1)], - [qw(Distributions)], - [qw(Fraction)], - [qw(Fun)], - [qw(Hermite)], - [qw(Label)], - [qw(ChoiceList)], - [qw(Match)], - [qw(MatrixReal1)], # required by Matrix - [qw(Matrix)], - [qw(Multiple)], - [qw(PGrandom)], - [qw(Regression)], - [qw(Select)], - [qw(Units)], - [qw(VectorField)], - [qw(Parser Value)], - [qw(Parser::Legacy)], - [qw(Statistics)], - #[qw(Chromatic)], # for Northern Arizona graph problems - [qw(Applet GeogebraWebApplet)], - [qw(PGcore PGalias PGresource PGloadfiles PGanswergroup PGresponsegroup Tie::IxHash)], - [qw(Locale::Maketext)], - [qw(WeBWorK::Localize)], - [qw(JSON)], - [qw(Rserve Class::Tiny IO::Handle)], - [qw(DragNDrop)], - [qw(Types::Serialiser)], - [qw(Mojo::Exception)], -]; - -##### 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 -# 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 -# 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 -# 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; - -#The default attempts to open children value to use when adding -# a problem. This only matters for JITAR sets -$problemDefaults{att_to_open_children} = 1; - -#The default counts for parent grade problem value to use when adding -# a problem. This only matters for JITAR sets -$problemDefaults{counts_parent_grade} = 0; - -# The default prPeriod value (re-randomization period) to use for the newly created problem. -# It is suggested to use the value of -1, which means that the course-wide setting would be used -# Setting this to -1 defaults to the use of course-wide settings (suggested) -# Setting this to 0 disables periodic randomization regardless of the course-wide setting -# Setting this to a positive value will override the course-wide setting -$problemDefaults{prPeriod} = -1; - -##### Answer evaluatior defaults - -$pg{ansEvalDefaults} = { - functAbsTolDefault => .001, - functLLimitDefault => .0000001, - functMaxConstantOfIntegration => 1E8, - functNumOfPoints => 3, - functRelPercentTolDefault => .1, - functULimitDefault => .9999999, - functVarDefault => "x", - functZeroLevelDefault => 1E-14, - functZeroLevelTolDefault => 1E-12, - numAbsTolDefault => .001, - numFormatDefault => "", - numRelPercentTolDefault => .1, - numZeroLevelDefault => 1E-14, - numZeroLevelTolDefault => 1E-12, - useBaseTenLog => 0, - defaultDisplayMatrixStyle => "[s]", # left delimiter, middle line delimiters, right delimiter - 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) -}; - - - -################################################################################ -# Compatibility -################################################################################ - -# Define the old names for the various "root" variables. -$webworkRoot = $webworkDirs{root}; -$webworkURLRoot = $webworkURLs{root}; -$pgRoot = $pg{directories}{root}; - - - -################################################################################ -# Webservices -################################################################################ -$webservices = { - enableCourseActions => 0, # enable createCourse, addUser, dropUser - enableCourseActionsLog => 0 ,# enable logging of course actions - # will log when courses are created and - # when students are added or dropped - courseActionsLogfile => "$webworkDirs{logs}/courseactions.log", - # enable this to assign all visible homework sets to new students - # added by webservices - courseActionsAssignHomework => 0, -}; - - -############################################################################### -# Math entry assistance -############################################################################### - -$pg{specialPGEnvironmentVars}{entryAssist} = 'MathQuill'; - -############################################################################### -# default Homework Config settings -############################################################################### - -# time of day the assignment is due. -$pg{timeAssignDue} = '11:59pm'; - -# number of minutes prior to due date that the assignment is open. -$pg{assignOpenPriorToDue} = 10080; - -#number of minutes after due date are the answers open; -$pg{answersOpenAfterDueDate} = 2880; - -############################################################################### -# Progress Bar switch -############################################################################### -$pg{options}{enableProgressBar} = 1; - -################################################################################ -# WeBWorK::ContentGenerator::Instructor::Config -################################################################################ - -# Configuration data -# It is organized by section. The allowable types are -# 'text' for a text string (no quote marks 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. -# 'checkboxlist' for variables which really hold a list of values which -# can be independently picked yes/no as checkboxes - -# 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 two variants, math4-green and math4-red. ' - . '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 sripts, 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 => 'hardcopyTheme', - doc => x('Hardcopy Theme'), - doc2 => x( - 'There are currently two hardcopy themes to choose from: One Column and Two Columns. The Two ' - . 'Columns theme is the traditional hardcopy format. The One Column theme uses the full page ' - . 'width for each column' - ), - values => $hardcopyThemes, - labels => $hardcopyThemeNames, - type => 'popuplist', - hashVar => '{hardcopyTheme}' - }, - { - 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.

' - ), - min => 1, - values => [qw(None MathQuill MathView WIRIS)], - 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' - }, - ], - [ - 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 => 'mail{allowedRecipients}', - doc => x('E-mail addresses which can receive e-mail from a pg problem'), - doc2 => x( - 'List of e-mail addresses to which e-mail can be sent by a problem. Professors need to be added to ' - . 'this list if questionaires are used, or other WeBWorK problems which send e-mail as part of ' - . 'their answer mechanism.' - ), - type => 'list' - }, - { - var => 'permissionLevels{receive_feedback}', - doc => x('E-mail feedback from students automatically sent to this permission level and higher'), - doc2 => x( - 'Users with this permssion level or greater will automatically be sent feedback from students ' - . '(generated when they use the "Contact instructor" button on any problem page). In addition ' - . 'the feedback message will be sent to addresses listed below. To send ONLY to addresses listed ' - . 'below set permission level to "nobody".' - ), - type => 'permission' - }, - { - var => 'mail{feedbackRecipients}', - doc => x('Additional addresses for receiving feedback e-mail'), - 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 in this blank. Separate email address entries by ' - . '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' - }, - ], -]; - -################################################################################ -# Site wide overrides are entered into the file localOverrides.conf -################################################################################ -#include("conf/localOverrides.conf"); - -1; #final line of the file to reassure perl that it was read properly. diff --git a/lib/WeBWorK/conf/site.conf b/lib/WeBWorK/conf/site.conf deleted file mode 100644 index 5b3f04c44..000000000 --- a/lib/WeBWorK/conf/site.conf +++ /dev/null @@ -1,337 +0,0 @@ -#!perl -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/conf/site.conf.dist,v 1.225 2010/05/18 18:03:31 apizer Exp $ -# -# 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. -# All package variables set in this file are added to the course environment. -# If you wish to set a variable here but omit it from the course environment, -# use the "my" keyword. The $webwork_dir variable is set in the WeBWorK Apache -# configuration file (webwork.apache-config) and is available for use here. In -# addition, the $courseName variable holds the name of the current course. - -# This file is used to set up the basic paths and URLs specific to your -# installation of WeBWorK, with the exception of the $webwork_dir variable which -# is set in the WeBWorK Apache configuration file (webwork.apache2-config). -# Any customization of global WeBWorK settings should be done in localOverrides.conf. - -################################################################################ -# site.conf -- this file -################################################################################ - -# site.conf includes all of the information specific to your server required -# to run WeBWorK. - -################################################################################ -# Seed variables -################################################################################ - -# Set these variables to correspond to your configuration. It is not -# recommended to change any of the settings in this file once your -# web server has been initially configured. - -# URL of WeBWorK handler. If WeBWorK is to be on the web server root, use "". Note -# that using "" may not work so we suggest sticking with "/webwork2". -$webwork_url = '/webwork2'; -$server_root_url = $ENV{SITE_HOST} || 'http://localhost:3000'; # e.g. 'https://webwork.yourschool.edu' or 'http://localhost' - # Note, if running a secure (ssl) server, you probably need 'https://...' - -# The following two variables must match the user ID and group ID respectively -# under which apache is running. -# In the apache configuration file (often called httpd.conf) you will find -# User www-data --- this is the $server_userID -- of course it may be wwhttpd or some other name -# Group wwdata --- this is the $server_groupID -- this will have different names also - -$server_userID = 'www-data'; -$server_groupID = 'wwdata'; - - -# Uncomment out the following line to set your apache version number manually. -# WeBWorK will automatically get the apache version directly from the server -# banner. If you remove the version from the server banner you will have to -# set it directly here - -#$server_apache_version = ''; # e.g. '2.22.1' - -# The following variable is the address that will be listed in server error -# messages that come from WeBWorK: -# "An error occured while processing your request. -# For help, please send mail to this site's webmaster -# (mail link to ), including all of the following -# information as well as what what you were doing when the error occurred... etc..." -# Make sure that your webwork.apacheX-config file is up to date with the distributed version -# and that the line $ENV{WEBWORK_SERVER_ADMIN} = $ce->{webwork_server_admin_email}; -# is present in the file. -# If $webwork_server_admin_email is not defined then the -# ServerAdmin address defined in httpd.conf is used. -# Be sure to use single quotes for the address or the @ sign will be interpreted as an array. - - -$webwork_server_admin_email =''; -################################################################################ -# Paths to external programs -################################################################################ - -# These applications are often found in /bin, but sometimes in /usr/bin -# 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{git} = "/usr/bin/git"; - -#################################################### -# equation rendering/hardcopy utiltiies -#################################################### -$externalPrograms{latex} ="/usr/bin/latex"; - -$externalPrograms{pdflatex} ="/usr/bin/pdflatex --shell-escape"; -# 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 --shell-escape"; - -$externalPrograms{dvipng} ="/usr/bin/dvipng"; - -# In order to use imagemagick convert you need to change the rights for PDF files from -# "none" to "read" in the policy file /etc/ImageMagick-6/policy.xml. This has possible -# security implications for the server. -$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"; - -#################################################### -# url checker -#################################################### -# set timeout time (-t 40 sec) to be less than timeout for problem (usually 60 seconds) -$externalPrograms{checkurl} = "/usr/bin/curl -Is -m 30 "; # or "/usr/local/bin/w3c -head " -$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 -################################################################################ - - -# Standard permissions command used to initialize the webwork database -# GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, INDEX, LOCK TABLES ON webwork.* TO webworkWrite@localhost IDENTIFIED BY 'passwordRW'; -# 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. - -# 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 -################################################################################ - -# The database dsn is the path to the WeBWorK database which you have created. -# Unless you have given the database a different name or the database resides on another -# server you do not need to change this first value. -# The format is dbi:mysql:[databasename] for databases on the local machine -# For a remote database the format is dbi:mysql:[databasename]:[hostname]:[port] -$database_dsn ="dbi:Pg:dbname=webwork;host=localhost"; -$database_storage_engine = 'myisam'; - -######################### -# MYSQL compatibility settings for handling international Unicode characters (utf8 and utf8mb) -######################### -# These set the way characters are encoded in mysql and will depend on the version of mysqld being used. -# the default is to use latin1. With version 2.15 we will move to -# encoding utf8mb4 which allows the encoding of characters from many languages -# including chinese, arabic and hebrew. - -$ENABLE_UTF8MB4 =1; # setting this to 1 enables utf8mb4 encoding, setting this to - # 0 sets this for older mysql (pre 5.3) which cannot - # handle utf8mb4 characters. - -$database_character_set=($ENABLE_UTF8MB4) ? 'utf8mb4' : 'utf8'; - - -# DATABASE login information -# The following two variables must match the GRANT statement run on the mysql server as described above. -$database_username ="postgres"; -$database_password ="postgres"; - -################################################################################# -# These variables describe the locations of various components of WeBWorK on your -# server. You may use the defaults unless you have things in different places. -################################################################################# - -# Root directory of PG. -$pg_dir = "$ENV{RENDER_ROOT}/lib/PG"; - -# URL and path to htdocs directory. -$webwork_htdocs_url = "$ENV{baseURL}/webwork2_files"; -$webwork_htdocs_dir = "$webwork_dir/htdocs"; - -# URL and path to courses directory. -$webwork_courses_url = "$ENV{baseURL}/webwork2_course_files"; -$webwork_courses_dir = "$ENV{RENDER_ROOT}"; # standalone renderer has no courses - - -################################################################################ -# Mail settings -################################################################################ - -# 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{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. - -# Seconds to wait before timing out when connecting to the SMTP server. -# the default is 120 seconds. -# Change it by uncommenting the following line -# set it to 5 for testing, 30 or larger for production - -$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 - -$mail{tls_allowed} = 0; - -#$tls_allowed=0; #old method -- this variable no longer works. - - -# 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) - - -# 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 - -#$mail{smtpPort} = 25; - -# Debugging tutorial for sending email using ssl/tls -# https://maulwuff.de/research/ssl-debugging.html - -################################################################################ -# Problem library options -################################################################################ -# -# The problemLibrary configuration data should now be set in localOverrides.conf - -# For configuration instructions, see: -# http://webwork.maa.org/wiki/National_Problem_Library -# The directory containing the Open Problem Library files. -# Set the root to "" if no problem -# library is installed. Use version 2.0 for the NPL and use the version 2.5 for the OPL. -# When changing from the NPL to the OPL it is important to change the version number -# because the names of the tables in the database have changed. - -# RE-CONFIGURE problemLibrary values in the localOverrides.conf file. -# The settings in site.conf are overridden by settings in default.config -################################################# -#$problemLibrary{root} ="/opt/webwork/libraries/webwork-open-problem-library/OpenProblemLibrary"; -########################################################### - -################################################################################ -#Time Zone -################################################################################ - -# Set the default timezone of courses on this server. To get a list of valid -# timezones, run: -# -# perl -MDateTime::TimeZone -e 'print join "\n", DateTime::TimeZone::all_names' -# -# To get a list of valid timezone "links" (deprecated names), run: -# -# 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. -# -$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 -################################################################################ -# sets the default meta robots content for individual course pages -# this will not stop your main course listing page from being indexed -# valid contents: index, noindex, follow, nofollow, noarchive, and -# unavailable_after (example: "index, unavailable_after: 23-Jul-2007 18:00:00 EST") -$options{metaRobotsContent}='noindex, nofollow'; - -1; diff --git a/lib/WeBWorK/lib/WeBWorK/Constants.pm b/lib/WeBWorK/lib/WeBWorK/Constants.pm deleted file mode 100644 index ee889ead4..000000000 --- a/lib/WeBWorK/lib/WeBWorK/Constants.pm +++ /dev/null @@ -1,120 +0,0 @@ -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/Constants.pm,v 1.62 2010/02/01 01:57:56 apizer Exp $ -# -# 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 WeBWorK::Constants; - -=head1 NAME - -WeBWorK::Constants - provide constant values for other WeBWorK modules. - -=cut - -use strict; -use warnings; - -$WeBWorK::Constants::WEBWORK_DIRECTORY = $ENV{RENDER_ROOT}."/lib/WeBWorK" unless defined($WeBWorK::Constants::WEBWORK_DIRECTORY); - - -################################################################################ -# WeBWorK::Debug -################################################################################ - -# If true, WeBWorK::Debug will print debugging output. -# -$WeBWorK::Debug::Enabled = 0; - -# If non-empty, debugging output will be sent to the file named rather than STDERR. -# -$WeBWorK::Debug::Logfile = $WeBWorK::Constants::WEBWORK_DIRECTORY . "/logs/debug.log"; - -# If defined, prevent subroutines matching the following regular expression from -# logging. -# -# For example, this pattern prevents the dispatch() function from logging: -# $WeBWorK::Debug::DenySubroutineOutput = qr/^WeBWorK::dispatch$/; -# -$WeBWorK::Debug::DenySubroutineOutput = undef; -#$WeBWorK::Debug::DenySubroutineOutput = qr/^WeBWorK::dispatch$/; - -# If defined, allow only subroutines matching the following regular expression -# to log. -# -# For example, this pattern allow only some function being worked on to log: -# $WeBWorK::Debug::AllowSubroutineOutput = qr/^WeBWorK::SomePkg::myFunc$/; -# -# $WeBWorK::Debug::AllowSubroutineOutput = undef; -# $WeBWorK::Debug::AllowSubroutineOutput =qr/^WeBWorK::Authen::get_credentials$/; - -################################################################################ -# WeBWorK::ContentGenerator::Hardcopy -################################################################################ - -# If true, don't delete temporary files -# -$WeBWorK::ContentGenerator::Hardcopy::PreserveTempFiles = 0; - -################################################################################ -# WeBWorK::PG::Local -################################################################################ -# The maximum amount of time (in seconds) to work on a single problem. -# At the end of this time a timeout message is sent to the browser. - -$WeBWorK::PG::Local::TIMEOUT = 60; - -################################################################################ -# WeBWorK::PG::ImageGenerator -################################################################################ - -# Arguments to pass to dvipng. This is dependant on the version of dvipng. -# -# For dvipng versions 0.x -# $WeBWorK::PG::ImageGenerator::DvipngArgs = "-x4000.5 -bgTransparent -Q6 -mode toshiba -D180"; -# For dvipng versions 1.0 to 1.5 -# $WeBWorK::PG::ImageGenerator::DvipngArgs = "-bgTransparent -D120 -q -depth"; -# -# For dvipng versions 1.6 (and probably above) -# $WeBWorK::PG::ImageGenerator::DvipngArgs = "-bgtransparent -D120 -q -depth"; -# Note: In 1.6 and later, bgTransparent gives alpha-channel transparency while -# bgtransparent gives single-bit transparency. If you use alpha-channel transparency, -# the images will not be viewable with MSIE. bgtransparent works for version 1.5, -# but does not give transparent backgrounds. It does not work for version 1.2. It has not -# been tested with other versions. -# -$WeBWorK::PG::ImageGenerator::DvipngArgs = "-bgTransparent -D120 -q -depth"; - -# If true, don't delete temporary files -# -$WeBWorK::PG::ImageGenerator::PreserveTempFiles = 0; -# TeX to prepend to equations to be processed. -# -$WeBWorK::PG::ImageGenerator::TexPreamble = <<'EOF'; -\documentclass[12pt]{article} -\nonstopmode -\usepackage{amsmath,amsfonts,amssymb} -\def\gt{>} -\def\lt{<} -\usepackage[active,textmath,displaymath]{preview} -\begin{document} -EOF - -# TeX to append to equations to be processed. -# -$WeBWorK::PG::ImageGenerator::TexPostamble = <<'EOF'; -\end{document} -EOF - - -1; diff --git a/lib/WeBWorK/lib/WeBWorK/CourseEnvironment.pm b/lib/WeBWorK/lib/WeBWorK/CourseEnvironment.pm deleted file mode 100644 index 789cd5164..000000000 --- a/lib/WeBWorK/lib/WeBWorK/CourseEnvironment.pm +++ /dev/null @@ -1,381 +0,0 @@ -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2019 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/CourseEnvironment.pm,v 1.37 2007/08/10 16:37:10 sh002i Exp $ -# -# 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 WeBWorK::CourseEnvironment; - -=head1 NAME - -WeBWorK::CourseEnvironment - Read configuration information from defaults.config -and course.conf files. - -=head1 SYNOPSIS - - use WeBWorK::CourseEnvironment; - $ce = WeBWorK::CourseEnvironment->new({ - webwork_url => "/webwork2", - webwork_dir => "/opt/webwork2", - pg_dir => "/opt/pg", - webwork_htdocs_url => "/webwork2_files", - webwork_htdocs_dir => "/opt/webwork2/htdocs", - webwork_courses_url => "/webwork2_course_files", - webwork_courses_dir => "/opt/webwork2/courses", - courseName => "name_of_course", - }); - - my $timeout = $courseEnv->{sessionKeyTimeout}; - my $mode = $courseEnv->{pg}->{options}->{displayMode}; - # etc... - -=head1 DESCRIPTION - -The WeBWorK::CourseEnvironment module reads the system-wide F and -course-specific F files used by WeBWorK to calculate and store -settings needed throughout the system. The F<.conf> files are perl source files -that can contain any code allowed under the default safe compartment opset. -After evaluation of both files, any package variables are copied out of the -safe compartment into a hash. This hash becomes the course environment. - -=cut - -use strict; -use warnings; -use Carp; -use WWSafe; -use WeBWorK::Utils qw(readFile); -use WeBWorK::Debug; -use Opcode qw(empty_opset); - -=head1 CONSTRUCTION - -=over - -=item new(HASHREF) - -HASHREF is a reference to a hash containing scalar variables with which to seed -the course environment. It must contain at least a value for the key -C. - -The C method finds the file F relative to the given -C directory. After reading this file, it uses the -C<$courseFiles{environment}> variable, if present, to locate the course -environment file. If found, the file is read and added to the environment. - -=item new(ROOT URLROOT PGROOT COURSENAME) - -A deprecated form of the constructor in which four seed variables are given -explicitly: C, C, C, and C. - -=cut - -# NEW SYNTAX -# -# new($invocant, $seedVarsRef) -# $invocant implicitly set by caller -# $seedVarsRef reference to hash containing scalar variables with which to -# seed the course environment -# -# OLD SYNTAX -# -# new($invocant, $webworkRoot, $webworkURLRoot, $pgRoot, $courseName) -# $invocant implicitly set by caller -# $webworkRoot directory that contains the WeBWorK distribution -# $webworkURLRoot URL that points to the WeBWorK system -# $pgRoot directory that contains the PG distribution -# $courseName name of the course being used -sub new { - my ($invocant, @rest) = @_; - my $class = ref($invocant) || $invocant; - - # contains scalar symbols/values with which to seed course environment - my %seedVars; - - # where do we get the seed variables? - if (ref $rest[0] eq "HASH") { - %seedVars = %{$rest[0]}; - } else { - debug __PACKAGE__, ": deprecated four-argument form of new() used.", caller(1),"\n", caller(2),"\n"; - $seedVars{webwork_dir} = $rest[0]; - $seedVars{webwork_url} = $rest[1]; - $seedVars{pg_dir} = $rest[2]; - $seedVars{courseName} = $rest[3]; - } - $seedVars{courseName} = $seedVars{courseName}||"___"; # prevents extraneous error messages - my $safe = WWSafe->new; - $safe->permit('rand'); - # to avoid error messages make sure that courseName is defined - $seedVars{courseName} = $seedVars{courseName}//"foobar_course"; - # seed course environment with initial values - while (my ($var, $val) = each %seedVars) { - $val = "" if not defined $val; - $safe->reval("\$$var = '$val';"); - } - - # Compile the "include" function with all opcodes available. - my $include = q[ sub include { - my ($file) = @_; - my $fullPath = "].$seedVars{webwork_dir}.q[/$file"; - # This regex matches any string that begins with "../", - # ends with "/..", contains "/../", or is "..". - if ($fullPath =~ m!(?:^|/)\.\.(?:/|$)!) { - die "Included file $file has potentially insecure path: contains \"..\""; - } else { - local @INC = (); - my $result = do $fullPath; - if ($!) { - die "Failed to read include file $fullPath (has it been created from the corresponding .dist file?): $!"; - } elsif ($@) { - die "Failed to compile include file $fullPath: $@"; - } elsif (not $result) { - die "Include file $fullPath did not return a true value."; - } - } - } ]; - - my $maskBackup = $safe->mask; - $safe->mask(empty_opset); - $safe->reval($include); - $@ and die "Failed to reval include subroutine: $@"; - $safe->mask($maskBackup); - - # determine location of globalEnvironmentFile - my $globalEnvironmentFile; - if (-r "$seedVars{webwork_dir}/conf/defaults.config") { - $globalEnvironmentFile = "$seedVars{webwork_dir}/conf/defaults.config"; - } else { - croak "Cannot read global environment file $globalEnvironmentFile"; - } - - # read and evaluate the global environment file - my $globalFileContents = readFile($globalEnvironmentFile); - # warn "about to evaluate defaults.conf $seedVars{courseName}\n"; - # warn join(" | ", (caller(1))[0,1,2,3,4] ), "\n"; - $safe->share_from('main', [qw(%ENV)]); - $safe->reval($globalFileContents); - # warn "end the evaluation\n"; - - - - - # if that evaluation failed, we can't really go on... - # we need a global environment! - $@ and croak "Could not evaluate global environment file $globalEnvironmentFile: $@"; - - # determine location of courseEnvironmentFile and simple configuration file - # pull it out of $safe's symbol table ad hoc - # (we don't want to do the hash conversion yet) - no strict 'refs'; - my $courseEnvironmentFile = ${*{${$safe->root."::"}{courseFiles}}}{environment}; - my $courseWebConfigFile = $seedVars{web_config_filename} || - ${*{${$safe->root."::"}{courseFiles}}}{simpleConfig}; - use strict 'refs'; - - # make sure the course environment file actually exists (it might not if we don't have a real course) - # before we try to read it - if(-r $courseEnvironmentFile){ - # read and evaluate the course environment file - # if readFile failed, we don't bother trying to reval - my $courseFileContents = eval { readFile($courseEnvironmentFile) }; # catch exceptions - $@ or $safe->reval($courseFileContents); - my $courseWebConfigContents = eval { readFile($courseWebConfigFile) }; # catch exceptions - $@ or $safe->reval($courseWebConfigContents); - } - - # get the safe compartment's namespace as a hash - no strict 'refs'; - my %symbolHash = %{$safe->root."::"}; - use strict 'refs'; - - # convert the symbol hash into a hash of regular variables. - my $self = {}; - foreach my $name (keys %symbolHash) { - # weed out internal symbols - next if $name =~ /^(INC|_.*|__ANON__|main::|include)$/; - # pull scalar, array, and hash values for this symbol - my $scalar = ${*{$symbolHash{$name}}}; - my @array = @{*{$symbolHash{$name}}}; - my %hash = %{*{$symbolHash{$name}}}; - # for multiple variables sharing a symbol, scalar takes precedence - # over array, which takes precedence over hash. - if (defined $scalar) { - $self->{$name} = $scalar; - } elsif (@array) { - $self->{$name} = \@array; - } elsif (%hash) { - $self->{$name} = \%hash; - } - } - # now that we know the name of the pg_dir we can get the pg VERSION file - my $PG_version_file = $self->{'pg_dir'}."/VERSION"; - - # Try a fallback location - if ( !-r $PG_version_file ) { - $PG_version_file = $self->{'webwork_dir'}."/../PG/VERSION"; - } - # # We'll get the pg version here and read it into the safe symbol table - if (-r $PG_version_file){ - #print STDERR ( "\n\nread PG_version file $PG_version_file\n\n"); - my $PG_version_file_contents = readFile($PG_version_file)//''; - $safe->reval($PG_version_file_contents); - #print STDERR ("\n contents: $PG_version_file_contents"); - - no strict 'refs'; - my %symbolHash2 = %{$safe->root."::"}; - #print STDERR "symbolHash".join(' ', keys %symbolHash2); - use strict 'refs'; - $self->{PG_VERSION}=${*{$symbolHash2{PG_VERSION}}}; - } else { - $self->{PG_VERSION}="unknown"; - #croak "Cannot read PG version file $PG_version_file"; - warn "Cannot read PG version file $PG_version_file"; - } - - - bless $self, $class; - - # here is where we can do evil things to the course environment *sigh* - # anything changed has to be done here. after this, CE is considered read-only - # anything added must be prefixed with an underscore. - - # create reverse-lookup hash mapping status abbreviations to real names - $self->{_status_abbrev_to_name} = { - map { my $name = $_; map { $_ => $name } @{$self->{statuses}{$name}{abbrevs}} } - keys %{$self->{statuses}} - }; - - # now that we're done, we can go ahead and return... - return $self; -} - -=back - -=head1 ACCESS - -There are no formal accessor methods. However, since the course environemnt is -a hash of hashes and arrays, is exists as the self hash of an instance -variable: - - $ce->{someKey}{someOtherKey}; - -=head1 EXPERIMENTAL ACCESS METHODS - -This is an experiment in extending CourseEnvironment to know a little more about -its contents, and perform useful operations for me. - -There is a set of operations that require certain data from the course -environment. Most of these are un Utils.pm. I've been forced to pass $ce into -them, so that they can get their data out. But some things are so intrinsically -linked to the course environment that they might as well be methods in this -class. - -=head2 STATUS METHODS - -=over - -=item status_abbrev_to_name($status_abbrev) - -Given the abbreviation for a status, return the name. Returns undef if the -abbreviation is not found. - -=cut - -sub status_abbrev_to_name { - my ($ce, $status_abbrev) = @_; - if (not defined $status_abbrev or $status_abbrev eq "") { - carp "status_abbrev_to_name: status_abbrev (first argument) must be defined and non-empty"; - return; - } - - return $ce->{_status_abbrev_to_name}{$status_abbrev}; -} - -=item status_name_to_abbrevs($status_name) - -Returns the list of abbreviations for a given status. Returns an empty list if -the status is not found. - -=cut - -sub status_name_to_abbrevs { - my ($ce, $status_name) = @_; - if (not defined $status_name or $status_name eq "") { - carp "status_name_to_abbrevs: status_name (first argument) must be defined and non-empty"; - return; - } - - return unless exists $ce->{statuses}{$status_name}; - return @{$ce->{statuses}{$status_name}{abbrevs}}; -} - -=item status_has_behavior($status_name, $behavior) - -Return true if $status_name lists $behavior. - -=cut - -sub status_has_behavior { - my ($ce, $status_name, $behavior) = @_; - if (not defined $status_name or $status_name eq "") { - carp "status_has_behavior: status_name (first argument) must be defined and non-empty"; - return; - } - if (not defined $behavior or $behavior eq "") { - carp "status_has_behavior: behavior (second argument) must be defined and non-empty"; - return; - } - - if (exists $ce->{statuses}{$status_name}) { - if (exists $ce->{statuses}{$status_name}{behaviors}) { - my $num_matches = grep { $_ eq $behavior } @{$ce->{statuses}{$status_name}{behaviors}}; - return $num_matches > 0; - } else { - return 0; # no behaviors - } - } else { - warn "status '$status_name' not found in \%statuses -- assuming no behaviors.\n"; - return 0; - } -} - -=item status_abbrev_has_behavior($status_abbrev, $behavior) - -Return true if the status abbreviated by $status_abbrev lists $behavior. - -=cut - -sub status_abbrev_has_behavior { - my ($ce, $status_abbrev, $behavior) = @_; - if (not defined $status_abbrev or $status_abbrev eq "") { - carp "status_abbrev_has_behavior: status_abbrev (first argument) must be defined and non-empty"; - return; - } - if (not defined $behavior or $behavior eq "") { - carp "status_abbrev_has_behavior: behavior (second argument) must be defined and non-empty"; - return; - } - - my $status_name = $ce->status_abbrev_to_name($status_abbrev); - if (defined $status_name) { - return $ce->status_has_behavior($status_name, $behavior); - } else { - warn "status abbreviation '$status_abbrev' not found in \%statuses -- assuming no behaviors.\n"; - } -} - -=back - -=cut - -1; diff --git a/lib/WeBWorK/lib/WeBWorK/Debug.pm b/lib/WeBWorK/lib/WeBWorK/Debug.pm deleted file mode 100644 index e30abf9e2..000000000 --- a/lib/WeBWorK/lib/WeBWorK/Debug.pm +++ /dev/null @@ -1,146 +0,0 @@ -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/Debug.pm,v 1.10 2006/06/28 16:20:39 sh002i Exp $ -# -# 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 WeBWorK::Debug; -use base qw(Exporter); -use Date::Format; -our @EXPORT = qw(debug); - -=head1 NAME - -WeBWorK::Debug - Print (or don't print) debugging output. - -=head1 SYNOPSIS - - use WeBWorK::Debug; - - # Enable debugging - $WeBWorK::Debug::Enabled = 1; - - # Log to a file instead of STDERR - $WeBWorK::Debug::Logfile = "/path/to/debug.log"; - - # log some debugging output - debug("Generated 5 widgets."); - -=cut - -use strict; -use warnings; -use Time::HiRes qw/gettimeofday/; -use WeBWorK::Constants; -use WeBWorK::Utils qw/undefstr/; - -################################################################################ - -=head1 CONFIGURATION VARIABLES - -=over - -=item $Enabled - -If true, debugging messages will be output. If false, they will be ignored. - -=cut - -our $Enabled = 0 unless defined $Enabled; - -=item $Logfile - -If non-empty, debugging output will be sent to the file named rather than STDERR. - -=cut - -our $Logfile = "" unless defined $Logfile; - -=item $DenySubroutineOutput - -If defined, prevent subroutines matching the following regular expression from -logging. - -=cut - -our $DenySubroutineOutput; - -=item $AllowSubroutineOutput - -If defined, allow only subroutines matching the following regular expression to -log. - -=cut - -our $AllowSubroutineOutput; - -=back - -=cut - -################################################################################ - -=head1 FUNCTIONS - -=over - -=item debug(@messages) - -Write @messages to the debugging log. - -=cut - -sub debug { - my (@message) = undefstr("###UNDEF###", @_); - - #print STDERR "in ww::debug\n"; - #print STDERR $WeBWorK::Constants::WEBWORK_DIRECTORY . "\n"; - #print STDERR $Logfile . "\n"; - - if ($Enabled) { - my ($package, $filename, $line, $subroutine) = caller(1); - return if defined $AllowSubroutineOutput and not $subroutine =~ m/$AllowSubroutineOutput/; - return if defined $DenySubroutineOutput and $subroutine =~ m/$DenySubroutineOutput/; - - my ($sec, $msec) = gettimeofday; - my $date = time2str("%a %b %d %H:%M:%S.$msec %Y", $sec); - my $finalMessage = "[$date] $subroutine: " . join("", @message); - $finalMessage .= "\n" unless $finalMessage =~ m/\n$/; - - if ($WeBWorK::Debug::Logfile ne "") { - if (open my $fh, ">>", $Logfile) { - print $fh $finalMessage; - close $fh; - } else { - warn "Failed to open debug log '$Logfile' in append mode: $!"; - print STDERR $finalMessage; - } - } else { - print STDERR $finalMessage; - } - } -} - -=back - -=cut - -################################################################################ - -=head1 AUTHOR - -Written by Sam Hathaway, sh002i (at) math.rochester.edu. - -=cut - -1; diff --git a/lib/WeBWorK/lib/WeBWorK/PG.pm b/lib/WeBWorK/lib/WeBWorK/PG.pm deleted file mode 100644 index edba44011..000000000 --- a/lib/WeBWorK/lib/WeBWorK/PG.pm +++ /dev/null @@ -1,532 +0,0 @@ -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/PG.pm,v 1.76 2009/07/18 02:52:51 gage Exp $ -# -# 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 WeBWorK::PG; - -=head1 NAME - -WeBWorK::PG - Invoke one of several PG rendering methods using an easy-to-use -API. - -=cut - -use strict; -use warnings; -no warnings qw( redefine ); -use WeBWorK::Debug; -use WeBWorK::PG::ImageGenerator; -use WeBWorK::Utils qw(runtime_use formatDateTime makeTempDirectory); -use WeBWorK::Utils::RestrictedClosureClass; #likely removable... -use WeBWorK::Localize; -use DateTime; -use DateTime::Duration; - -use constant DISPLAY_MODES => { - # display name # mode name - tex => "TeX", - plainText => "HTML", - images => "HTML_dpng", - MathJax => "HTML_MathJax", - PTX => "PTX", -}; - -sub new { - shift; # throw away invocant -- we don't need it - my ($ce, $user, $key, $set, $problem, $psvn, $formFields, - $translationOptions) = @_; - - my $renderer = "WeBWorK::PG::Local"; - - runtime_use $renderer; - - return $renderer->new(@_); -} - -sub free { - my $self = shift; - # - # If certain MathObjects (e.g. LimitedPolynomials) are left in the PG structure, then - # freeing them later can cause "Can't locate package ..." errors in the log during - # perl garbage collection. So free them here. - # - $self->{pgcore}{OUTPUT_ARRAY} = []; - $self->{answers} = {}; - undef $self->{translator}{safe}; - foreach (keys %{$self->{pgcore}{PG_ANSWERS_HASH}}) {undef $self->{pgcore}{PG_ANSWERS_HASH}{$_}} -} - -sub defineProblemEnvir { - my ( - $self, - $ce, - $user, - $key, - $set, - $problem, - $psvn, - $formFields, - $translationOptions, - $extras, - ) = @_; - - my %envir = %main::envir; - - debug("in WEBWORK::PG"); - - # ---------------------------------------------------------------------- - - # PG environment variables - # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002 - # any changes are noted by "ADDED:" or "REMOVED:" - - # Vital state information - # ADDED: displayModeFailover, displayHintsQ, displaySolutionsQ, - # refreshMath2img, texDisposition - - $envir{psvn} = $psvn; #'problem set version number' (associated with homework set) - $envir{psvn} = $envir{psvn}//$set->psvn; # use set value of psvn unless there is an explicit override. - # update problemUUID from submitted form, and fall back to the earlier name problemIdentifierPrefix if necessary - $envir{problemUUID} = $formFields->{problemUUID} // - $formFields->{problemIdentifierPrefix} // - $envir{problemUUID}// - 0; - $envir{psvnNumber} = "psvnNumber-is-deprecated-Please-use-psvn-Instead"; #FIXME - $envir{probNum} = $problem->{problem_id}; - $envir{questionNumber} = $envir{probNum}; - $envir{fileName} = $problem->{source_file}; - $envir{probFileName} = $envir{fileName}; - $envir{problemSeed} = $problem->{problem_seed}; - $envir{displayMode} = translateDisplayModeNames($translationOptions->{displayMode}); -# $envir{languageMode} = $envir{displayMode}; # don't believe this is ever used. - $envir{outputMode} = $envir{displayMode}; - $envir{displayHintsQ} = $translationOptions->{showHints}; - $envir{displaySolutionsQ} = $translationOptions->{showSolutions}; - $envir{texDisposition} = "pdf"; # in webwork2, we use pdflatex - - # Problem Information - # ADDED: courseName, formatedDueDate, enable_reduced_scoring - -# $envir{openDate} = $set->open_date; -# $envir{formattedOpenDate} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}); -# $envir{OpenDateDayOfWeek} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%A", $ce->{siteDefaults}{locale}); -# $envir{OpenDateDayOfWeekAbbrev} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%a", $ce->{siteDefaults}{locale}); -# $envir{OpenDateDay} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%d", $ce->{siteDefaults}{locale}); -# $envir{OpenDateMonthNumber} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%m", $ce->{siteDefaults}{locale}); -# $envir{OpenDateMonthWord} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%B", $ce->{siteDefaults}{locale}); -# $envir{OpenDateMonthAbbrev} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%b", $ce->{siteDefaults}{locale}); -# $envir{OpenDateYear2Digit} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%y", $ce->{siteDefaults}{locale}); -# $envir{OpenDateYear4Digit} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%Y", $ce->{siteDefaults}{locale}); -# $envir{OpenDateHour12} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%I", $ce->{siteDefaults}{locale}); -# $envir{OpenDateHour24} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%H", $ce->{siteDefaults}{locale}); -# $envir{OpenDateMinute} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%M", $ce->{siteDefaults}{locale}); -# $envir{OpenDateAMPM} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%P", $ce->{siteDefaults}{locale}); -# $envir{OpenDateTimeZone} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%Z", $ce->{siteDefaults}{locale}); -# $envir{OpenDateTime12} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%I:%M%P", $ce->{siteDefaults}{locale}); -# $envir{OpenDateTime24} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}, "%R", $ce->{siteDefaults}{locale}); -# $envir{dueDate} = $set->due_date; -# $envir{formattedDueDate} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}); -# $envir{formatedDueDate} = $envir{formattedDueDate}; # typo in many header files -# $envir{DueDateDayOfWeek} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%A", $ce->{siteDefaults}{locale}); -# $envir{DueDateDayOfWeekAbbrev} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%a", $ce->{siteDefaults}{locale}); -# $envir{DueDateDay} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%d", $ce->{siteDefaults}{locale}); -# $envir{DueDateMonthNumber} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%m", $ce->{siteDefaults}{locale}); -# $envir{DueDateMonthWord} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%B", $ce->{siteDefaults}{locale}); -# $envir{DueDateMonthAbbrev} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%b", $ce->{siteDefaults}{locale}); -# $envir{DueDateYear2Digit} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%y", $ce->{siteDefaults}{locale}); -# $envir{DueDateYear4Digit} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%Y", $ce->{siteDefaults}{locale}); -# $envir{DueDateHour12} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%I", $ce->{siteDefaults}{locale}); -# $envir{DueDateHour24} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%H", $ce->{siteDefaults}{locale}); -# $envir{DueDateMinute} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%M", $ce->{siteDefaults}{locale}); -# $envir{DueDateAMPM} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%P", $ce->{siteDefaults}{locale}); -# $envir{DueDateTimeZone} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%Z", $ce->{siteDefaults}{locale}); -# $envir{DueDateTime12} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%I:%M%P", $ce->{siteDefaults}{locale}); -# $envir{DueDateTime24} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}, "%R", $ce->{siteDefaults}{locale}); - $envir{answerDate} = ($formFields->{showSolutions})?DateTime->now->subtract(days=>1)->epoch():DateTime->now->add(days=>1)->epoch(); -# $envir{formattedAnswerDate} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}); -# $envir{AnsDateDayOfWeek} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%A", $ce->{siteDefaults}{locale}); -# $envir{AnsDateDayOfWeekAbbrev} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%a", $ce->{siteDefaults}{locale}); -# $envir{AnsDateDay} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%d", $ce->{siteDefaults}{locale}); -# $envir{AnsDateMonthNumber} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%m", $ce->{siteDefaults}{locale}); -# $envir{AnsDateMonthWord} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%B", $ce->{siteDefaults}{locale}); -# $envir{AnsDateMonthAbbrev} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%b", $ce->{siteDefaults}{locale}); -# $envir{AnsDateYear2Digit} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%y", $ce->{siteDefaults}{locale}); -# $envir{AnsDateYear4Digit} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%Y", $ce->{siteDefaults}{locale}); -# $envir{AnsDateHour12} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%I", $ce->{siteDefaults}{locale}); -# $envir{AnsDateHour24} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%H", $ce->{siteDefaults}{locale}); -# $envir{AnsDateMinute} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%M", $ce->{siteDefaults}{locale}); -# $envir{AnsDateAMPM} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%P", $ce->{siteDefaults}{locale}); -# $envir{AnsDateTimeZone} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%Z", $ce->{siteDefaults}{locale}); -# $envir{AnsDateTime12} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%I:%M%P", $ce->{siteDefaults}{locale}); -# $envir{AnsDateTime24} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}, "%R", $ce->{siteDefaults}{locale}); - my $ungradedAttempts = ($formFields->{submitAnswers})?1:0; # is an attempt about to be graded? - $envir{numOfAttempts} = ($problem->{num_correct} || 0) + ($problem->{num_incorrect} || 0) +$ungradedAttempts; - $envir{problemValue} = $problem->{value}; - $envir{sessionKey} = $key; - $envir{courseName} = $ce->{courseName}; - $envir{enable_reduced_scoring} = 1; #$ce->{pg}{ansEvalDefaults}{enableReducedScoring} && $set->enable_reduced_scoring; - - $envir{language} = $ce->{language}; - $envir{language_subroutine} = WeBWorK::Localize::getLoc($envir{language}); -# $envir{reducedScoringDate} = $set->reduced_scoring_date; - - # Student Information - # ADDED: studentID - - $envir{sectionName} = $user->{section}; - $envir{sectionNumber} = $envir{sectionName}; - $envir{recitationName} = $user->{recitation}; - $envir{recitationNumber} = $envir{recitationName}; - $envir{setNumber} = $set->{set_id}; - $envir{studentLogin} = $user->{user_id}; - $envir{studentName} = $user->{first_name} . " " . $user->{last_name}; - $envir{studentID} = $user->{student_id}; - $envir{permissionLevel} = $translationOptions->{permissionLevel}; # permission level of actual user - $envir{effectivePermissionLevel} = $translationOptions->{effectivePermissionLevel}; # permission level of user assigned to this question - - - # Answer Information - # REMOVED: refSubmittedAnswers - - $envir{inputs_ref} = $formFields; - - # External Programs - # ADDED: externalLaTeXPath, externalDvipngPath, - # externalGif2EpsPath, externalPng2EpsPath - -# $envir{externalLaTeXPath} = $ce->{externalPrograms}->{latex}; -# $envir{externalDvipngPath} = $ce->{externalPrograms}->{dvipng}; -# $envir{externalGif2EpsPath} = $ce->{externalPrograms}->{gif2eps}; -# $envir{externalPng2EpsPath} = $ce->{externalPrograms}->{png2eps}; -# $envir{externalGif2PngPath} = $ce->{externalPrograms}->{gif2png}; - $envir{externalCheckUrl} = $ce->{externalPrograms}->{checkurl}; - $envir{externalCurlCommand} = $ce->{externalPrograms}->{curl}; - # Directories and URLs - # REMOVED: courseName - # ADDED: dvipngTempDir - # ADDED: jsMathURL - # ADDED: MathJaxURL - # ADDED: asciimathURL - # ADDED: macrosPath - # REMOVED: macrosDirectory, courseScriptsDirectory - # ADDED: LaTeXMathML - -# $envir{cgiDirectory} = undef; -# $envir{cgiURL} = undef; -# $envir{classDirectory} = undef; - $envir{macrosPath} = $ce->{pg}->{directories}{macrosPath}; - $envir{appletPath} = $ce->{pg}->{directories}{appletPath}; - $envir{htmlPath} = $ce->{pg}->{directories}{htmlPath}; - $envir{imagesPath} = $ce->{pg}->{directories}{imagesPath}; - $envir{pdfPath} = $ce->{pg}->{directories}{pdfPath}; - $envir{pgDirectories} = $ce->{pg}->{directories}; - $envir{webworkHtmlDirectory} = $ce->{webworkDirs}->{htdocs}."/"; - $envir{webworkHtmlURL} = $ce->{webworkURLs}->{htdocs}."/"; - $envir{htmlDirectory} = $ce->{courseDirs}->{html}."/"; - $envir{htmlURL} = $ce->{courseURLs}->{html}."/"; - $envir{templateDirectory} = $ce->{courseDirs}->{templates}."/"; - $envir{tempDirectory} = $ce->{courseDirs}->{html_temp}."/"; - $envir{tempURL} = $ce->{courseURLs}->{html_temp}."/"; - $envir{scriptDirectory} = undef; - $envir{webworkDocsURL} = $ce->{webworkURLs}->{docs}."/"; - $envir{localHelpURL} = $ce->{webworkURLs}->{local_help}."/"; - $envir{MathJaxURL} = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js?config=TeX-MML-AM_HTMLorMML-full'; - $envir{server_root_url} = $ce->{server_root_url} || 'http://localhost/failed-lib-webwork-lib-webwork-pg.pm'; - - # Information for sending mail - -# $envir{mailSmtpServer} = $ce->{mail}->{smtpServer}; -# $envir{mailSmtpSender} = $ce->{mail}->{smtpSender}; -# $envir{ALLOW_MAIL_TO} = $ce->{mail}->{allowedRecipients}; - - # Default values for evaluating answers - - my $ansEvalDefaults = $ce->{pg}->{ansEvalDefaults}; - $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults); - - # ---------------------------------------------------------------------- - - # ADDED: ImageGenerator for images mode - if (defined $extras->{image_generator}) { - #$envir{imagegen} = $extras->{image_generator}; - # only allow access to the add() method -# $envir{imagegen} = new WeBWorK::Utils::RestrictedClosureClass($extras->{image_generator}, 'add','addToTeXPreamble', 'refresh'); - } - - if (defined $extras->{mailer}) { - #my $rmailer = new WeBWorK::Utils::RestrictedClosureClass($extras->{mailer}, - # qw/Open SendEnc Close Cancel skipped_recipients error error_msg/); - #my $safe_hole = new Safe::Hole {}; - #$envir{mailer} = $safe_hole->wrap($rmailer); -# $envir{mailer} = new WeBWorK::Utils::RestrictedClosureClass($extras->{mailer}, "add_message"); - } - # ADDED use_opaque_prefix and use_site_prefix - - $envir{use_site_prefix} = $translationOptions->{use_site_prefix}; - $envir{use_opaque_prefix} = $translationOptions->{use_opaque_prefix}; - - # Other things... - $envir{QUIZ_PREFIX} = $translationOptions->{QUIZ_PREFIX}//''; # used by quizzes - $envir{PROBLEM_GRADER_TO_USE} = "avg_problem_grader"; - $envir{PRINT_FILE_NAMES_FOR} = "professor"; - $envir{useMathQuill} = $translationOptions->{useMathQuill}; - $envir{useMathView} = $translationOptions->{useMathView}; - $envir{mathViewLocale} = $ce->{pg}{options}{mathViewLocale}; - $envir{useWirisEditor} = $translationOptions->{useWirisEditor}; - - # ADDED: __files__ - # an array for mapping (eval nnn) to filenames in error messages - $envir{__files__} = { - root => $ce->{webworkDirs}{root}, # used to shorten filenames - pg => $ce->{pg}{directories}{root}, # ditto - tmpl => $ce->{courseDirs}{templates}, # ditto - }; - - # variables for interpreting capa problems and other things to be - # seen in a pg file - my $specialPGEnvironmentVarHash = $ce->{pg}->{specialPGEnvironmentVars}; - for my $SPGEV (keys %{$specialPGEnvironmentVarHash}) { - $envir{$SPGEV} = $specialPGEnvironmentVarHash->{$SPGEV}; - } - - #%main::envir = %envir; - - return \%envir; -} - -sub translateDisplayModeNames($) { - my $name = shift; - return DISPLAY_MODES()->{$name}; -} - -sub oldSafetyFilter { - my $answer = shift; # accepts one answer and checks it - my $submittedAnswer = $answer; - $answer = '' unless defined $answer; - my ($errorno); - $answer =~ tr/\000-\037/ /; - # Return if answer field is empty - unless ($answer =~ /\S/) { - #$errorno = "
No answer was submitted."; - $errorno = 0; ## don't report blank answer as error - return ($answer,$errorno); - } - # replace ^ with ** (for exponentiation) - # $answer =~ s/\^/**/g; - # Return if forbidden characters are found - unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\[\]\(\)\,\|]+$/ ) { - $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c; - $errorno = "
There are forbidden characters in your answer: $submittedAnswer
"; - return ($answer,$errorno); - } - $errorno = 0; - return($answer, $errorno); -} - -sub nullSafetyFilter { - return shift, 0; # no errors -} - -1; - -__END__ - -=head1 SYNOPSIS - - $pg = WeBWorK::PG->new( - $ce, # a WeBWorK::CourseEnvironment object - $user, # a WeBWorK::DB::Record::User object - $sessionKey, - $set, # a WeBWorK::DB::Record::UserSet object - $problem, # a WeBWorK::DB::Record::UserProblem object - $psvn, - $formFields # in &WeBWorK::Form::Vars format - { # translation options - displayMode => "images", # (plainText|formattedText|images|MathJax) - showHints => 1, # (0|1) - showSolutions => 0, # (0|1) - refreshMath2img => 0, # (0|1) - processAnswers => 1, # (0|1) - }, - ); - $translator = $pg->{translator}; # WeBWorK::PG::Translator - $body = $pg->{body_text}; # text string - $header = $pg->{head_text}; # text string - $post_header_text = $pg->{post_header_text}; # text string - $answerHash = $pg->{answers}; # WeBWorK::PG::AnswerHash - $result = $pg->{result}; # hash reference - $state = $pg->{state}; # hash reference - $errors = $pg->{errors}; # text string - $warnings = $pg->{warnings}; # text string - $flags = $pg->{flags}; # hash reference - -=head1 DESCRIPTION - -WeBWorK::PG is a factory for modules which use the WeBWorK::PG API. Notable -modules which use this API (and exist) are WeBWorK::PG::Local and -WeBWorK::PG::Remote. The course environment key $pg{renderer} is consulted to -determine which render to use. - -=head1 THE WEBWORK::PG API - -Modules which support this API must implement the following method: - -=over - -=item new ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS - -The C method creates a translator, initializes it using the parameters -specified, translates a PG file, and processes answers. It returns a reference -to a blessed hash containing the results of the translation process. - -=back - -=head2 Parameters - -=over - -=item ENVIRONMENT - -a WeBWorK::CourseEnvironment object - -=item USER - -a WeBWorK::User object - -=item KEY - -the session key of the current session - -=item SET - -a WeBWorK::Set object - -=item PROBLEM - -a WeBWorK::DB::Record::UserProblem object. The contents of the source_file -field can specify a PG file either by absolute path or path relative to the -"templates" directory. I - -=item PSVN - -the problem set version number: use variable $psvn - -=item FIELDS - -a reference to a hash (as returned by &WeBWorK::Form::Vars) containing form -fields submitted by a problem processor. The translator will look for fields -like "AnSwEr[0-9]" containing submitted student answers. - -=item OPTIONS - -a reference to a hash containing the following data: - -=over - -=item displayMode - -one of "plainText", "formattedText", "MathJax" or "images" - -=item showHints - -boolean, render hints - -=item showSolutions - -boolean, render solutions - -=item refreshMath2img - -boolean, force images created by math2img (in "images" mode) to be recreated, -even if the PG source has not been updated. FIXME: remove this option. - -=item processAnswers - -boolean, call answer evaluators and graders - -=back - -=back - -=head2 RETURN VALUE - -The C method returns a blessed hash reference containing the following -fields. More information can be found in the documentation for -WeBWorK::PG::Translator. - -=over - -=item translator - -The WeBWorK::PG::Translator object used to render the problem. - -=item head_text - -HTML code for the EheadE block of an resulting web page. Used for -JavaScript features. - -=item body_text - -HTML code for the EbodyE block of an resulting web page. - -=item answers - -An C object containing submitted answers, and results of answer -evaluation. - -=item result - -A hash containing the results of grading the problem. - -=item state - -A hash containing the new problem state. - -=item errors - -A string containing any errors encountered while rendering the problem. - -=item warnings - -A string containing any warnings encountered while rendering the problem. - -=item flags - -A hash containing PG_flags (see the Translator docs). - -=back - -=head1 METHODS PROVIDED BY THE BASE CLASS - -The following methods are provided for use by subclasses of WeBWorK::PG. - -=over - -=item defineProblemEnvir ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS - -Generate a problem environment hash to pass to the renderer. - -=item translateDisplayModeNames NAME - -NAME contains - -=back - -=head1 AUTHOR - -Written by Sam Hathaway, sh002i (at) math.rochester.edu. - -=cut diff --git a/lib/WeBWorK/lib/WeBWorK/PG/Local.pm b/lib/WeBWorK/lib/WeBWorK/PG/Local.pm deleted file mode 100644 index 0069d91f7..000000000 --- a/lib/WeBWorK/lib/WeBWorK/PG/Local.pm +++ /dev/null @@ -1,566 +0,0 @@ -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/PG/Local.pm,v 1.28 2009/10/17 15:50:33 apizer Exp $ -# -# 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 WeBWorK::PG::Local; -use base qw(WeBWorK::PG); - -=head1 NAME - -WeBWorK::PG::Local - Use the WeBWorK::PG API to invoke a local -WeBWorK::PG::Translator object. - -=head1 DESCRIPTION - -WeBWorK::PG::Local encapsulates the PG translation process, making multiple -calls to WeBWorK::PG::Translator. Much of the flexibility of the Translator is -hidden, instead making choices that are appropriate for the webwork2 -system - -It implements the WeBWorK::PG interface and uses a local -WeBWorK::PG::Translator to perform problem rendering. See the documentation for -the WeBWorK::PG module for information about the API. - -=cut - -use strict; -use warnings; -use WeBWorK::Constants; -use File::Path qw(rmtree); -use WeBWorK::PG::Translator; -use WeBWorK::Utils qw(readFile writeTimingLogEntry); - - -#BEGIN{ -# unless (exists $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} >= 2) { -# require "mod_perl.pm"; # used only for mod_perl1 should we continue to support this? -# } -# -# use constant MP2 => ( exists $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} >= 2 ); -#} -# Problem processing will time out after this number of seconds. -use constant TIMEOUT => $WeBWorK::PG::Local::TIMEOUT || 10; - -BEGIN { - # This safe compartment is used to read the large macro files such as - # PG.pl, PGbasicmacros.pl and PGanswermacros and cache the results so that - # future calls have preloaded versions of these large files. This saves a - # significant amount of time. - # $WeBWorK::PG::Local::safeCache = new WWSafe; -} - -sub alarm_handler { - my $msg = - "Timeout after processing this problem for " - . TIMEOUT - . " seconds. Check for infinite loops in problem source.\n"; - warn $msg; - CORE::die $msg; -} - -sub new { - my $invocant = shift; - local $SIG{ALRM} = \&alarm_handler; - alarm TIMEOUT; - my $result = eval { $invocant->new_helper(@_) }; - alarm 0; - die $@ if $@; - return $result; -} - -sub new_helper { - my $invocant = shift; - my $class = ref($invocant) || $invocant; - my ( - $ce, - $user, - $key, - $set, - $problem, - $psvn, #FIXME -- not used - $formFields, # in CGI::Vars format - $translationOptions, # hashref containing options for the - # translator, such as whether to show - # hints and the display mode to use - ) = @_; - -# install a local warn handler to collect warnings FIXME -- figure out what I meant to do here. - my $warnings = ""; - - local $SIG{__WARN__} = sub { $warnings .= shift()."
\n"} - if $ce->{pg}->{options}->{catchWarnings}; - - # create a Translator - #warn "PG: creating a Translator\n"; - my $translator = WeBWorK::PG::Translator->new; - - ############################################################################ - # evaluate modules and "extra packages" - ############################################################################ - - #warn "PG: evaluating modules and \"extra packages\"\n"; - my @modules = @{ $ce->{pg}->{modules} }; - - foreach my $module_packages_ref (@modules) { - my ( $module, @extra_packages ) = @$module_packages_ref; - - # the first item is the main package - $translator->evaluate_modules($module); - - # the remaining items are "extra" packages - $translator->load_extra_packages(@extra_packages); - } - - ############################################################################ - # prepare an imagegenerator object (if we're in "images" mode) - ############################################################################ - my $image_generator; - my $site_prefix = ( $translationOptions->{use_site_prefix} ) // ''; - if ( $translationOptions->{displayMode} eq "images" - || $translationOptions->{displayMode} eq "opaque_image" ) - { - my %imagesModeOptions = %{ $ce->{pg}{displayModeOptions}{images} }; - $image_generator = WeBWorK::PG::ImageGenerator->new( - tempDir => $ce->{webworkDirs}->{tmp}, # global temp dir - latex => $ce->{externalPrograms}->{latex}, - dvipng => $ce->{externalPrograms}->{dvipng}, - useCache => 1, - cacheDir => $ce->{webworkDirs}{equationCache}, - cacheURL => $site_prefix . $ce->{webworkURLs}{equationCache}, - cacheDB => $ce->{webworkFiles}{equationCacheDB}, - useMarkers => ( - $imagesModeOptions{dvipng_align} - && $imagesModeOptions{dvipng_align} eq 'mysql' - ), - dvipng_align => $imagesModeOptions{dvipng_align}, - dvipng_depth_db => $imagesModeOptions{dvipng_depth_db}, - ); - } - - ############################################################################ -# create a "delayed mailer" object that will send emails after the page is finished. - ############################################################################ - - my $mailer = {}; #new WeBWorK::Utils::DelayedMailer( - -# smtp_server => $ce->{mail}{smtpServer}, -# smtp_sender => $ce->{mail}{smtpSender}, -# smtp_timeout => $ce->{mail}{smtpTimeout}, -# # FIXME I'd like to have an X-Remote-Host header, but before I do that I have to -# # factor out the remote host/remote port code from Feedback.pm and Authen.pm and -# # put it in Utils! (or maybe in WW::Request?) -# headers => "X-WeBWorK-Module: " . __PACKAGE__ . "\n" -# . "X-WeBWorK-Course: " . $ce->{courseName} . "\n" -# # can't add user-related information because this is used for anonymous questionnaires -# #. "X-WeBWorK-User: " . $user->user_id . "\n" -# #. "X-WeBWorK-Section: " . $user->section . "\n" -# #. "X-WeBWorK-Recitation: " . $user->recitation . "\n" -# . "X-WeBWorK-Set: " . $set->set_id . "\n" -# . "X-WeBWorK-Problem: " . $problem->problem_id . "\n" -# . "X-WeBWorK-PGSourceFile: " . $problem->source_file . "\n", -# allowed_recipients => $ce->{mail}{allowedRecipients}, -# on_illegal_rcpt => "carp", -# ); - - ############################################################################ - # set the environment (from defineProblemEnvir) - ############################################################################ - - #warn "PG: setting the environment (from defineProblemEnvir)\n"; - my $envir = $class->defineProblemEnvir( - $ce, - $user, - $key, - $set, - $problem, - $psvn, #FIXME -- not used - $formFields, - $translationOptions, - { #extras (this is kind of a hack, but not a serious one) - image_generator => $image_generator, - mailer => $mailer, - problemUUID => 0, - }, - ); - $translator->environment($envir); - - ############################################################################ - # initialize the Translator - ############################################################################ - #warn "PG: initializing the Translator\n"; - $translator->initialize(); - - ############################################################################ - # preload macros - ############################################################################ - # This is in transition. here are the old instructions - -# Preload the macros files which are used routinely: PG.pl, -# dangerousMacros.pl, IO.pl, PGbasicmacros.pl, and PGanswermacros.pl -# (Preloading the last two files safes a significant amount of time.) -# -# IO.pl, PG.pl, and dangerousMacros.pl are loaded using -# unrestricted_load This is hard wired into the -# Translator::pre_load_macro_files subroutine. I'd like to change this -# at some point to have the same sort of interface to defaults.config that -# the module loading does -- have a list of macros to load -# unrestrictedly. -# -# This has been replaced by the pre_load_macro_files subroutine. It -# loads AND caches the files. While PG.pl and dangerousMacros are not -# large, they are referred to by PGbasicmacros and PGanswermacros. -# Because these are loaded into the cached name space (e.g. -# Safe::Root1::) all calls to, say NEW_ANSWER_NAME are actually calls -# to Safe::Root1::NEW_ANSWER_NAME. It is useful to have these names -# inside the Safe::Root1: cached safe compartment. (NEW_ANSWER_NAME -# and all other subroutine names are also automatically exported into -# the current safe compartment Safe::Rootx:: -# -# The headers of both PGbasicmacros and PGanswermacros has code that -# insures that the constants used are imported into the current safe -# compartment. This involves evaluating references to, say -# $main::displayMode, at runtime to insure that main refers to -# Safe::Rootx:: and NOT to Safe::Root1::, which is the value of main:: -# at compile time. -# -# TO ENABLE CACHEING UNCOMMENT THE FOLLOWING: -- this has not been used in some time -# eval{$translator->pre_load_macro_files( -# $WeBWorK::PG::Local::safeCache, -# $ce->{pg}->{directories}->{macros}, -# #'PG.pl', 'dangerousMacros.pl','IO.pl','PGbasicmacros.pl','PGanswermacros.pl' -# )}; -# warn "Error while preloading macro files: $@" if $@; - - ############################################################################ - # Here are the new instructions for preloading the macros - ############################################################################ - - # STANDARD LOADING CODE: for cached script files, this merely - # initializes the constants. - #2010 -- in the new scheme PG.pl is the only file guaranteed - # initialization -- it reads in everything that dangerous macros - # and IO.pl - # did before. Mostly it just defines access to the PGcore object - -# 2021 -- for the standaloneRenderer, PG.pl is pre-cached in Translator.pm - # foreach (qw(PG.pl )) { # dangerousMacros.pl IO.pl - # my $macroPath = $WeBWorK::Constants::PG_DIRECTORY . "/macros/$_"; - # my $err = $translator->unrestricted_load($macroPath); - # warn "Error while loading $macroPath: $err" if $err; - # } - - ############################################################################ - # set the opcode mask (using default values) - ############################################################################ - #warn "PG: setting the opcode mask (using default values)\n"; - $translator->set_mask(); - - ############################################################################ - # get the problem source - # FIXME -- this operation can be moved out of the translator. - ############################################################################ - #warn "PG: storing the problem source\n"; - my $source = ''; - my $sourceFilePath = ''; - my $readErrors = undef; - if ( ref( $translationOptions->{r_source} ) ) { - - # the source for the problem is already given to us as a reference to a string - $source = ${ $translationOptions->{r_source} }; - } - else { - # the source isn't given to us so we need to read it - # from a file defined by the problem - - # we grab the sourceFilePath from the problem - $sourceFilePath = $problem->source_file; - - # the path to the source file is usually given relative to the - # the templates directory. Unless the path starts with / assume - # that it is relative to the templates directory - - $sourceFilePath = $ce->{courseDirs}->{templates} . "/" . $sourceFilePath - unless ( $sourceFilePath =~ /^\// ); - - #now grab the source - eval { $source = readFile($sourceFilePath) }; - $readErrors = $@ if $@; - } - - ############################################################################ - # put the source into the translator object - ############################################################################ - - eval { $translator->source_string($source) } unless $readErrors; - $readErrors .= "\n $@ " if $@; - if ($readErrors) { - - # well, we couldn't get the problem source, for some reason. - return bless { - translator => $translator, - head_text => "", - post_header_text => "", - body_text => < {}, - result => {}, - state => {}, - errors => "Failed to read the problem source file.", - warnings => "$warnings", - flags => { error_flag => 1 }, - pgcore => $translator->{rh_pgcore}, - }, $class; - } - - ############################################################################ - # install a safety filter - # FIXME -- I believe that since MathObjects this is no longer operational - ############################################################################ - #warn "PG: installing a safety filter\n"; - #$translator->rf_safety_filter(\&oldSafetyFilter); - $translator->rf_safety_filter( \&WeBWorK::PG::nullSafetyFilter ); - - ############################################################################ - # write timing log entry -- the translator is now all set up - ############################################################################ - # writeTimingLogEntry($ce, "WeBWorK::PG::new", - # "initialized", - # "intermediate"); - - ############################################################################ - # translate the PG source into text - ############################################################################ - - #warn "PG: translating the PG source into text\n"; - $translator->translate(); - - ############################################################################ - # !!!!!!!! IMPORTANT: $envir shouldn't be trusted after problem code runs! - ############################################################################ - - my ( $result, $state ); # we'll need these on the other side of the if block! - if ( $translationOptions->{processAnswers} ) { - - ############################################################################ - # process student answers - ############################################################################ - - #warn "PG: processing student answers\n"; - $translator->process_answers($formFields); - - ############################################################################ - # retrieve the problem state and give it to the translator - ############################################################################ - #warn "PG: retrieving the problem state and giving it to the translator\n"; - - $translator->rh_problem_state( - { - recorded_score => $problem->{status}, - sub_recorded_score => $problem->{sub_status}, - num_of_correct_ans => $problem->{num_correct}, - num_of_incorrect_ans => $problem->{num_incorrect}, - } - ); - - ############################################################################ - # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by - # the PG macro package (PG.pl) - ############################################################################ - #warn "PG: determining an entry order\n"; - - my @answerOrder = - $translator->rh_flags->{ANSWER_ENTRY_ORDER} - ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} } - : keys %{ $translator->rh_evaluated_answers }; - - ############################################################################ - # install a grader -- use the one specified in the problem, - # or fall back on the default from the course environment. - # (two magic strings are accepted, to avoid having to - # reference code when it would be difficult.) - ############################################################################ - #warn "PG: installing a grader\n"; - - my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE} - || "avg_problem_grader"; - $grader = $translator->rf_std_problem_grader - if $grader eq "std_problem_grader"; - $grader = $translator->rf_avg_problem_grader - if $grader eq "avg_problem_grader"; - die "Problem grader $grader is not a CODE reference." - unless ref $grader eq "CODE"; - $translator->rf_problem_grader($grader); - - ############################################################################ - # grade the problem - ############################################################################ - #warn "PG: grading the problem\n"; - - ( $result, $state ) = $translator->grade_problem( - answers_submitted => $translationOptions->{processAnswers}, - ANSWER_ENTRY_ORDER => \@answerOrder, - %{$formFields} - , #FIXME? this is used by sequentialGrader is there a better way - ); - - } - - ############################################################################ - # after we're done translating, we may have to clean up after the - # translator: - ############################################################################ - - ############################################################################ - # HTML_dpng uses an ImageGenerator. We have to render the queued equations. - ############################################################################ - my $body_text_ref = $translator->r_text; - if ($image_generator) { - my $sourceFile = - $ce->{courseDirs}->{templates} . "/" . $problem->source_file; - my %mtimeOption = - -e $sourceFile ? ( mtime => ( stat $sourceFile )[9] ) : (); - - $image_generator->render( - refresh => $translationOptions->{refreshMath2img}, - %mtimeOption, - body_text => $body_text_ref, - ); - } - - ############################################################################ - # send any queued mail messages - ############################################################################ - - # if ($mailer) { - # $mailer->send_messages; - # } - - ############################################################################ - # end of cleanup phase - ############################################################################ - - ############################################################################ - # write timing log entry - ############################################################################ - # writeTimingLogEntry($ce, "WeBWorK::PG::new", "", "end"); - - ############################################################################ - # return an object which contains the translator and the results of - # the translation process. - ############################################################################ - - return bless { - translator => $translator, - head_text => ${ $translator->r_header }, - post_header_text => ${ $translator->r_post_header }, - body_text => ${$body_text_ref}, # from $translator->r_text - answers => $translator->rh_evaluated_answers, - result => $result, - state => $state, - errors => $translator->errors, - warnings => $warnings, - flags => $translator->rh_flags, - pgcore => $translator->{rh_pgcore}, - }, $class; -} - -1; - -__END__ - -=head1 OPERATION - -WeBWorK::PG::Local goes through the following operations when constructed: - -=over - -=item Create a translator - -Instantiate a WeBWorK::PG::Translator object. - -=item Set the directory hash - -Set the translator's directory hash (courseScripts, macros, templates, and temp -directories) from the course environment. - -=item Evaluate PG modules - -Using the module list from the course environment (pg->modules), perform a -"use"-like operation to evaluate modules at runtime. - -=item Set the problem environment - -Use data from the user, set, and problem, as well as the course -environemnt and translation options, to set the problem environment. The -default subroutine, &WeBWorK::PG::defineProblemEnvir, is used. - -=item Initialize the translator - -Call &WeBWorK::PG::Translator::initialize. What more do you want? - -=item Load IO.pl, PG.pl and dangerousMacros.pl - -These macros must be loaded without opcode masking, so they are loaded here. - -=item Set the opcode mask - -Set the opcode mask to the default specified by WeBWorK::PG::Translator. - -=item Load the problem source - -Give the problem source to the translator. - -=item Install a safety filter - -The safety filter is used to preprocess student input before evaluation. The -default safety filter, &WeBWorK::PG::safetyFilter, is used. - -=item Translate the problem source - -Call &WeBWorK::PG::Translator::translate to render the problem source into the -format given by the display mode. - -=item Process student answers - -Use form field inputs to evaluate student answers. - -=item Load the problem state - -Use values from the database to initialize the problem state, so that the -grader will have a point of reference. - -=item Determine an entry order - -Use the ANSWER_ENTRY_ORDER flag to determine the order of answers in the -problem. This is important for problems with dependancies among parts. - -=item Install a grader - -Use the PROBLEM_GRADER_TO_USE flag, or a default from the course environment, -to install a grader. - -=item Grade the problem - -Use the selected grader to grade the problem. - -=back - -=head1 AUTHOR - -Written by Sam Hathaway, sh002i (at) math.rochester.edu. - -=cut diff --git a/lib/WeBWorK/lib/WeBWorK/Utils.pm b/lib/WeBWorK/lib/WeBWorK/Utils.pm deleted file mode 100644 index fb15f06c3..000000000 --- a/lib/WeBWorK/lib/WeBWorK/Utils.pm +++ /dev/null @@ -1,292 +0,0 @@ -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/Utils.pm,v 1.83 2009/07/12 23:48:00 gage Exp $ -# -# 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 WeBWorK::Utils; -use base qw(Exporter); - -use strict; -use warnings; -use DateTime; -use DateTime::TimeZone; -use Date::Format; -use Encode qw(encode_utf8 decode_utf8); -use File::Spec::Functions qw(canonpath); - -use constant DATE_FORMAT => "%m/%d/%Y at %I:%M%P %Z"; -use constant MKDIR_ATTEMPTS => 10; - -our @EXPORT = (); -our @EXPORT_OK = qw( - wwRound - undefstr - runtime_use - formatDateTime - makeTempDirectory - readFile - writeTimingLogEntry - constituency_hash - writeLog - surePathToFile - path_is_subdir - getAssetURL -); - -sub force_eoln($) { - my ($string) = @_; - $string = $string//''; - $string =~ s/\015\012?/\012/g; - return $string; -} - -sub writeLog($$@) { - my ($ce, $facility, @message) = @_; - unless ($ce->{webworkFiles}->{logs}->{$facility}) { - warn "There is no log file for the $facility facility defined.\n"; - return; - } - my $logFile = $ce->{webworkFiles}->{logs}->{$facility}; - surePathToFile($ce->{webworkDirs}->{root}, $logFile); - local *LOG; - if (open LOG, ">>", $logFile) { - print LOG "[", time2str("%a %b %d %H:%M:%S %Y", time), "] @message\n"; - close LOG; - } else { - warn "failed to open $logFile for writing: $!"; - } -} - -sub surePathToFile($$) { - # constructs intermediate directories enroute to the file - # the input path must be the path relative to this starting directory - my $start_directory = shift; - my $path = shift; - my $delim = "/"; - unless ($start_directory and $path ) { - warn "missing directory
surePathToFile start_directory path "; - return ''; - } - # use the permissions/group on the start directory itself as a template - my ($perms, $groupID) = (stat $start_directory)[2,5]; - # warn "&urePathToTmpFile: perms=$perms groupID=$groupID\n"; - - # if the path starts with $start_directory (which is permitted but optional) remove this initial segment - $path =~ s|^$start_directory|| if $path =~ m|^$start_directory|; - - - # find the nodes on the given path - my @nodes = split("$delim",$path); - - # create new path - $path = $start_directory; #convertPath("$tmpDirectory"); - - while (@nodes>1) { # the last node is the file name - $path = $path . shift (@nodes) . "/"; #convertPath($path . shift (@nodes) . "/"); - #FIXME this make directory command may not be fool proof. - unless (-e $path) { - mkdir($path, $perms) - or warn "Failed to create directory $path with start directory $start_directory "; - } - - } - - $path = $path . shift(@nodes); #convertPath($path . shift(@nodes)); - return $path; -} - -sub constituency_hash { - my $hash = {}; - @$hash{@_} = (); - return $hash; -} - -sub wwRound(@) { -# usage wwRound($places,$float) -# return $float rounded up to number of decimal places given by $places - my $places = shift; - my $float = shift; - my $factor = 10**$places; - return int($float*$factor+0.5)/$factor; -} - -sub undefstr($@) { - map { defined $_ ? $_ : $_[0] } @_[1..$#_]; -} - -sub runtime_use($;@) { - my ($module, @import_list) = @_; - my $package = (caller)[0]; # import into caller's namespace - - my $import_string; - if (@import_list == 1 and ref $import_list[0] eq "ARRAY" and @{$import_list[0]} == 0) { - $import_string = ""; - } else { - # \Q = quote metachars \E = end quoting - $import_string = "import $module " . join(",", map { qq|"\Q$_\E"| } @import_list); - } - eval "package $package; require $module; $import_string"; - die $@ if $@; -} - -sub formatDateTime($;$;$;$) { - my ($dateTime, $display_tz, $format_string, $locale) = @_; - warn "Utils::formatDateTime is not a method. ", join(" ",caller(2)) if ref($dateTime); # catch bad calls to Utils::formatDateTime - warn "not defined formatDateTime('$dateTime', '$display_tz') ",join(" ",caller(2)) unless $display_tz; - $dateTime = $dateTime ||0; # do our best to provide default values - $display_tz ||= "local"; # do our best to provide default vaules - $display_tz = verify_timezone($display_tz); - - $format_string ||= DATE_FORMAT; # If a format is not provided, use the default WeBWorK date format - my $dt; - if($locale) { - $dt = DateTime->from_epoch(epoch => $dateTime, time_zone => $display_tz, locale=>$locale); - } - else { - $dt = DateTime->from_epoch(epoch => $dateTime, time_zone => $display_tz); - } - #warn "\t\$dt = ", $dt->strftime(DATE_FORMAT), "\n"; - return $dt->strftime($format_string); -} - -sub makeTempDirectory($$) { - my ($parent, $basename) = @_; - # Loop until we're able to create a directory, or it fails for some - # reason other than there already being something there. - my $triesRemaining = MKDIR_ATTEMPTS; - my ($fullPath, $success); - do { - my $suffix = join "", map { ('A'..'Z','a'..'z','0'..'9')[int rand 62] } 1 .. 8; - $fullPath = "$parent/$basename.$suffix"; - $success = mkdir $fullPath; - } until ($success or not $!{EEXIST}); - die "Failed to create directory $fullPath: $!" - unless $success; - return $fullPath; -} - -sub readFile($) { - my $fileName = shift; - # debugging code: found error in CourseEnvironment.pm with this -# if ($fileName =~ /___/ or $fileName =~ /the-course-should-be-determined-at-run-time/) { -# print STDERR "File $fileName not found.\n Usually an unnecessary call to readFile from\n", -# join("\t ", caller()), "\n"; -# return(); -# } - local $/ = undef; # slurp the whole thing into one string - my $result=''; # need this initialized because the file (e.g. simple.conf) may not exist - if (-r $fileName) { - eval{ - # CODING WARNING: - # if (open my $dh, "<", $fileName){ - # will cause a utf8 "\xA9" does not map to Unicode warning if © is in latin-1 file - # use the following instead - if (open my $dh, "<:raw", $fileName){ - $result = <$dh>; - decode_utf8($result) or die "failed to decode $fileName"; - close $dh; - } else { - print STDERR "File $fileName cannot be read."; # this is not a fatal error. - } - }; - if ($@) { - print STDERR "reading $fileName: error in Utils::readFile: $@\n"; - } - my $prevent_error_message = utf8::decode($result) or warn "Non-fatal warning: file $fileName contains at least one character code which ". - "is not valid in UTF-8. (The copyright sign is often a culprit -- use '&copy;' instead.)\n". - "While this is not fatal you should fix it\n"; - # FIXME - # utf8::decode($result) raises an error about the copyright sign - # decode_utf8 and Encode::decode_utf8 do not -- which is doing the right thing? - } - # returns the empty string if the file cannot be read - return force_eoln($result); -} - -sub writeTimingLogEntry($$$$) { - my ($ce, $function, $details, $beginEnd) = @_; - $beginEnd = ($beginEnd eq "begin") ? ">" : ($beginEnd eq "end") ? "<" : "-"; - writeLog($ce, "timing", "$$ ".time." $beginEnd $function [$details]"); -} - -sub path_is_subdir($$;$) { - my ($path, $dir, $allow_relative) = @_; - - unless ($path =~ /^\//) { - if ($allow_relative) { - $path = "$dir/$path"; - } else { - return 0; - } - } - - $path = canonpath($path); - $path .= "/" unless $path =~ m|/$|; - return 0 if $path =~ m#(^\.\.$|^\.\./|/\.\./|/\.\.$)#; - - $dir = canonpath($dir); - $dir .= "/" unless $dir =~ m|/$|; - return 0 unless $path =~ m|^$dir|; - - return 1; -} - -my $staticPGAssets; - -# Get the url for static assets. -sub getAssetURL { - my ($language, $file, $isThemeFile) = @_; - - # Load the static files list generated by `npm install` the first time this method is called. - if (!$staticPGAssets) { - my $staticAssetsList = "$WeBWorK::Constants::PG_DIRECTORY/htdocs/static-assets.json"; - if (-r $staticAssetsList) { - my $data = do { - open(my $fh, "<:encoding(UTF-8)", $staticAssetsList) - or die "FATAL: Unable to open '$staticAssetsList'!"; - local $/; - <$fh>; - }; - - $staticPGAssets = JSON->new->decode($data); - } else { - warn "ERROR: '$staticAssetsList' not found!\n" - . "You may need to run 'npm install' from '$WeBWorK::Constants::PG_DIRECTORY/htdocs'."; - } - } - - # If a right-to-left language is enabled (Hebrew or Arabic) and this is a css file that is not a third party asset, - # then determine the rtl varaint file name. This will be looked for first in the asset lists. - my $rtlfile = $file =~ s/\.css$/.rtl.css/r - if ($language =~ /^(he|ar)/ && $file !~ /node_modules/ && $file =~ /\.css$/); - - # Now check to see if this is a file in the pg htdocs location with a rtl variant. - # These also can only be local files. - return "/pg_files/$staticPGAssets->{$rtlfile}" if defined $rtlfile && defined $staticPGAssets->{$rtlfile}; - - # Next check to see if this is a file in the pg htdocs location. - if (defined $staticPGAssets->{$file}) { - # File served by cdn. - return $staticPGAssets->{$file} if $staticPGAssets->{$file} =~ /^https?:\/\//; - # File served locally. - return "/pg_files/$staticPGAssets->{$file}"; - } - - # If the file was not found in the lists, then just use the given file and assume its path is relative to the pg - # htdocs location. - return "/pg_files/$file"; -} - - -1; diff --git a/lib/WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm b/lib/WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm deleted file mode 100644 index 7667777b3..000000000 --- a/lib/WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm +++ /dev/null @@ -1,462 +0,0 @@ -#!/usr/bin/perl -w -use 5.010; - -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2021 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 - - AttemptsTable - -=head1 SYNPOSIS - - my $tbl = WeBWorK::Utils::AttemptsTable->new( - $answers, - answersSubmitted => 1, - answerOrder => $pg->{flags}->{ANSWER_ENTRY_ORDER}, - displayMode => 'MathJax', - showAnswerNumbers => 0, - showAttemptAnswers => $showAttemptAnswers && $showEvaluatedAnswers, - showAttemptPreviews => $showAttemptPreview, - showAttemptResults => $showAttemptResults, - showCorrectAnswers => $showCorrectAnswers, - showMessages => $showAttemptAnswers, # internally checks for messages - showSummary => $showSummary, - imgGen => $imgGen, # not needed if ce is present , - ce => '', # not needed if $imgGen is present - maketext => WeBWorK::Localize::getLoc("en"), - ); - $tbl->{imgGen}->render(refresh => 1) if $tbl->displayMode eq 'images'; - my $answerTemplate = $tbl->answerTemplate; - # this also collects the correct_ids and incorrect_ids - $self->{correct_ids} = $tbl->correct_ids; - $self->{incorrect_ids} = $tbl->incorrect_ids; - - -=head1 DESCRIPTION -This module handles the formatting of the table which presents the results of analyzing a student's -answer to a WeBWorK problem. It is used in Problem.pm, OpaqueServer.pm, standAlonePGproblemRender - -=head2 new - - my $tbl = WeBWorK::Utils::AttemptsTable->new( - $answers, - answersSubmitted => 1, - answerOrder => $pg->{flags}->{ANSWER_ENTRY_ORDER}, - displayMode => 'MathJax', - showHeadline => 1, - showAnswerNumbers => 0, - showAttemptAnswers => $showAttemptAnswers && $showEvaluatedAnswers, - showAttemptPreviews => $showAttemptPreview, - showAttemptResults => $showAttemptResults, - showCorrectAnswers => $showCorrectAnswers, - showMessages => $showAttemptAnswers, # internally checks for messages - showSummary => $showSummary, - imgGen => $imgGen, # not needed if ce is present , - ce => '', # not needed if $imgGen is present - maketext => WeBWorK::Localize::getLoc("en"), - summary =>'', - ); - - $answers -- a hash of student answers e.g. $pg->{answers} - answersSubmitted if 0 then then the attemptsTable is not displayed (???) - answerOrder -- an array indicating the order the answers appear on the page. - displayMode 'MathJax' and 'images' are the most common - - showHeadline Show the header line 'Results for this submission' - - showAnswerNumbers, showAttemptAnswers, showAttemptPreviews,showAttemptResults, - showCorrectAnswers and showMessages control the display of each column in the table. - - attemptAnswers the student's typed in answer (possibly simplified numerically) - attemptPreview the student's answer after typesetting - attemptResults "correct", "_% correct", "incorrect" or "ungraded"- links to the answer blank - correctAnswers typeset version (untypeset versions are available via popups) - messages warns of formatting typos in the answer, or - more detailed messages about a wrong answer - summary is obtained from $pg->{result}->{summary}. - If this is empty then a (localized) - version of "all answers are correct" - or "at least one answer is not coorrect" - imgGen points to a prebuilt image generator objectfor "images" mode - ce points to the CourseEnvironment -- it is needed if AttemptsTable - is required to build its own imgGen object - maketext points to a localization subroutine - - - - -=head2 Methods - -=over 4 - -=item answerTemplate - -Returns HTML which formats the analysis of the student's answers to the problem. - -=back - -=head2 Read/Write Properties - -=over 4 - -=item correct_ids, incorrect_ids, - -These are references to lists of the ids of the correct answers and the incorrect answers respectively. - -=item showMessages, - -This can be switched on or off before exporting the answerTemplate, perhaps under instructions - from the PG problem. - -=item summary - -The contents of the summary can be defined when the attemptsTable object is created. - -The summary can be defined by the PG problem grader -usually returned as $pg->{result}->{summary}. - -If the summary is not explicitly defined then (localized) versions -of the default summaries are created: - - "The answer above is correct.", - "Some answers will be graded later.", - "All of the [gradeable] answers above are correct.", - "[N] of the questions remain unanswered.", - "At least one of the answers above is NOT [fully] correct.', - -=back - -=cut - -package WeBWorK::Utils::AttemptsTable; -use base qw(Class::Accessor); - -use strict; -use warnings; -use Scalar::Util 'blessed'; -use WeBWorK::Utils 'wwRound'; -use CGI; - -# Object contains hash of answer results -# Object contains display mode -# Object contains or creates Image generator -# object returns table - -sub new { - my $class = shift; - $class = (ref($class))? ref($class) : $class; # create a new object of the same class - my $rh_answers = shift; - ref($rh_answers) =~/HASH/ or die "The first entry to AttemptsTable must be a hash of answers"; - my %options = @_; # optional: displayMode=>, submitted=>, imgGen=>, ce=> - my $self = { - answers => $rh_answers // {}, - answerOrder => $options{answerOrder} // [], - answersSubmitted => $options{answersSubmitted} // 0, - summary => $options{summary} // '', # summary provided by problem grader - displayMode => $options{displayMode} || "MathJax", - showHeadline => $options{showHeadline} // 1, - showAnswerNumbers => $options{showAnswerNumbers} // 1, - showAttemptAnswers => $options{showAttemptAnswers} // 1, # show student answer as entered and simplified - # (e.g numerical formulas are calculated to produce numbers) - showAttemptPreviews => $options{showAttemptPreviews} // 1, # show preview of student answer - showAttemptResults => $options{showAttemptResults} // 1, # show whether student answer is correct - showMessages => $options{showMessages} // 1, # show any messages generated by evaluation - showCorrectAnswers => $options{showCorrectAnswers} // 1, # show the correct answers - showSummary => $options{showSummary} // 1, # show summary to students - maketext => $options{maketext} // sub {return @_}, # pointer to the maketext subroutine - imgGen => undef, # created in _init method - }; - bless $self, $class; - # create read only accessors/mutators - $self->mk_ro_accessors(qw(answers answerOrder answersSubmitted displayMode imgGen maketext)); - $self->mk_ro_accessors(qw(showAnswerNumbers showAttemptAnswers showHeadline - showAttemptPreviews showAttemptResults - showCorrectAnswers showSummary)); - $self->mk_accessors(qw(correct_ids incorrect_ids showMessages summary)); - # sanity check and initialize imgGenerator. - _init($self, %options); - return $self; -} - -sub _init { - # verify display mode - # build imgGen if it is not supplied - my $self = shift; - my %options = @_; - $self->{submitted}=$options{submitted}//0; - $self->{displayMode} = $options{displayMode} || "MathJax"; - # only show message column if there is at least one message: - my @reallyShowMessages = grep { $self->answers->{$_}->{ans_message} } @{$self->answerOrder}; - $self->showMessages( $self->showMessages && !!@reallyShowMessages ); - # (!! forces boolean scalar environment on list) - # only used internally -- don't need accessors. - $self->{numCorrect}=0; - $self->{numBlanks}=0; - $self->{numEssay}=0; - - if ( $self->displayMode eq 'images') { - if ( blessed( $options{imgGen} ) ) { - $self->{imgGen} = $options{imgGen}; - } elsif ( blessed( $options{ce} ) ) { - warn "building imgGen"; - my $ce = $options{ce}; - my $site_url = $ce->{server_root_url}; - my %imagesModeOptions = %{$ce->{pg}->{displayModeOptions}->{images}}; - - my $imgGen = WeBWorK::PG::ImageGenerator->new( - tempDir => $ce->{webworkDirs}->{tmp}, - latex => $ce->{externalPrograms}->{latex}, - dvipng => $ce->{externalPrograms}->{dvipng}, - useCache => 1, - cacheDir => $ce->{webworkDirs}->{equationCache}, - cacheURL => $site_url.$ce->{webworkURLs}->{equationCache}, - cacheDB => $ce->{webworkFiles}->{equationCacheDB}, - dvipng_align => $imagesModeOptions{dvipng_align}, - dvipng_depth_db => $imagesModeOptions{dvipng_depth_db}, - ); - $self->{imgGen} = $imgGen; - } else { - warn "Must provide image Generator (imgGen) or a course environment (ce) to build attempts table."; - } - } -} - -sub maketext { - my $self = shift; -# Uncomment to check that strings are run through maketext -# return 'xXx'.&{$self->{maketext}}(@_).'xXx'; - return &{$self->{maketext}}(@_); -} -sub formatAnswerRow { - my $self = shift; - my $rh_answer = shift; - my $ans_id = shift; - my $answerNumber = shift; - my $answerString = $rh_answer->{student_ans}//''; - # use student_ans and not original_student_ans above. student_ans has had HTML entities translated to prevent XSS. - my $answerPreview = $self->previewAnswer($rh_answer)//' '; - my $correctAnswer = $rh_answer->{correct_ans}//''; - my $correctAnswerPreview = $self->previewCorrectAnswer($rh_answer)//' '; - - my $answerMessage = $rh_answer->{ans_message}//''; - $answerMessage =~ s/\n/
/g; - my $answerScore = $rh_answer->{score}//0; - $self->{numCorrect} += $answerScore >=1; - $self->{numEssay} += ($rh_answer->{type}//'') eq 'essay'; - $self->{numBlanks}++ unless $answerString =~/\S/ || $answerScore >= 1; - - my $feedbackMessageClass = ($answerMessage eq "") ? "" : $self->maketext("FeedbackMessage"); - - my (@correct_ids, @incorrect_ids); - my $resultString; - my $resultStringClass; - if ($answerScore >= 1) { - $resultString = $self->maketext("correct"); - $resultStringClass = "ResultsWithoutError"; - } elsif (($rh_answer->{type} // '') eq 'essay') { - $resultString = $self->maketext("Ungraded"); - $self->{essayFlag} = 1; - } elsif (defined($answerScore) and $answerScore == 0) { - $resultStringClass = "ResultsWithError"; - $resultString = $self->maketext("incorrect"); - } else { - $resultString = $self->maketext("[_1]% correct", wwRound(0, $answerScore * 100)); - } - my $attemptResults = CGI::td({ class => $resultStringClass }, - CGI::a({ href => '#', data_answer_id => $ans_id }, $self->nbsp($resultString))); - - my $row = join('', - ($self->showAnswerNumbers) ? CGI::td({},$answerNumber):'', - ($self->showAttemptAnswers) ? CGI::td({dir=>"auto"},$self->nbsp($answerString)):'' , # student original answer - ($self->showAttemptPreviews)? $self->formatToolTip($answerString, $answerPreview):"" , - ($self->showAttemptResults)? $attemptResults : '' , - ($self->showCorrectAnswers)? $self->formatToolTip($correctAnswer,$correctAnswerPreview):"" , - ($self->showMessages)? CGI::td({class=>$feedbackMessageClass},$self->nbsp($answerMessage)):"", - "\n" - ); - $row; -} - -##################################################### -# determine whether any answers were submitted -# and create answer template if they have been -##################################################### - -sub answerTemplate { - my $self = shift; - my $rh_answers = $self->{answers}; - my @tableRows; - my @correct_ids; - my @incorrect_ids; - - push @tableRows,CGI::Tr( - ($self->showAnswerNumbers) ? CGI::th("#"):'', - ($self->showAttemptAnswers)? CGI::th($self->maketext("Entered")):'', # student original answer - ($self->showAttemptPreviews)? CGI::th($self->maketext("Answer Preview")):'', - ($self->showAttemptResults)? CGI::th($self->maketext("Result")):'', - ($self->showCorrectAnswers)? CGI::th($self->maketext("Correct Answer")):'', - ($self->showMessages)? CGI::th($self->maketext("Message")):'', - ); - - my $answerNumber = 1; - foreach my $ans_id (@{ $self->answerOrder() }) { - push @tableRows, CGI::Tr($self->formatAnswerRow($rh_answers->{$ans_id}, $ans_id, $answerNumber++)); - push @correct_ids, $ans_id if ($rh_answers->{$ans_id}->{score}//0) >= 1; - push @incorrect_ids, $ans_id if ($rh_answers->{$ans_id}->{score}//0) < 1; - #$self->{essayFlag} = 1; - } - my $answerTemplate = ""; - $answerTemplate .= CGI::h3({ class => 'attemptResultsHeader' }, $self->maketext("Results for this submission")) - if $self->showHeadline; - $answerTemplate .= CGI::table({ class => 'attemptResults table table-sm table-bordered' }, @tableRows); - ### "results for this submission" is better than "attempt results" for a headline - $answerTemplate .= ($self->showSummary)? $self->createSummary() : ''; - $answerTemplate = "" unless $self->answersSubmitted; # only print if there is at least one non-blank answer - $self->correct_ids(\@correct_ids); - $self->incorrect_ids(\@incorrect_ids); - $answerTemplate; -} -################################################# - -sub previewAnswer { - my $self =shift; - my $answerResult = shift; - my $displayMode = $self->displayMode; - my $imgGen = $self->imgGen; - - # note: right now, we have to do things completely differently when we are - # rendering math from INSIDE the translator and from OUTSIDE the translator. - # so we'll just deal with each case explicitly here. there's some code - # duplication that can be dealt with later by abstracting out dvipng/etc. - - my $tex = $answerResult->{preview_latex_string}; - - return "" unless defined $tex and $tex ne ""; - - return $tex if $answerResult->{non_tex_preview}; - - if ($displayMode eq "plainText") { - return $tex; - } elsif (($answerResult->{type}//'') eq 'essay') { - return $tex; - } elsif ($displayMode eq "images") { - $imgGen->add($tex); - } elsif ($displayMode eq "MathJax") { - return ''; - } -} - -sub previewCorrectAnswer { - my $self =shift; - my $answerResult = shift; - my $displayMode = $self->displayMode; - my $imgGen = $self->imgGen; - - my $tex = $answerResult->{correct_ans_latex_string}; - return $answerResult->{correct_ans} unless defined $tex and $tex=~/\S/; # some answers don't have latex strings defined - # return "" unless defined $tex and $tex ne ""; - - return $tex if $answerResult->{non_tex_preview}; - - if ($displayMode eq "plainText") { - return $tex; - } elsif ($displayMode eq "images") { - $imgGen->add($tex); - # warn "adding $tex"; - } elsif ($displayMode eq "MathJax") { - return ''; - } -} - -########################################### -# Create summary -########################################### -sub createSummary { - my $self = shift; - my $summary = ""; - my $numCorrect = $self->{numCorrect}; - my $numBlanks = $self->{numBlanks}; - my $numEssay = $self->{numEssay}; - - unless (defined($self->summary) and $self->summary =~ /\S/) { - my @answerNames = @{ $self->answerOrder() }; - if (scalar @answerNames == 1) { #default messages - if ($numCorrect == scalar @answerNames) { - $summary .= - CGI::div({ class => 'ResultsWithoutError mb-2' }, $self->maketext('The answer above is correct.')); - } elsif ($self->{essayFlag}) { - $summary .= CGI::div($self->maketext('Some answers will be graded later.')); - } else { - $summary .= - CGI::div({ class => 'ResultsWithError mb-2' }, $self->maketext('The answer above is NOT correct.')); - } - } else { - if ($numCorrect + $numEssay == scalar @answerNames) { - if ($numEssay) { - $summary .= CGI::div({ class => 'ResultsWithoutError mb-2' }, - $self->maketext('All of the gradeable answers above are correct.')); - } else { - $summary .= CGI::div({ class => 'ResultsWithoutError mb-2' }, - $self->maketext('All of the answers above are correct.')); - } - } elsif ($numBlanks + $numEssay != scalar(@answerNames)) { - $summary .= CGI::div({ class => 'ResultsWithError mb-2' }, - $self->maketext('At least one of the answers above is NOT correct.')); - } - if ($numBlanks > $numEssay) { - my $s = ($numBlanks > 1) ? '' : 's'; - $summary .= CGI::div( - { class => 'ResultsAlert mb-2' }, - $self->maketext( - '[quant,_1,of the questions remains,of the questions remain] unanswered.', $numBlanks - ) - ); - } - } - } else { - $summary = $self->summary; # summary has been defined by grader - } - $summary = CGI::div({role=>"alert", class=>"attemptResultsSummary"}, - $summary); - $self->summary($summary); - return $summary; # return formatted version of summary in class "attemptResultsSummary" div -} -################################################ - -############################################ -# utility subroutine -- prevents unwanted line breaks -############################################ -sub nbsp { - my ($self, $str) = @_; - return (defined $str && $str =~/\S/) ? $str : " "; -} - -# note that formatToolTip output includes CGI::td wrapper -sub formatToolTip { - my $self = shift; - my $answer = shift; - my $formattedAnswer = shift; - return CGI::td(CGI::span({ - class => "answer-preview", - data_bs_toggle => "popover", - data_bs_content => $answer, - data_bs_placement => "bottom", - }, - $self->nbsp($formattedAnswer)) - ); -} - -1; diff --git a/lib/WeBWorK/lib/WeBWorK/Utils/DelayedMailer.pm b/lib/WeBWorK/lib/WeBWorK/Utils/DelayedMailer.pm deleted file mode 100644 index c4127bace..000000000 --- a/lib/WeBWorK/lib/WeBWorK/Utils/DelayedMailer.pm +++ /dev/null @@ -1,167 +0,0 @@ -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/Utils/DelayedMailer.pm,v 1.2 2007/08/13 22:59:59 sh002i Exp $ -# -# 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 should be expendable - -package WeBWorK::Utils::DelayedMailer; - -use strict; -use warnings; -use Carp; -use Net::SMTP; -use WeBWorK::Utils qw/constituency_hash/; - -sub new { - my ($invocant, %options) = @_; - my $class = ref $invocant || $invocant; - my $self = bless {}, $class; - - # messages get queued here. format: hashref, safe arguments to MailMsg - $$self{msgs} = []; - - # SMTP settings - $$self{smtp_server} = $options{smtp_server}; - $$self{smtp_sender} = $options{smtp_sender}; - $$self{smtp_timeout} = $options{smtp_timeout}; - - # extra headers - $$self{headers} = $options{headers}; - - # recipients are checked against this list before sending - # these should be bare rfc822 addresses, not "Name " - $$self{allowed_recipients} = constituency_hash(@{$options{allowed_recipients}}); - - # what to do if an illegal recipient is specified - # "croak" (default), "carp", or "ignore" - $$self{on_illegal_rcpt} = $options{on_illegal_rcpt}; - - return $self; -} - -# %msg format: -# $msg{to} = either a single address or an arrayref containing multiple addresses -# $msg{subject} = string subject -# $msg{msg} = string body of email (this is what Email::Sender::MailMsg uses) -sub add_message { - my ($self, %msg) = @_; - - # make sure recipients are allowed - $msg{to} = $self->_check_recipients($msg{to}); - - push @{$$self{msgs}}, \%msg; -} - -sub _check_recipients { - my ($self, $rcpts) = @_; - my @rcpts = ref $rcpts eq "ARRAY" ? @$rcpts : $rcpts; - - my @legal; - foreach my $rcpt (@rcpts) { - my ($base) = $rcpt =~ /<([^<>]*)>\s*$/; # works for addresses generated by Record::User - $base ||= $rcpt; # if it doesn't match, it's a plain address - if (exists $$self{allowed_recipients}{$base}) { - push @legal, $rcpt; - } else { - if (not defined $$self{on_illegal_rcpt} or $$self{on_illegal_rcpt} eq "croak") { - die "can't address message to illegal recipient '$rcpt'"; - } elsif ($$self{on_illegal_rcpt} eq "carp") { - warn "can't address message to illegal recipient '$rcpt'"; - } - } - } - - return \@legal; -} - -sub send_messages { - my ($self) = @_; - - return unless @{$$self{msgs}}; - - my $smtp = new Net::SMTP($$self{smtp_server}, Timeout=>$$self{smtp_timeout}) - or die "failed to create Net::SMTP object"; - - my @results; - foreach my $msg (@{$$self{msgs}}) { - push @results, $self->_send_msg($smtp, $msg); - } - - return @results; -} - -sub _send_msg { - my ($self, $smtp, $msg) = @_; - - my $sender = $$self{smtp_sender}; - my @recipients = @{$$msg{to}}; - my $message = $self->_format_msg($msg); - - # reduce "Foo " to "bar@bar" - foreach my $rcpt (@recipients) { - my ($base) = $rcpt =~ /<([^<>]*)>\s*$/; - $rcpt = $base if defined $base; - } - - my %result; - - $smtp->mail($sender); - my @good_rcpts = $smtp->recipient(@recipients, {SkipBad=>1}); - if (@good_rcpts) { - my $data_sent = $smtp->data($message); - unless ($data_sent) { - $result{error} = "(Error number not available with Net::SMTP)"; - $result{error_msg} = "Unknown error sending message data to SMTP server"; - } - } else { - $result{error} = "(Error number not available with Net::SMTP)"; - $result{error_msg} = "No recipient addresses were accepted by SMTP server"; - } - - # figure out which recipients were rejected - my %bad_rcpts; - @bad_rcpts{@recipients} = (); - delete @bad_rcpts{@good_rcpts}; - my @bad_rcpts = keys %bad_rcpts; - if (@bad_rcpts) { - $result{skipped_recipients} = - { map { $_ => "(Server message not available with Net::SMTP)" } @bad_rcpts }; - } - - return \%result; -} - -sub _format_msg { - my ($self, $msg) = @_; - - my $from = $$self{smtp_sender}; - my $to = join(", ", @{$$msg{to}}); - my $subject = $$msg{subject}; - my $headers = $$self{headers}; - my $body = $$msg{msg}; - - my $formatted_msg = "From: $from\n" - . "To: $to\n" - . "Subject: $subject\n"; - if (defined $headers) { - $formatted_msg .= $headers; - $formatted_msg .= "\n" unless $formatted_msg =~ /\n$/; - } - $formatted_msg .= "\n$body"; - - return $formatted_msg; -} - -1; diff --git a/lib/WeBWorK/lib/WeBWorK/Utils/RestrictedClosureClass.pm b/lib/WeBWorK/lib/WeBWorK/Utils/RestrictedClosureClass.pm deleted file mode 100644 index 61e592e34..000000000 --- a/lib/WeBWorK/lib/WeBWorK/Utils/RestrictedClosureClass.pm +++ /dev/null @@ -1,116 +0,0 @@ -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/Utils/RestrictedClosureClass.pm,v 1.4 2007/08/10 00:27:14 sh002i Exp $ -# -# 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 WeBWorK::Utils::RestrictedClosureClass; - -=head1 NAME - -WeBWorK::Utils::RestrictedClosureClass - Protect instance data and only allow -calling of specified methods. - -=head1 SYNPOSIS - - package MyScaryClass; - - sub new { return bless { @_[1..$#_] }, ref $_[0] || $_[0] } - sub get_secret { return $_[0]->{secret_data} } - sub set_secret { $_[0]->{secret_data} = $_[1] } - sub use_secret { print "Secret length is ".length($_[0]->get_secret) } - sub call_for_help { print "HELP!!" } - - package main; - use WeBWorK::Utils::RestrictedClosureClass; - - my $unlocked = new MyScaryClass(secret_data => "pErL iS gReAt"); - my $locked = new WeBWorK::Utils::RestrictedClosureClass($obj, qw/use_secret call_for_help/); - - $unlocked->get_secret; # OK - $unlocked->set_secret("fOoBaR"); # OK - $unlocked->use_secret; # OK - $unlocked->call_for_help; # OK - print $unlocked->{secret_data}; # OK - $unlocked->{secret_data} = "WySiWyG"; # OK - - $locked->get_secret; # NG (not in method list) - $locked->set_secret("fOoBaR"); # NG (not in method list) - $locked->use_secret; # OK - $locked->call_for_help; # OK - print $locked->{secret_data}; # NG (not a hash reference) - $locked->{secret_data} = "WySiWyG"; # NG (not a hash reference) - -=head1 DESCRIPTION - -RestrictedClosureClass generates a wrapper object for a given object that -prevents access to the objects instance data and only allows specified method -calls. The wrapper object is a closure that calls methods of the underlying -object, if permitted. - -This is great for exposing a limited API to an untrusted environment, i.e. the -PG Safe compartment. - -=head1 CONSTRUCTOR - -=over - -=item $wrapper_object = CLASS->new($object, @methods) - -Generate a wrapper object for the given $object. Only calls to the methods -listed in @methods will be permitted. - -=back - -=head1 LIMITATIONS - -You can't call SUPER methods, or methods with an explicit class given: - - $locked->SUPER::call_for_help # NG, would be superclass of RestrictedClosureClass - -=head1 SEE ALSO - -L - -=cut - -use strict; -use warnings; -use Carp; -use Scalar::Util qw/blessed/; - -sub new { - my ($invocant, $object, @methods) = @_; - croak "wrapper class with no methods is dumb" unless @methods; - my $class = ref $invocant || $invocant; - croak "object is not a blessed reference" unless blessed $object; - my %methods; @methods{@methods} = (); - my $self = sub { # CLOSURE over $object, %methods; - my $method = shift; - if (not exists $methods{$method}) { - croak "Can't locate object method \"$method\" via package \"".ref($object)."\" fnord"; - } - return $object->$method(@_); - }; - return bless $self, $class; -} - -sub AUTOLOAD { - my $self = shift; - my $name = our $AUTOLOAD; - $name =~ s/.*:://; - return if $name eq "DESTROY"; # real obj's DESTROY method called when closure goes out of scope - return $self->($name, @_); -} - -1; diff --git a/lib/WeBWorK/lib/WebworkClient/classic_format.pl b/lib/WeBWorK/lib/WebworkClient/classic_format.pl deleted file mode 100644 index 7b3fed610..000000000 --- a/lib/WeBWorK/lib/WebworkClient/classic_format.pl +++ /dev/null @@ -1,66 +0,0 @@ -$simple_format = <<'ENDPROBLEMTEMPLATE'; - - - - - - - - - - - - -$extra_css_files - - - - - - - - - - - - -$extra_js_files - -$problemHeadText -$problemPostHeaderText - -WeBWorK using host: $SITE_URL, format: simple seed: $problemSeed - - -
-
-
- $answerTemplate -
-
- $problemText -
- $scoreSummary - $LTIGradeMessage - - - - - - - - -

- - -

-
-
-
-
- - - -ENDPROBLEMTEMPLATE - -$simple_format; diff --git a/lib/WeBWorK/lib/WebworkClient/json_format.pl b/lib/WeBWorK/lib/WebworkClient/json_format.pl deleted file mode 100644 index 7e57099f7..000000000 --- a/lib/WeBWorK/lib/WebworkClient/json_format.pl +++ /dev/null @@ -1,146 +0,0 @@ -# The json output format needs to collect the data differently than -# the other formats. It will return an array which alternates between -# key-names and values, and each relevant value will later undergo -# variable interpolation. - -# Most parts which need variable interpolation end in "_VI". -# Other parts which need variable interpolation are: -# hidden_input_field_* -# real_webwork_* - -@pairs_for_json = ( - "head_part001_VI", "\n" . '' . "\n" -); - -$nextBlock = <<'ENDPROBLEMTEMPLATE'; - - - - -ENDPROBLEMTEMPLATE - -push( @pairs_for_json, "head_part010", $nextBlock ); - -$nextBlock = <<'ENDPROBLEMTEMPLATE'; - - - - -ENDPROBLEMTEMPLATE - -push( @pairs_for_json, "head_part100", $nextBlock ); - -$nextBlock = <<'ENDPROBLEMTEMPLATE'; - - - - - - - - - - -ENDPROBLEMTEMPLATE - -push( @pairs_for_json, "head_part200", $nextBlock ); - -push( @pairs_for_json, "head_part300_VI", '$problemHeadText' . "\n" ); - -$nextBlock = <<'ENDPROBLEMTEMPLATE'; -WeBWorK problem -ENDPROBLEMTEMPLATE - -push( @pairs_for_json, "head_part400", $nextBlock ); - -push( @pairs_for_json, "head_part999", "\n" ); - -push( @pairs_for_json, "body_part001", "\n" ); - -$nextBlock = <<'ENDPROBLEMTEMPLATE'; -
-
-
-ENDPROBLEMTEMPLATE - -push( @pairs_for_json, "body_part100", $nextBlock ); - -push( @pairs_for_json, "body_part300_VI", '$answerTemplate' . "\n" ); - -$nextBlock = <<'ENDPROBLEMTEMPLATE'; -
-ENDPROBLEMTEMPLATE - -push( @pairs_for_json, "body_part500", $nextBlock ); - - -$nextBlock = <<'ENDPROBLEMTEMPLATE'; -
-ENDPROBLEMTEMPLATE - -push( @pairs_for_json, "body_part530_VI", $nextBlock ); - -push( @pairs_for_json, "body_part550_VI", '$problemText' . "\n" ); - -push( @pairs_for_json, "body_part590", "
\n" ); - -push( @pairs_for_json, "body_part650_VI", '$scoreSummary' . "\n" ); - -$nextBlock = <<'ENDPROBLEMTEMPLATE'; -

- - -ENDPROBLEMTEMPLATE - -push( @pairs_for_json, "body_part710_VI", $nextBlock ); - -$nextBlock = <<'ENDPROBLEMTEMPLATE'; - -ENDPROBLEMTEMPLATE - -push( @pairs_for_json, "body_part780_optional_VI", $nextBlock ); - -push( @pairs_for_json, "body_part790", "

\n" ); - -$nextBlock = <<'ENDPROBLEMTEMPLATE'; -
-
-
-
- - - -ENDPROBLEMTEMPLATE - -push( @pairs_for_json, "body_part999", $nextBlock ); - -push( @pairs_for_json, "hidden_input_field_answersSubmitted", '1' ); -push( @pairs_for_json, "hidden_input_field_sourceFilePath", '$sourceFilePath' ); -push( @pairs_for_json, "hidden_input_field_problemSource", '$encoded_source' ); -push( @pairs_for_json, "hidden_input_field_problemSeed", '$problemSeed' ); -push( @pairs_for_json, "hidden_input_field_problemUUID", '$problemUUID' ); -push( @pairs_for_json, "hidden_input_field_psvn", '$psvn' ); -push( @pairs_for_json, "hidden_input_field_pathToProblemFile", '$fileName' ); -push( @pairs_for_json, "hidden_input_field_courseName", '$courseID' ); -push( @pairs_for_json, "hidden_input_field_courseID", '$courseID' ); -push( @pairs_for_json, "hidden_input_field_userID", '$userID' ); -push( @pairs_for_json, "hidden_input_field_course_password", '$course_password' ); -push( @pairs_for_json, "hidden_input_field_displayMode", '$displayMode' ); -push( @pairs_for_json, "hidden_input_field_session_key", '$session_key' ); -push( @pairs_for_json, "hidden_input_field_outputFormat", 'json' ); -push( @pairs_for_json, "hidden_input_field_language", '$formLanguage' ); -push( @pairs_for_json, "hidden_input_field_showSummary", '$showSummary' ); -push( @pairs_for_json, "hidden_input_field_forcePortNumber", '$forcePortNumber' ); - -# These are the real WeBWorK server URLs which the intermediate needs to use -# to communicate with WW, while the distant client must use URLs of the -# intermediate server (the man in the middle). - -push( @pairs_for_json, "real_webwork_SITE_URL", '$SITE_URL' ); -push( @pairs_for_json, "real_webwork_FORM_ACTION_URL", '$FORM_ACTION_URL' ); -push( @pairs_for_json, "internal_problem_lang_and_dir", '$PROBLEM_LANG_AND_DIR'); - -# Output back to WebworkClient.pm is the reference to the array: -\@pairs_for_json; diff --git a/lib/WeBWorK/lib/WebworkClient/jwe_secure_format.pl b/lib/WeBWorK/lib/WebworkClient/jwe_secure_format.pl deleted file mode 100644 index 0bf30f52f..000000000 --- a/lib/WeBWorK/lib/WebworkClient/jwe_secure_format.pl +++ /dev/null @@ -1,66 +0,0 @@ -$jwe_format = <<'ENDPROBLEMTEMPLATE'; - - - - - - - - - - - - -$extra_css_files - - - - - - - - - - - - -$extra_js_files - -$problemHeadText -$problemPostHeaderText - -WeBWorK using host: $SITE_URL - - -
-
-
- $answerTemplate -
-
- $problemText -
- $scoreSummary - $LTIGradeMessage - - -

- - -

-
-
-
-
- - - - -ENDPROBLEMTEMPLATE - -$jwe_format; diff --git a/lib/WeBWorK/lib/WebworkClient/nosubmit_format.pl b/lib/WeBWorK/lib/WebworkClient/nosubmit_format.pl deleted file mode 100644 index fd67e5500..000000000 --- a/lib/WeBWorK/lib/WebworkClient/nosubmit_format.pl +++ /dev/null @@ -1,58 +0,0 @@ -$nosubmit_format = <<'ENDPROBLEMTEMPLATE'; - - - - - - - - - - - - -$extra_css_files - - - - - - - - - -$extra_js_files - -$problemHeadText -$problemPostHeaderText - -WeBWorK Standalone Renderer - - -
-
-
- $answerTemplate -
-
- $problemText -
- $scoreSummary - $LTIGradeMessage - - - - - - - -
-
-
-
- - - -ENDPROBLEMTEMPLATE - -$nosubmit_format; diff --git a/lib/WeBWorK/lib/WebworkClient/practice_format.pl b/lib/WeBWorK/lib/WebworkClient/practice_format.pl deleted file mode 100644 index d431f76c6..000000000 --- a/lib/WeBWorK/lib/WebworkClient/practice_format.pl +++ /dev/null @@ -1,65 +0,0 @@ -$simple_format = <<'ENDPROBLEMTEMPLATE'; - - - - - - - - - - - - -$extra_css_files - - - - - - - - - - - - -$extra_js_files - -$problemHeadText -$problemPostHeaderText - -WeBWorK using host: $SITE_URL, format: simple seed: $problemSeed - - -
-
-
- $answerTemplate -
-
- $problemText -
- $scoreSummary - $LTIGradeMessage - - - - - - - -

- - -

-
-
-
-
- - - -ENDPROBLEMTEMPLATE - -$simple_format; diff --git a/lib/WeBWorK/lib/WebworkClient/simple_format.pl b/lib/WeBWorK/lib/WebworkClient/simple_format.pl deleted file mode 100644 index 4aeb774ec..000000000 --- a/lib/WeBWorK/lib/WebworkClient/simple_format.pl +++ /dev/null @@ -1,67 +0,0 @@ -$simple_format = <<'ENDPROBLEMTEMPLATE'; - - - - - - - - - - - - -$extra_css_files - - - - - - - - - - - - -$extra_js_files - -$problemHeadText -$problemPostHeaderText - -WeBWorK using host: $SITE_URL, format: simple seed: $problemSeed - - -
-
-
- $answerTemplate -
-
- $problemText -
- $scoreSummary - $LTIGradeMessage - - - - - - - - -

- - - -

-
-
-
-
- - - -ENDPROBLEMTEMPLATE - -$simple_format; diff --git a/lib/WeBWorK/lib/WebworkClient/single_format.pl b/lib/WeBWorK/lib/WebworkClient/single_format.pl deleted file mode 100644 index d279547f3..000000000 --- a/lib/WeBWorK/lib/WebworkClient/single_format.pl +++ /dev/null @@ -1,64 +0,0 @@ -$single_format = <<'ENDPROBLEMTEMPLATE'; - - - - - - - - - - - - -$extra_css_files - - - - - - - - - - - -$extra_js_files - -$problemHeadText -$problemPostHeaderText - -WeBWorK Standalone Renderer - - -
-
-
- $answerTemplate -
-
- $problemText -
- $scoreSummary - $LTIGradeMessage - - - - - - - - -

- -

-
-
-
-
- - - -ENDPROBLEMTEMPLATE - -$single_format; diff --git a/lib/WeBWorK/lib/WebworkClient/standard_format.pl b/lib/WeBWorK/lib/WebworkClient/standard_format.pl deleted file mode 100644 index 5fb4c4f52..000000000 --- a/lib/WeBWorK/lib/WebworkClient/standard_format.pl +++ /dev/null @@ -1,96 +0,0 @@ -$standard_format = <<'ENDPROBLEMTEMPLATE'; - - - - - - - - - - - - -$extra_css_files - - - - - - - - - - - - -$extra_js_files - -$problemHeadText -$problemPostHeaderText - -WeBWorK using host: $SITE_URL, format: standard seed: $problemSeed course: $courseID - - - -

WeBWorK using host: $SITE_URL, course: $courseID format: standard

-$answerTemplate -
-
- $problemText -
- $scoreSummary - $LTIGradeMessage - - - - - - - - - - - - - - - - - - - - -

- Show:   -   -   -   -   -  
- - - - -

-
-
- -

Perl warning section

-$warnings -

PG Warning section

-$PG_warning_messages; -

Debug message section

-$debug_messages -

internal errors

-$internal_debug_messages - - - - - -ENDPROBLEMTEMPLATE - -$standard_format; diff --git a/lib/WeBWorK/lib/WebworkClient/static_format.pl b/lib/WeBWorK/lib/WebworkClient/static_format.pl deleted file mode 100644 index 680875c09..000000000 --- a/lib/WeBWorK/lib/WebworkClient/static_format.pl +++ /dev/null @@ -1,59 +0,0 @@ -$static_format = <<'ENDPROBLEMTEMPLATE'; - - - - - - - - - - - - -$extra_css_files - - - - - - - - - -$extra_js_files - -$problemHeadText -$problemPostHeaderText - -WeBWorK Standalone Renderer - - -
-
-
- $answerTemplate -
-
- $problemText -
- $scoreSummary - $LTIGradeMessage - - - - - - - - -
-
-
-
- - - -ENDPROBLEMTEMPLATE - -$static_format; diff --git a/lib/WeBWorK/lib/WebworkClient/ww3_format.pl b/lib/WeBWorK/lib/WebworkClient/ww3_format.pl deleted file mode 100644 index 97ba23b78..000000000 --- a/lib/WeBWorK/lib/WebworkClient/ww3_format.pl +++ /dev/null @@ -1,22 +0,0 @@ -{ - answerTemplate => '$answerTemplate', - scoreSummary => '$scoreSummary', - LTIGradeMessage => '$LTIGradeMessage', - - problemText => <<'ENDPROBLEMTEMPLATE' -$problemHeadText -
-
- $problemText -
- - - - - - - - -
-ENDPROBLEMTEMPLATE -}; diff --git a/package-lock.json b/package-lock.json index 45fb088be..07ea79ea4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2,23 +2,5 @@ "name": "renderer", "lockfileVersion": 2, "requires": true, - "packages": { - "": { - "dependencies": { - "codemirror": "^5.65.2" - } - }, - "node_modules/codemirror": { - "version": "5.65.6", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.6.tgz", - "integrity": "sha512-zNihMSMoDxK9Gqv9oEyDT8oM51rcRrQ+IEo2zyS48gJByBq5Fj8XuNEguMra+MuIOuh6lkpnLUJeL70DoTt6yw==" - } - }, - "dependencies": { - "codemirror": { - "version": "5.65.6", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.6.tgz", - "integrity": "sha512-zNihMSMoDxK9Gqv9oEyDT8oM51rcRrQ+IEo2zyS48gJByBq5Fj8XuNEguMra+MuIOuh6lkpnLUJeL70DoTt6yw==" - } - } + "packages": {} } diff --git a/package.json b/package.json deleted file mode 100644 index 3a353eb43..000000000 --- a/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "pg.javascript_package_manager", - "description": "Third party javascript for the standalon renderer", - "license": "GPL-2.0+", - "repository": { - "type": "git", - "url": "https://github.com/openwebwork/renderer" - }, - "dependencies": { - "codemirror": "^5.65.2" - } -} diff --git a/public/PGCodeMirror/PG.js b/public/PGCodeMirror/PG.js deleted file mode 100644 index b90e4d04d..000000000 --- a/public/PGCodeMirror/PG.js +++ /dev/null @@ -1,1025 +0,0 @@ -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - - CodeMirror.defineMode("PG",function(){ - // http://perldoc.perl.org - var PERL={ // null - magic touch - // 1 - keyword - // 2 - def - // 3 - atom - // 4 - operator - // 5 - variable-2 (predefined) - // [x,y] - x=1,2,3; y=must be defined if x{...} - // PERL operators - '->' : 4, - '++' : 4, - '--' : 4, - '**' : 4, - // ! ~ \ and unary + and - - '=~' : 4, - '!~' : 4, - '*' : 4, - '/' : 4, - '%' : 4, - 'x' : 4, - '+' : 4, - '-' : 4, - '.' : 4, - '<<' : 4, - '>>' : 4, - // named unary operators - '<' : 4, - '>' : 4, - '<=' : 4, - '>=' : 4, - 'lt' : 4, - 'gt' : 4, - 'le' : 4, - 'ge' : 4, - '==' : 4, - '!=' : 4, - '<=>' : 4, - 'eq' : 4, - 'ne' : 4, - 'cmp' : 4, - '~~' : 4, - '&' : 4, - '|' : 4, - '^' : 4, - '&&' : 4, - '||' : 4, - '//' : 4, - '..' : 4, - '...' : 4, - '?' : 4, - ':' : 4, - '=' : 4, - '+=' : 4, - '-=' : 4, - '*=' : 4, // etc. ??? - ',' : 4, - '=>' : 4, - '::' : 4, - // list operators (rightward) - 'not' : 4, - 'and' : 4, - 'or' : 4, - 'xor' : 4, - // PERL predefined variables (I know, what this is a paranoid idea, but may be needed for people, who learn PERL, and for me as well, ...and may be for you?;) - 'BEGIN' : [5,1], - 'END' : [5,1], - 'PRINT' : [5,1], - 'PRINTF' : [5,1], - 'GETC' : [5,1], - 'READ' : [5,1], - 'READLINE' : [5,1], - 'DESTROY' : [5,1], - 'TIE' : [5,1], - 'TIEHANDLE' : [5,1], - 'UNTIE' : [5,1], - 'STDIN' : 5, - 'STDIN_TOP' : 5, - 'STDOUT' : 5, - 'STDOUT_TOP' : 5, - 'STDERR' : 5, - 'STDERR_TOP' : 5, - '$ARG' : 5, - '$_' : 5, - '@ARG' : 5, - '@_' : 5, - '$LIST_SEPARATOR' : 5, - '$"' : 5, - '$PROCESS_ID' : 5, - '$PID' : 5, - '$$' : 5, - '$REAL_GROUP_ID' : 5, - '$GID' : 5, - '$(' : 5, - '$EFFECTIVE_GROUP_ID' : 5, - '$EGID' : 5, - '$)' : 5, - '$PROGRAM_NAME' : 5, - '$0' : 5, - '$SUBSCRIPT_SEPARATOR' : 5, - '$SUBSEP' : 5, - '$;' : 5, - '$REAL_USER_ID' : 5, - '$UID' : 5, - '$<' : 5, - '$EFFECTIVE_USER_ID' : 5, - '$EUID' : 5, - '$>' : 5, - '$COMPILING' : 5, - '$^C' : 5, - '$DEBUGGING' : 5, - '$^D' : 5, - '${^ENCODING}' : 5, - '$ENV' : 5, - '%ENV' : 5, - '$SYSTEM_FD_MAX' : 5, - '$^F' : 5, - '@F' : 5, - '${^GLOBAL_PHASE}' : 5, - '$^H' : 5, - '%^H' : 5, - '@INC' : 5, - '%INC' : 5, - '$INPLACE_EDIT' : 5, - '$^I' : 5, - '$^M' : 5, - '$OSNAME' : 5, - '$^O' : 5, - '${^OPEN}' : 5, - '$PERLDB' : 5, - '$^P' : 5, - '$SIG' : 5, - '%SIG' : 5, - '$BASETIME' : 5, - '$^T' : 5, - '${^TAINT}' : 5, - '${^UNICODE}' : 5, - '${^UTF8CACHE}' : 5, - '${^UTF8LOCALE}' : 5, - '$PERL_VERSION' : 5, - '$^V' : 5, - '${^WIN32_SLOPPY_STAT}' : 5, - '$EXECUTABLE_NAME' : 5, - '$^X' : 5, - '$1' : 5, // - regexp $1, $2... - '$MATCH' : 5, - '$&' : 5, - '${^MATCH}' : 5, - '$PREMATCH' : 5, - '$`' : 5, - '${^PREMATCH}' : 5, - '$POSTMATCH' : 5, - "$'" : 5, - '${^POSTMATCH}' : 5, - '$LAST_PAREN_MATCH' : 5, - '$+' : 5, - '$LAST_SUBMATCH_RESULT' : 5, - '$^N' : 5, - '@LAST_MATCH_END' : 5, - '@+' : 5, - '%LAST_PAREN_MATCH' : 5, - '%+' : 5, - '@LAST_MATCH_START' : 5, - '@-' : 5, - '%LAST_MATCH_START' : 5, - '%-' : 5, - '$LAST_REGEXP_CODE_RESULT' : 5, - '$^R' : 5, - '${^RE_DEBUG_FLAGS}' : 5, - '${^RE_TRIE_MAXBUF}' : 5, - '$ARGV' : 5, - '@ARGV' : 5, - 'ARGV' : 5, - 'ARGVOUT' : 5, - '$OUTPUT_FIELD_SEPARATOR' : 5, - '$OFS' : 5, - '$,' : 5, - '$INPUT_LINE_NUMBER' : 5, - '$NR' : 5, - '$.' : 5, - '$INPUT_RECORD_SEPARATOR' : 5, - '$RS' : 5, - '$/' : 5, - '$OUTPUT_RECORD_SEPARATOR' : 5, - '$ORS' : 5, - '$\\' : 5, - '$OUTPUT_AUTOFLUSH' : 5, - '$|' : 5, - '$ACCUMULATOR' : 5, - '$^A' : 5, - '$FORMAT_FORMFEED' : 5, - '$^L' : 5, - '$FORMAT_PAGE_NUMBER' : 5, - '$%' : 5, - '$FORMAT_LINES_LEFT' : 5, - '$-' : 5, - '$FORMAT_LINE_BREAK_CHARACTERS' : 5, - '$:' : 5, - '$FORMAT_LINES_PER_PAGE' : 5, - '$=' : 5, - '$FORMAT_TOP_NAME' : 5, - '$^' : 5, - '$FORMAT_NAME' : 5, - '$~' : 5, - '${^CHILD_ERROR_NATIVE}' : 5, - '$EXTENDED_OS_ERROR' : 5, - '$^E' : 5, - '$EXCEPTIONS_BEING_CAUGHT' : 5, - '$^S' : 5, - '$WARNING' : 5, - '$^W' : 5, - '${^WARNING_BITS}' : 5, - '$OS_ERROR' : 5, - '$ERRNO' : 5, - '$!' : 5, - '%OS_ERROR' : 5, - '%ERRNO' : 5, - '%!' : 5, - '$CHILD_ERROR' : 5, - '$?' : 5, - '$EVAL_ERROR' : 5, - '$@' : 5, - '$OFMT' : 5, - '$#' : 5, - '$*' : 5, - '$ARRAY_BASE' : 5, - '$[' : 5, - '$OLD_PERL_VERSION' : 5, - '$]' : 5, - // PERL blocks - 'if' :[1,1], - elsif :[1,1], - 'else' :[1,1], - 'while' :[1,1], - unless :[1,1], - 'for' :[1,1], - foreach :[1,1], - // PERL functions - 'abs' :1, // - absolute value function - accept :1, // - accept an incoming socket connect - alarm :1, // - schedule a SIGALRM - 'atan2' :1, // - arctangent of Y/X in the range -PI to PI - bind :1, // - binds an address to a socket - binmode :1, // - prepare binary files for I/O - bless :1, // - create an object - bootstrap :1, // - 'break' :1, // - break out of a "given" block - caller :1, // - get context of the current subroutine call - chdir :1, // - change your current working directory - chmod :1, // - changes the permissions on a list of files - chomp :1, // - remove a trailing record separator from a string - chop :1, // - remove the last character from a string - chown :1, // - change the ownership on a list of files - chr :1, // - get character this number represents - chroot :1, // - make directory new root for path lookups - close :1, // - close file (or pipe or socket) handle - closedir :1, // - close directory handle - connect :1, // - connect to a remote socket - 'continue' :[1,1], // - optional trailing block in a while or foreach - 'cos' :1, // - cosine function - crypt :1, // - one-way passwd-style encryption - dbmclose :1, // - breaks binding on a tied dbm file - dbmopen :1, // - create binding on a tied dbm file - 'default' :1, // - defined :1, // - test whether a value, variable, or function is defined - 'delete' :1, // - deletes a value from a hash - die :1, // - raise an exception or bail out - 'do' :1, // - turn a BLOCK into a TERM - dump :1, // - create an immediate core dump - each :1, // - retrieve the next key/value pair from a hash - endgrent :1, // - be done using group file - endhostent :1, // - be done using hosts file - endnetent :1, // - be done using networks file - endprotoent :1, // - be done using protocols file - endpwent :1, // - be done using passwd file - endservent :1, // - be done using services file - eof :1, // - test a filehandle for its end - 'eval' :1, // - catch exceptions or compile and run code - 'exec' :1, // - abandon this program to run another - exists :1, // - test whether a hash key is present - exit :1, // - terminate this program - 'exp' :1, // - raise I to a power - fcntl :1, // - file control system call - fileno :1, // - return file descriptor from filehandle - flock :1, // - lock an entire file with an advisory lock - fork :1, // - create a new process just like this one - format :1, // - declare a picture format with use by the write() function - formline :1, // - internal function used for formats - getc :1, // - get the next character from the filehandle - getgrent :1, // - get next group record - getgrgid :1, // - get group record given group user ID - getgrnam :1, // - get group record given group name - gethostbyaddr :1, // - get host record given its address - gethostbyname :1, // - get host record given name - gethostent :1, // - get next hosts record - getlogin :1, // - return who logged in at this tty - getnetbyaddr :1, // - get network record given its address - getnetbyname :1, // - get networks record given name - getnetent :1, // - get next networks record - getpeername :1, // - find the other end of a socket connection - getpgrp :1, // - get process group - getppid :1, // - get parent process ID - getpriority :1, // - get current nice value - getprotobyname :1, // - get protocol record given name - getprotobynumber :1, // - get protocol record numeric protocol - getprotoent :1, // - get next protocols record - getpwent :1, // - get next passwd record - getpwnam :1, // - get passwd record given user login name - getpwuid :1, // - get passwd record given user ID - getservbyname :1, // - get services record given its name - getservbyport :1, // - get services record given numeric port - getservent :1, // - get next services record - getsockname :1, // - retrieve the sockaddr for a given socket - getsockopt :1, // - get socket options on a given socket - given :1, // - glob :1, // - expand filenames using wildcards - gmtime :1, // - convert UNIX time into record or string using Greenwich time - 'goto' :1, // - create spaghetti code - grep :1, // - locate elements in a list test true against a given criterion - hex :1, // - convert a string to a hexadecimal number - 'import' :1, // - patch a module's namespace into your own - index :1, // - find a substring within a string - 'int' :1, // - get the integer portion of a number - ioctl :1, // - system-dependent device control system call - 'join' :1, // - join a list into a string using a separator - keys :1, // - retrieve list of indices from a hash - kill :1, // - send a signal to a process or process group - last :1, // - exit a block prematurely - lc :1, // - return lower-case version of a string - lcfirst :1, // - return a string with just the next letter in lower case - length :1, // - return the number of bytes in a string - 'link' :1, // - create a hard link in the filesytem - listen :1, // - register your socket as a server - local : 2, // - create a temporary value for a global variable (dynamic scoping) - localtime :1, // - convert UNIX time into record or string using local time - lock :1, // - get a thread lock on a variable, subroutine, or method - 'log' :1, // - retrieve the natural logarithm for a number - lstat :1, // - stat a symbolic link - m :null, // - match a string with a regular expression pattern - map :1, // - apply a change to a list to get back a new list with the changes - mkdir :1, // - create a directory - msgctl :1, // - SysV IPC message control operations - msgget :1, // - get SysV IPC message queue - msgrcv :1, // - receive a SysV IPC message from a message queue - msgsnd :1, // - send a SysV IPC message to a message queue - my : 2, // - declare and assign a local variable (lexical scoping) - 'new' :1, // - next :1, // - iterate a block prematurely - no :1, // - unimport some module symbols or semantics at compile time - oct :1, // - convert a string to an octal number - open :1, // - open a file, pipe, or descriptor - opendir :1, // - open a directory - ord :1, // - find a character's numeric representation - our : 2, // - declare and assign a package variable (lexical scoping) - pack :1, // - convert a list into a binary representation - 'package' :1, // - declare a separate global namespace - pipe :1, // - open a pair of connected filehandles - pop :1, // - remove the last element from an array and return it - pos :1, // - find or set the offset for the last/next m//g search - print :1, // - output a list to a filehandle - printf :1, // - output a formatted list to a filehandle - prototype :1, // - get the prototype (if any) of a subroutine - push :1, // - append one or more elements to an array - q :null, // - singly quote a string - qq :null, // - doubly quote a string - qr :null, // - Compile pattern - quotemeta :null, // - quote regular expression magic characters - qw :null, // - quote a list of words - qx :null, // - backquote quote a string - rand :1, // - retrieve the next pseudorandom number - read :1, // - fixed-length buffered input from a filehandle - readdir :1, // - get a directory from a directory handle - readline :1, // - fetch a record from a file - readlink :1, // - determine where a symbolic link is pointing - readpipe :1, // - execute a system command and collect standard output - recv :1, // - receive a message over a Socket - redo :1, // - start this loop iteration over again - ref :1, // - find out the type of thing being referenced - rename :1, // - change a filename - require :1, // - load in external functions from a library at runtime - reset :1, // - clear all variables of a given name - 'return' :1, // - get out of a function early - reverse :1, // - flip a string or a list - rewinddir :1, // - reset directory handle - rindex :1, // - right-to-left substring search - rmdir :1, // - remove a directory - s :null, // - replace a pattern with a string - say :1, // - print with newline - scalar :1, // - force a scalar context - seek :1, // - reposition file pointer for random-access I/O - seekdir :1, // - reposition directory pointer - select :1, // - reset default output or do I/O multiplexing - semctl :1, // - SysV semaphore control operations - semget :1, // - get set of SysV semaphores - semop :1, // - SysV semaphore operations - send :1, // - send a message over a socket - setgrent :1, // - prepare group file for use - sethostent :1, // - prepare hosts file for use - setnetent :1, // - prepare networks file for use - setpgrp :1, // - set the process group of a process - setpriority :1, // - set a process's nice value - setprotoent :1, // - prepare protocols file for use - setpwent :1, // - prepare passwd file for use - setservent :1, // - prepare services file for use - setsockopt :1, // - set some socket options - shift :1, // - remove the first element of an array, and return it - shmctl :1, // - SysV shared memory operations - shmget :1, // - get SysV shared memory segment identifier - shmread :1, // - read SysV shared memory - shmwrite :1, // - write SysV shared memory - shutdown :1, // - close down just half of a socket connection - 'sin' :1, // - return the sine of a number - sleep :1, // - block for some number of seconds - socket :1, // - create a socket - socketpair :1, // - create a pair of sockets - 'sort' :1, // - sort a list of values - splice :1, // - add or remove elements anywhere in an array - 'split' :1, // - split up a string using a regexp delimiter - sprintf :1, // - formatted print into a string - 'sqrt' :1, // - square root function - srand :1, // - seed the random number generator - stat :1, // - get a file's status information - state :1, // - declare and assign a state variable (persistent lexical scoping) - study :1, // - optimize input data for repeated searches - 'sub' :1, // - declare a subroutine, possibly anonymously - 'substr' :1, // - get or alter a portion of a stirng - symlink :1, // - create a symbolic link to a file - syscall :1, // - execute an arbitrary system call - sysopen :1, // - open a file, pipe, or descriptor - sysread :1, // - fixed-length unbuffered input from a filehandle - sysseek :1, // - position I/O pointer on handle used with sysread and syswrite - system :1, // - run a separate program - syswrite :1, // - fixed-length unbuffered output to a filehandle - tell :1, // - get current seekpointer on a filehandle - telldir :1, // - get current seekpointer on a directory handle - tie :1, // - bind a variable to an object class - tied :1, // - get a reference to the object underlying a tied variable - time :1, // - return number of seconds since 1970 - times :1, // - return elapsed time for self and child processes - tr :null, // - transliterate a string - truncate :1, // - shorten a file - uc :1, // - return upper-case version of a string - ucfirst :1, // - return a string with just the next letter in upper case - umask :1, // - set file creation mode mask - undef :1, // - remove a variable or function definition - unlink :1, // - remove one link to a file - unpack :1, // - convert binary structure into normal perl variables - unshift :1, // - prepend more elements to the beginning of a list - untie :1, // - break a tie binding to a variable - use :1, // - load in a module at compile time - utime :1, // - set a file's last access and modify times - values :1, // - return a list of the values in a hash - vec :1, // - test or set particular bits in a string - wait :1, // - wait for any child process to die - waitpid :1, // - wait for a particular child process to die - wantarray :1, // - get void vs scalar vs list context of current subroutine call - warn :1, // - print debugging info - when :1, // - write :1, // - print a picture record - y :null, - }; // - transliterate a string - - var RXstyle="string-2"; - var RXmodifiers=/[goseximacplud]/; // NOTE: "m", "s", "y" and "tr" need to correct real modifiers for each regexp type - - function tokenChain(stream,state,chain,style,tail,tokener){ // NOTE: chain.length > 2 is not working now (it's for s[...][...]geos;) - state.chain=null; - state.style=null; - state.tail=null; - - state.tokenize=function(stream,state){ - var e=false,c,i=0; - while(c=stream.next()){ - if(c===chain[i]&&!e){ - if(chain[i+1]!==undefined && - look(stream) === chain[i+1]){ - i++; - state.chain=chain[i]; - state.style=style; - state.tail=tail;} - else if(chain[i+1]!==undefined) { - state.chain=chain; - state.style=style; - state.tail=tail;} - else if(tail) - stream.eatWhile(tail); - state.tokenize=tokener || tokenPerl; - return style;} - e=!e&&c=="\\";} - return style;}; - return state.tokenize(stream,state);} - - function tokenSOMETHING(stream,state,string){ - state.tokenize=function(stream,state){ - if(stream.string==string) - state.tokenize=tokenPerl; - stream.skipToEnd(); - return "string";}; - return state.tokenize(stream,state);} - - function tokenEV3(stream,state,string) { - state.tokenize=function(stream,state) { - - var thisEV3 = function(stream,state) { - return tokenEV3(stream,state,string); - } - - if(stream.eatSpace()) - return null; - - if(state.chain) - return tokenChain(stream,state,state.chain,state.style,state.tail,thisEV3,string); - - if(stream.string==string) { - state.tokenize=tokenPerl; - } - - if (stream.match(/^\\\(/)) { - return tokenChain(stream,state,["\\",")"],"comment",null,thisEV3); - } - - if (stream.match(/^\\\[/)) { - return tokenChain(stream,state,["\\","]"],"comment",null,thisEV3); - } - - if (stream.match(/^\\{/)) { - return tokenChain(stream,state,["\\","}"],"variable-2",null,thisEV3); - } - - if (stream.match(/^`/)) { - return tokenChain(stream,state,["`"],"variable-3",null,thisEV3); - } - - var ch = stream.next(); - - if (ch =="$"){ - var p=stream.pos; - if(stream.eatWhile(/\w/)||stream.eat("{")&&stream.eatWhile(/\w/)&&stream.eat("}")) { - return "variable"; - } else { - stream.pos=p; - } - } - - stream.eatWhile(/[^\$\\`]/); - - return "block-quote";}; - return state.tokenize(stream,state); - - } - - function tokenPGML(stream,state,string,style,prevState) { - state.tokenize = function(stream,state) { - - var reg = new RegExp("^"+string); - - if (stream.match(reg)) { - if (!prevState) { - state.tokenize = tokenPerl; - } else { - stream.eatWhile('*'); - stream.match(/\{.*\}/); - state.tokenize = function (stream,state) { - return tokenPGML(stream,state, - prevState.string, - prevState.style, - prevState.prevState); - } - } - - return style; - } else { - - state.tokenize = function (stream,state) { - return tokenPGML(stream,state, - string,style,prevState); - } - } - - var newPrevState = {}; - - if (prevState) { - var strValue = JSON.stringify(prevState) - newPrevState.prevState = JSON.parse(strValue); - } else { - newPrevState.prevState = null; - } - - newPrevState.style = style; - newPrevState.string = string; - - if (stream.match(/^\[:/)) { - style = "variable-3"; - state.tokenize = function (stream,state) { - return tokenPGML(stream,state,":\\]", - style, - newPrevState); - } - return style; - } else if (stream.match(/^\[`/)) { - style = "comment"; - state.tokenize = function (stream,state) { - return tokenPGML(stream,state,"`\\]", - style, - newPrevState); - } - return style; - } else if (stream.match(/^\[\|/)) { - style = "tag"; - state.tokenize = function (stream,state) { - return tokenPGML(stream,state,"\\|\\]", - style, - newPrevState); - } - return style; - } else if (stream.match(/^\[%/)) { - style = "bracket"; - state.tokenize = function (stream,state) { - return tokenPGML(stream,state,"%\\]", - style, - newPrevState); - } - return style; - } else if (stream.match(/^\[@/)) { - style = "variable-2"; - state.tokenize = function (stream,state) { - return tokenPGML(stream,state,"@\\]", - style, - newPrevState); - } - return style; - } else if (stream.match(/^\[\$/)) { - style = "variable"; - state.tokenize = function (stream,state) { - return tokenPGML(stream,state,"]", - style, - newPrevState); - } - return style; - } else if (stream.match(/^\[_+\]/)) { - stream.eatWhile('*'); - stream.match(/\{.*\}/); - return "builtin" - } else if (stream.match(/^ +$/)) { - return "trailingspace"; - } else if (stream.match(/^[\[\] :\|@%`]/)) { - return style; - } - - stream.eatWhile(/[^\[\] :\|@%`]/); - - return style; - }; - - return state.tokenize(stream,state); - - } - - - function tokenPerl(stream,state){ - if(stream.eatSpace()) - return null; - if(state.chain) - return tokenChain(stream,state,state.chain,state.style,state.tail,tokenPerl); - if(stream.match(/^\-?[\d\.]/,false)) - if(stream.match(/^(\-?(\d*\.\d+(e[+-]?\d+)?|\d+\.\d*)|0x[\da-fA-F]+|0b[01]+|\d+(e[+-]?\d+)?)/)) - return 'number'; - if(stream.match(/^<<(?=\w)/)){ // NOTE: <"],RXstyle,RXmodifiers);} - if(/[\^'"!~\/]/.test(c)){ - eatSuffix(stream, 1); - return tokenChain(stream,state,[stream.eat(c)],RXstyle,RXmodifiers);}} - else if(c=="q"){ - c=look(stream, 1); - if(c=="("){ - eatSuffix(stream, 2); - return tokenChain(stream,state,[")"],"string");} - if(c=="["){ - eatSuffix(stream, 2); - return tokenChain(stream,state,["]"],"string");} - if(c=="{"){ - eatSuffix(stream, 2); - return tokenChain(stream,state,["}"],"string");} - if(c=="<"){ - eatSuffix(stream, 2); - return tokenChain(stream,state,[">"],"string");} - if(/[\^'"!~\/]/.test(c)){ - eatSuffix(stream, 1); - return tokenChain(stream,state,[stream.eat(c)],"string");}} - else if(c=="w"){ - c=look(stream, 1); - if(c=="("){ - eatSuffix(stream, 2); - return tokenChain(stream,state,[")"],"bracket");} - if(c=="["){ - eatSuffix(stream, 2); - return tokenChain(stream,state,["]"],"bracket");} - if(c=="{"){ - eatSuffix(stream, 2); - return tokenChain(stream,state,["}"],"bracket");} - if(c=="<"){ - eatSuffix(stream, 2); - return tokenChain(stream,state,[">"],"bracket");} - if(/[\^'"!~\/]/.test(c)){ - eatSuffix(stream, 1); - return tokenChain(stream,state,[stream.eat(c)],"bracket");}} - else if(c=="r"){ - c=look(stream, 1); - if(c=="("){ - eatSuffix(stream, 2); - return tokenChain(stream,state,[")"],RXstyle,RXmodifiers);} - if(c=="["){ - eatSuffix(stream, 2); - return tokenChain(stream,state,["]"],RXstyle,RXmodifiers);} - if(c=="{"){ - eatSuffix(stream, 2); - return tokenChain(stream,state,["}"],RXstyle,RXmodifiers);} - if(c=="<"){ - eatSuffix(stream, 2); - return tokenChain(stream,state,[">"],RXstyle,RXmodifiers);} - if(/[\^'"!~\/]/.test(c)){ - eatSuffix(stream, 1); - return tokenChain(stream,state,[stream.eat(c)],RXstyle,RXmodifiers);}} - else if(/[\^'"!~\/(\[{<]/.test(c)){ - if(c=="("){ - eatSuffix(stream, 1); - return tokenChain(stream,state,[")"],"string");} - if(c=="["){ - eatSuffix(stream, 1); - return tokenChain(stream,state,["]"],"string");} - if(c=="{"){ - eatSuffix(stream, 1); - return tokenChain(stream,state,["}"],"string");} - if(c=="<"){ - eatSuffix(stream, 1); - return tokenChain(stream,state,[">"],"string");} - if(/[\^'"!~\/]/.test(c)){ - return tokenChain(stream,state,[stream.eat(c)],"string");}}}} - if(ch=="m"){ - var c=look(stream, -2); - if(!(c&&/\w/.test(c))){ - c=stream.eat(/[(\[{<\^'"!~\/]/); - if(c){ - if(/[\^'"!~\/]/.test(c)){ - return tokenChain(stream,state,[c],RXstyle,RXmodifiers);} - if(c=="("){ - return tokenChain(stream,state,[")"],RXstyle,RXmodifiers);} - if(c=="["){ - return tokenChain(stream,state,["]"],RXstyle,RXmodifiers);} - if(c=="{"){ - return tokenChain(stream,state,["}"],RXstyle,RXmodifiers);} - if(c=="<"){ - return tokenChain(stream,state,[">"],RXstyle,RXmodifiers);}}}} - if(ch=="s"){ - var c=/[\/>\]})\w]/.test(look(stream, -2)); - if(!c){ - c=stream.eat(/[(\[{<\^'"!~\/]/); - if(c){ - if(c=="[") - return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); - if(c=="{") - return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); - if(c=="<") - return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); - if(c=="(") - return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); - return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}} - if(ch=="y"){ - var c=/[\/>\]})\w]/.test(look(stream, -2)); - if(!c){ - c=stream.eat(/[(\[{<\^'"!~\/]/); - if(c){ - if(c=="[") - return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); - if(c=="{") - return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); - if(c=="<") - return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); - if(c=="(") - return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); - return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}} - if(ch=="t"){ - var c=/[\/>\]})\w]/.test(look(stream, -2)); - if(!c){ - c=stream.eat("r");if(c){ - c=stream.eat(/[(\[{<\^'"!~\/]/); - if(c){ - if(c=="[") - return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); - if(c=="{") - return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); - if(c=="<") - return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); - if(c=="(") - return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); - return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}}} - if(ch=="`"){ - return tokenChain(stream,state,[ch],"variable-2");} - if(ch=="/"){ - if(!/~\s*$/.test(prefix(stream))) - return "operator"; - else - return tokenChain(stream,state,[ch],RXstyle,RXmodifiers);} - if(ch=="$"){ - var p=stream.pos; - if(stream.eatWhile(/\d/)||stream.eat("{")&&stream.eatWhile(/\d/)&&stream.eat("}")) - return "variable-2"; - else - stream.pos=p;} - if(/[$@%]/.test(ch)){ - var p=stream.pos; - if(stream.eat("^")&&stream.eat(/[A-Z]/)||!/[@$%&]/.test(look(stream, -2))&&stream.eat(/[=|\\\-#?@;:&`~\^!\[\]*'"$+.,\/<>()]/)){ - var c=stream.current(); - if(PERL[c]) - return "variable-2";} - stream.pos=p;} - if(/[$@%&]/.test(ch)){ - if(stream.eatWhile(/[\w$\[\]]/)||stream.eat("{")&&stream.eatWhile(/[\w$\[\]]/)&&stream.eat("}")){ - var c=stream.current(); - if(PERL[c]) - return "variable-2"; - else - return "variable";}} - if(ch=="#"){ - if(look(stream, -2)!="$"){ - stream.skipToEnd(); - return "comment";}} - if(/[:+\-\^*$&%@=<>!?|\/~\.]/.test(ch)){ - var p=stream.pos; - stream.eatWhile(/[:+\-\^*$&%@=<>!?|\/~\.]/); - if(PERL[stream.current()]) - return "operator"; - else - stream.pos=p;} - if(ch=="_"){ - if(stream.pos==1){ - if(suffix(stream, 6)=="_END__"){ - return tokenChain(stream,state,['\0'],"comment");} - else if(suffix(stream, 7)=="_DATA__"){ - return tokenChain(stream,state,['\0'],"variable-2");} - else if(suffix(stream, 7)=="_C__"){ - return tokenChain(stream,state,['\0'],"string");}}} - if(/\w/.test(ch)){ - var p=stream.pos; - if(look(stream, -2)=="{"&&(look(stream, 0)=="}"||stream.eatWhile(/\w/)&&look(stream, 0)=="}")) - return "string"; - else - stream.pos=p;} - if(/[A-Z]/.test(ch)){ - var l=look(stream, -2); - var p=stream.pos; - stream.eatWhile(/[A-Z_]/); - if(/[\da-z]/.test(look(stream, 0))){ - stream.pos=p;} - else{ - var c=PERL[stream.current()]; - if(!c) - return "meta"; - if(c[1]) - c=c[0]; - if(l!=":"){ - if(c==1) - return "keyword"; - else if(c==2) - return "def"; - else if(c==3) - return "atom"; - else if(c==4) - return "operator"; - else if(c==5) - return "variable-2"; - else - return "meta";} - else - return "meta";}} - if(/[a-zA-Z_]/.test(ch)){ - var l=look(stream, -2); - stream.eatWhile(/\w/); - var c=PERL[stream.current()]; - if(!c) - return "meta"; - if(c[1]) - c=c[0]; - if(l!=":"){ - if(c==1) - return "keyword"; - else if(c==2) - return "def"; - else if(c==3) - return "atom"; - else if(c==4) - return "operator"; - else if(c==5) - return "variable-2"; - else - return "meta";} - else - return "meta";} - return null;} - - return { - startState: function() { - return { - tokenize: tokenPerl, - chain: null, - style: null, - tail: null - }; - }, - token: function(stream, state) { - return (state.tokenize || tokenPerl)(stream, state); - }, - lineComment: '#' - }; - }); - - CodeMirror.registerHelper("wordChars", "perl", /[\w$]/); - - CodeMirror.defineMIME("text/x-perl", "perl"); - - // it's like "peek", but need for look-ahead or look-behind if index < 0 - function look(stream, c){ - return stream.string.charAt(stream.pos+(c||0)); - } - - // return a part of prefix of current stream from current position - function prefix(stream, c){ - if(c){ - var x=stream.pos-c; - return stream.string.substr((x>=0?x:0),c);} - else{ - return stream.string.substr(0,stream.pos-1); - } - } - - // return a part of suffix of current stream from current position - function suffix(stream, c){ - var y=stream.string.length; - var x=y-stream.pos+1; - return stream.string.substr(stream.pos,(c&&c=(y=stream.string.length-1)) - stream.pos=y; - else - stream.pos=x; - } - -}); diff --git a/public/PGCodeMirror/PGaddons.js b/public/PGCodeMirror/PGaddons.js deleted file mode 100644 index 649ed898c..000000000 --- a/public/PGCodeMirror/PGaddons.js +++ /dev/null @@ -1,1085 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -// Open simple dialogs on top of an editor. Relies on dialog.css. - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - function dialogDiv(cm, template, bottom) { - var wrap = cm.getWrapperElement(); - var dialog; - dialog = wrap.appendChild(document.createElement("div")); - if (bottom) - dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom"; - else - dialog.className = "CodeMirror-dialog CodeMirror-dialog-top"; - - if (typeof template == "string") { - dialog.innerHTML = template; - } else { // Assuming it's a detached DOM element. - dialog.appendChild(template); - } - return dialog; - } - - function closeNotification(cm, newVal) { - if (cm.state.currentNotificationClose) - cm.state.currentNotificationClose(); - cm.state.currentNotificationClose = newVal; - } - - CodeMirror.defineExtension("openDialog", function(template, callback, options) { - if (!options) options = {}; - - closeNotification(this, null); - - var dialog = dialogDiv(this, template, options.bottom); - var closed = false, me = this; - function close(newVal) { - if (typeof newVal == 'string') { - inp.value = newVal; - } else { - if (closed) return; - closed = true; - dialog.parentNode.removeChild(dialog); - me.focus(); - - if (options.onClose) options.onClose(dialog); - } - } - - var inp = dialog.getElementsByTagName("input")[0], button; - if (inp) { - inp.focus(); - - if (options.value) { - inp.value = options.value; - if (options.selectValueOnOpen !== false) { - inp.select(); - } - } - - if (options.onInput) - CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);}); - if (options.onKeyUp) - CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);}); - - CodeMirror.on(inp, "keydown", function(e) { - if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; } - if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) { - inp.blur(); - CodeMirror.e_stop(e); - close(); - } - if (e.keyCode == 13) callback(inp.value, e); - }); - - if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close); - } else if (button = dialog.getElementsByTagName("button")[0]) { - CodeMirror.on(button, "click", function() { - close(); - me.focus(); - }); - - if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close); - - button.focus(); - } - return close; - }); - - CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) { - closeNotification(this, null); - var dialog = dialogDiv(this, template, options && options.bottom); - var buttons = dialog.getElementsByTagName("button"); - var closed = false, me = this, blurring = 1; - function close() { - if (closed) return; - closed = true; - dialog.parentNode.removeChild(dialog); - me.focus(); - } - buttons[0].focus(); - for (var i = 0; i < buttons.length; ++i) { - var b = buttons[i]; - (function(callback) { - CodeMirror.on(b, "click", function(e) { - CodeMirror.e_preventDefault(e); - close(); - if (callback) callback(me); - }); - })(callbacks[i]); - CodeMirror.on(b, "blur", function() { - --blurring; - setTimeout(function() { if (blurring <= 0) close(); }, 200); - }); - CodeMirror.on(b, "focus", function() { ++blurring; }); - } - }); - - /* - * openNotification - * Opens a notification, that can be closed with an optional timer - * (default 5000ms timer) and always closes on click. - * - * If a notification is opened while another is opened, it will close the - * currently opened one and open the new one immediately. - */ - CodeMirror.defineExtension("openNotification", function(template, options) { - closeNotification(this, close); - var dialog = dialogDiv(this, template, options && options.bottom); - var closed = false, doneTimer; - var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000; - - function close() { - if (closed) return; - closed = true; - clearTimeout(doneTimer); - dialog.parentNode.removeChild(dialog); - } - - CodeMirror.on(dialog, 'click', function(e) { - CodeMirror.e_preventDefault(e); - close(); - }); - - if (duration) - doneTimer = setTimeout(close, duration); - - return close; - }); -}); - -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - var Pos = CodeMirror.Pos; - - function SearchCursor(doc, query, pos, caseFold) { - this.atOccurrence = false; this.doc = doc; - if (caseFold == null && typeof query == "string") caseFold = false; - - pos = pos ? doc.clipPos(pos) : Pos(0, 0); - this.pos = {from: pos, to: pos}; - - // The matches method is filled in based on the type of query. - // It takes a position and a direction, and returns an object - // describing the next occurrence of the query, or null if no - // more matches were found. - if (typeof query != "string") { // Regexp match - if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g"); - this.matches = function(reverse, pos) { - if (reverse) { - query.lastIndex = 0; - var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start; - for (;;) { - query.lastIndex = cutOff; - var newMatch = query.exec(line); - if (!newMatch) break; - match = newMatch; - start = match.index; - cutOff = match.index + (match[0].length || 1); - if (cutOff == line.length) break; - } - var matchLen = (match && match[0].length) || 0; - if (!matchLen) { - if (start == 0 && line.length == 0) {match = undefined;} - else if (start != doc.getLine(pos.line).length) { - matchLen++; - } - } - } else { - query.lastIndex = pos.ch; - var line = doc.getLine(pos.line), match = query.exec(line); - var matchLen = (match && match[0].length) || 0; - var start = match && match.index; - if (start + matchLen != line.length && !matchLen) matchLen = 1; - } - if (match && matchLen) - return {from: Pos(pos.line, start), - to: Pos(pos.line, start + matchLen), - match: match}; - }; - } else { // String query - var origQuery = query; - if (caseFold) query = query.toLowerCase(); - var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; - var target = query.split("\n"); - // Different methods for single-line and multi-line queries - if (target.length == 1) { - if (!query.length) { - // Empty string would match anything and never progress, so - // we define it to match nothing instead. - this.matches = function() {}; - } else { - this.matches = function(reverse, pos) { - if (reverse) { - var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig); - var match = line.lastIndexOf(query); - if (match > -1) { - match = adjustPos(orig, line, match); - return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; - } - } else { - var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig); - var match = line.indexOf(query); - if (match > -1) { - match = adjustPos(orig, line, match) + pos.ch; - return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; - } - } - }; - } - } else { - var origTarget = origQuery.split("\n"); - this.matches = function(reverse, pos) { - var last = target.length - 1; - if (reverse) { - if (pos.line - (target.length - 1) < doc.firstLine()) return; - if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return; - var to = Pos(pos.line, origTarget[last].length); - for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln) - if (target[i] != fold(doc.getLine(ln))) return; - var line = doc.getLine(ln), cut = line.length - origTarget[0].length; - if (fold(line.slice(cut)) != target[0]) return; - return {from: Pos(ln, cut), to: to}; - } else { - if (pos.line + (target.length - 1) > doc.lastLine()) return; - var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length; - if (fold(line.slice(cut)) != target[0]) return; - var from = Pos(pos.line, cut); - for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln) - if (target[i] != fold(doc.getLine(ln))) return; - if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return; - return {from: from, to: Pos(ln, origTarget[last].length)}; - } - }; - } - } - } - - SearchCursor.prototype = { - findNext: function() {return this.find(false);}, - findPrevious: function() {return this.find(true);}, - - find: function(reverse) { - var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to); - function savePosAndFail(line) { - var pos = Pos(line, 0); - self.pos = {from: pos, to: pos}; - self.atOccurrence = false; - return false; - } - - for (;;) { - if (this.pos = this.matches(reverse, pos)) { - this.atOccurrence = true; - return this.pos.match || true; - } - if (reverse) { - if (!pos.line) return savePosAndFail(0); - pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length); - } - else { - var maxLine = this.doc.lineCount(); - if (pos.line == maxLine - 1) return savePosAndFail(maxLine); - pos = Pos(pos.line + 1, 0); - } - } - }, - - from: function() {if (this.atOccurrence) return this.pos.from;}, - to: function() {if (this.atOccurrence) return this.pos.to;}, - - replace: function(newText, origin) { - if (!this.atOccurrence) return; - var lines = CodeMirror.splitLines(newText); - this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin); - this.pos.to = Pos(this.pos.from.line + lines.length - 1, - lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)); - } - }; - - // Maps a position in a case-folded line back to a position in the original line - // (compensating for codepoints increasing in number during folding) - function adjustPos(orig, folded, pos) { - if (orig.length == folded.length) return pos; - for (var pos1 = Math.min(pos, orig.length);;) { - var len1 = orig.slice(0, pos1).toLowerCase().length; - if (len1 < pos) ++pos1; - else if (len1 > pos) --pos1; - else return pos1; - } - } - - CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { - return new SearchCursor(this.doc, query, pos, caseFold); - }); - CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { - return new SearchCursor(this, query, pos, caseFold); - }); - - CodeMirror.defineExtension("selectMatches", function(query, caseFold) { - var ranges = []; - var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold); - while (cur.findNext()) { - if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break; - ranges.push({anchor: cur.from(), head: cur.to()}); - } - if (ranges.length) - this.setSelections(ranges, 0); - }); -}); - -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -// Define search commands. Depends on dialog.js or another -// implementation of the openDialog method. - -// Replace works a little oddly -- it will do the replace on the next -// Ctrl-G (or whatever is bound to findNext) press. You prevent a -// replace by making sure the match is no longer selected when hitting -// Ctrl-G. - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - - function searchOverlay(query, caseInsensitive) { - if (typeof query == "string") - query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g"); - else if (!query.global) - query = new RegExp(query.source, query.ignoreCase ? "gi" : "g"); - - return {token: function(stream) { - query.lastIndex = stream.pos; - var match = query.exec(stream.string); - if (match && match.index == stream.pos) { - stream.pos += match[0].length || 1; - return "searching"; - } else if (match) { - stream.pos = match.index; - } else { - stream.skipToEnd(); - } - }}; - } - - function SearchState() { - this.posFrom = this.posTo = this.lastQuery = this.query = null; - this.overlay = null; - } - - function getSearchState(cm) { - return cm.state.search || (cm.state.search = new SearchState()); - } - - function queryCaseInsensitive(query) { - return typeof query == "string" && query == query.toLowerCase(); - } - - function getSearchCursor(cm, query, pos) { - // Heuristic: if the query string is all lowercase, do a case insensitive search. - return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); - } - - function persistentDialog(cm, text, deflt, onEnter, onKeyDown) { - cm.openDialog(text, onEnter, { - value: deflt, - selectValueOnOpen: true, - closeOnEnter: false, - onClose: function() { clearSearch(cm); }, - onKeyDown: onKeyDown - }); - } - - function dialog(cm, text, shortText, deflt, f) { - if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true}); - else f(prompt(shortText, deflt)); - } - - function confirmDialog(cm, text, shortText, fs) { - if (cm.openConfirm) cm.openConfirm(text, fs); - else if (confirm(shortText)) fs[0](); - } - - function parseString(string) { - return string.replace(/\\(.)/g, function(_, ch) { - if (ch == "n") return "\n" - if (ch == "r") return "\r" - return ch - }) - } - - function parseQuery(query) { - var isRE = query.match(/^\/(.*)\/([a-z]*)$/); - if (isRE) { - try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); } - catch(e) {} // Not a regular expression after all, do a string search - } else { - query = parseString(query) - } - if (typeof query == "string" ? query == "" : query.test("")) - query = /x^/; - return query; - } - - var queryDialog = - 'Search: (Use Ctl-Shift-F to Search/Replace)'; - - function startSearch(cm, state, query) { - state.queryText = query; - state.query = parseQuery(query); - cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); - state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); - cm.addOverlay(state.overlay); - if (cm.showMatchesOnScrollbar) { - if (state.annotate) { state.annotate.clear(); state.annotate = null; } - state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query)); - } - } - - function doSearch(cm, rev, persistent, immediate) { - var state = getSearchState(cm); - if (state.query) return findNext(cm, rev); - var q = cm.getSelection() || state.lastQuery; - if (persistent && cm.openDialog) { - var hiding = null - var searchNext = function(query, event) { - CodeMirror.e_stop(event); - if (!query) return; - if (query != state.queryText) { - startSearch(cm, state, query); - state.posFrom = state.posTo = cm.getCursor(); - } - if (hiding) hiding.style.opacity = 1 - findNext(cm, event.shiftKey, function(_, to) { - var dialog - if (to.line < 3 && document.querySelector && - (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) && - dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top) - (hiding = dialog).style.opacity = .4 - }) - }; - persistentDialog(cm, queryDialog, q, searchNext, function(event, query) { - var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][CodeMirror.keyName(event)]; - if (cmd == "findNext" || cmd == "findPrev") { - CodeMirror.e_stop(event); - startSearch(cm, getSearchState(cm), query); - cm.execCommand(cmd); - } else if (cmd == "find" || cmd == "findPersistent") { - CodeMirror.e_stop(event); - searchNext(query, event); - } - }); - if (immediate) { - startSearch(cm, state, q); - findNext(cm, rev); - } - } else { - dialog(cm, queryDialog, "Search for:", q, function(query) { - if (query && !state.query) cm.operation(function() { - startSearch(cm, state, query); - state.posFrom = state.posTo = cm.getCursor(); - findNext(cm, rev); - }); - }); - } - } - - function findNext(cm, rev, callback) {cm.operation(function() { - var state = getSearchState(cm); - var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); - if (!cursor.find(rev)) { - cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)); - if (!cursor.find(rev)) return; - } - cm.setSelection(cursor.from(), cursor.to()); - cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20); - state.posFrom = cursor.from(); state.posTo = cursor.to(); - if (callback) callback(cursor.from(), cursor.to()) - });} - - function clearSearch(cm) {cm.operation(function() { - var state = getSearchState(cm); - state.lastQuery = state.query; - if (!state.query) return; - state.query = state.queryText = null; - cm.removeOverlay(state.overlay); - if (state.annotate) { state.annotate.clear(); state.annotate = null; } - });} - - var replaceQueryDialog = - ' (Use /re/ syntax for regexp search/replace)'; - var replacementQueryDialog = 'With: '; - var doReplaceConfirm = "Replace? "; - - function replaceAll(cm, query, text) { - cm.operation(function() { - for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { - if (typeof query != "string") { - var match = cm.getRange(cursor.from(), cursor.to()).match(query); - cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];})); - } else cursor.replace(text); - } - }); - } - - function replace(cm, all) { - if (cm.getOption("readOnly")) return; - var query = cm.getSelection() || getSearchState(cm).lastQuery; - var dialogText = all ? "Replace all:" : "Replace:" - dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) { - if (!query) return; - query = parseQuery(query); - dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { - text = parseString(text) - if (all) { - replaceAll(cm, query, text) - } else { - clearSearch(cm); - var cursor = getSearchCursor(cm, query, cm.getCursor("from")); - var advance = function() { - var start = cursor.from(), match; - if (!(match = cursor.findNext())) { - cursor = getSearchCursor(cm, query); - if (!(match = cursor.findNext()) || - (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; - } - cm.setSelection(cursor.from(), cursor.to()); - cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); - confirmDialog(cm, doReplaceConfirm, "Replace?", - [function() {doReplace(match);}, advance, - function() {replaceAll(cm, query, text)}]); - }; - var doReplace = function(match) { - cursor.replace(typeof query == "string" ? text : - text.replace(/\$(\d)/g, function(_, i) {return match[i];})); - advance(); - }; - advance(); - } - }); - }); - } - - CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; - CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);}; - CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);}; - CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);}; - CodeMirror.commands.findNext = doSearch; - CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; - CodeMirror.commands.clearSearch = clearSearch; - CodeMirror.commands.replace = replace; - CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; -}); - -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror"), require("./searchcursor"), require("../scroll/annotatescrollbar")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror", "./searchcursor", "../scroll/annotatescrollbar"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - - CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) { - if (typeof options == "string") options = {className: options}; - if (!options) options = {}; - return new SearchAnnotation(this, query, caseFold, options); - }); - - function SearchAnnotation(cm, query, caseFold, options) { - this.cm = cm; - this.options = options; - var annotateOptions = {listenForChanges: false}; - for (var prop in options) annotateOptions[prop] = options[prop]; - if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match"; - this.annotation = cm.annotateScrollbar(annotateOptions); - this.query = query; - this.caseFold = caseFold; - this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1}; - this.matches = []; - this.update = null; - - this.findMatches(); - this.annotation.update(this.matches); - - var self = this; - cm.on("change", this.changeHandler = function(_cm, change) { self.onChange(change); }); - } - - var MAX_MATCHES = 1000; - - SearchAnnotation.prototype.findMatches = function() { - if (!this.gap) return; - for (var i = 0; i < this.matches.length; i++) { - var match = this.matches[i]; - if (match.from.line >= this.gap.to) break; - if (match.to.line >= this.gap.from) this.matches.splice(i--, 1); - } - var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold); - var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES; - while (cursor.findNext()) { - var match = {from: cursor.from(), to: cursor.to()}; - if (match.from.line >= this.gap.to) break; - this.matches.splice(i++, 0, match); - if (this.matches.length > maxMatches) break; - } - this.gap = null; - }; - - function offsetLine(line, changeStart, sizeChange) { - if (line <= changeStart) return line; - return Math.max(changeStart, line + sizeChange); - } - - SearchAnnotation.prototype.onChange = function(change) { - var startLine = change.from.line; - var endLine = CodeMirror.changeEnd(change).line; - var sizeChange = endLine - change.to.line; - if (this.gap) { - this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line); - this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line); - } else { - this.gap = {from: change.from.line, to: endLine + 1}; - } - - if (sizeChange) for (var i = 0; i < this.matches.length; i++) { - var match = this.matches[i]; - var newFrom = offsetLine(match.from.line, startLine, sizeChange); - if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch); - var newTo = offsetLine(match.to.line, startLine, sizeChange); - if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch); - } - clearTimeout(this.update); - var self = this; - this.update = setTimeout(function() { self.updateAfterChange(); }, 250); - }; - - SearchAnnotation.prototype.updateAfterChange = function() { - this.findMatches(); - this.annotation.update(this.matches); - }; - - SearchAnnotation.prototype.clear = function() { - this.cm.off("change", this.changeHandler); - this.annotation.clear(); - }; -}); -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - var ie_lt8 = /MSIE \d/.test(navigator.userAgent) && - (document.documentMode == null || document.documentMode < 8); - - var Pos = CodeMirror.Pos; - - var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; - - function findMatchingBracket(cm, where, strict, config) { - var line = cm.getLineHandle(where.line), pos = where.ch - 1; - var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; - if (!match) return null; - var dir = match.charAt(1) == ">" ? 1 : -1; - if (strict && (dir > 0) != (pos == where.ch)) return null; - var style = cm.getTokenTypeAt(Pos(where.line, pos + 1)); - - var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config); - if (found == null) return null; - return {from: Pos(where.line, pos), to: found && found.pos, - match: found && found.ch == match.charAt(0), forward: dir > 0}; - } - - // bracketRegex is used to specify which type of bracket to scan - // should be a regexp, e.g. /[[\]]/ - // - // Note: If "where" is on an open bracket, then this bracket is ignored. - // - // Returns false when no bracket was found, null when it reached - // maxScanLines and gave up - function scanForBracket(cm, where, dir, style, config) { - var maxScanLen = (config && config.maxScanLineLength) || 10000; - var maxScanLines = (config && config.maxScanLines) || 1000; - - var stack = []; - var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/; - var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) - : Math.max(cm.firstLine() - 1, where.line - maxScanLines); - for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { - var line = cm.getLine(lineNo); - if (!line) continue; - var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1; - if (line.length > maxScanLen) continue; - if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0); - for (; pos != end; pos += dir) { - var ch = line.charAt(pos); - if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) { - var match = matching[ch]; - if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch); - else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch}; - else stack.pop(); - } - } - } - return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; - } - - function matchBrackets(cm, autoclear, config) { - // Disable brace matching in long lines, since it'll cause hugely slow updates - var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000; - var marks = [], ranges = cm.listSelections(); - for (var i = 0; i < ranges.length; i++) { - var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config); - if (match && cm.getLine(match.from.line).length <= maxHighlightLen) { - var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; - marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style})); - if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen) - marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style})); - } - } - - if (marks.length) { - // Kludge to work around the IE bug from issue #1193, where text - // input stops going to the textare whever this fires. - if (ie_lt8 && cm.state.focused) cm.focus(); - - var clear = function() { - cm.operation(function() { - for (var i = 0; i < marks.length; i++) marks[i].clear(); - }); - }; - if (autoclear) setTimeout(clear, 800); - else return clear; - } - } - - var currentlyHighlighted = null; - function doMatchBrackets(cm) { - cm.operation(function() { - if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} - currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets); - }); - } - - CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) { - if (old && old != CodeMirror.Init) { - cm.off("cursorActivity", doMatchBrackets); - if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} - } - if (val) { - cm.state.matchBrackets = typeof val == "object" ? val : {}; - cm.on("cursorActivity", doMatchBrackets); - } - }); - - CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); - CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){ - return findMatchingBracket(this, pos, strict, config); - }); - CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){ - return scanForBracket(this, pos, dir, style, config); - }); -}); - - -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - - CodeMirror.defineExtension("annotateScrollbar", function(options) { - if (typeof options == "string") options = {className: options}; - return new Annotation(this, options); - }); - - CodeMirror.defineOption("scrollButtonHeight", 0); - - function Annotation(cm, options) { - this.cm = cm; - this.options = options; - this.buttonHeight = options.scrollButtonHeight || cm.getOption("scrollButtonHeight"); - this.annotations = []; - this.doRedraw = this.doUpdate = null; - this.div = cm.getWrapperElement().appendChild(document.createElement("div")); - this.div.style.cssText = "position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none"; - this.computeScale(); - - function scheduleRedraw(delay) { - clearTimeout(self.doRedraw); - self.doRedraw = setTimeout(function() { self.redraw(); }, delay); - } - - var self = this; - cm.on("refresh", this.resizeHandler = function() { - clearTimeout(self.doUpdate); - self.doUpdate = setTimeout(function() { - if (self.computeScale()) scheduleRedraw(20); - }, 100); - }); - cm.on("markerAdded", this.resizeHandler); - cm.on("markerCleared", this.resizeHandler); - if (options.listenForChanges !== false) - cm.on("change", this.changeHandler = function() { - scheduleRedraw(250); - }); - } - - Annotation.prototype.computeScale = function() { - var cm = this.cm; - var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) / - cm.getScrollerElement().scrollHeight - if (hScale != this.hScale) { - this.hScale = hScale; - return true; - } - }; - - Annotation.prototype.update = function(annotations) { - this.annotations = annotations; - this.redraw(); - }; - - Annotation.prototype.redraw = function(compute) { - if (compute !== false) this.computeScale(); - var cm = this.cm, hScale = this.hScale; - - var frag = document.createDocumentFragment(), anns = this.annotations; - - var wrapping = cm.getOption("lineWrapping"); - var singleLineH = wrapping && cm.defaultTextHeight() * 1.5; - var curLine = null, curLineObj = null; - function getY(pos, top) { - if (curLine != pos.line) { - curLine = pos.line; - curLineObj = cm.getLineHandle(curLine); - } - if (wrapping && curLineObj.height > singleLineH) - return cm.charCoords(pos, "local")[top ? "top" : "bottom"]; - var topY = cm.heightAtLine(curLineObj, "local"); - return topY + (top ? 0 : curLineObj.height); - } - - if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) { - var ann = anns[i]; - var top = nextTop || getY(ann.from, true) * hScale; - var bottom = getY(ann.to, false) * hScale; - while (i < anns.length - 1) { - nextTop = getY(anns[i + 1].from, true) * hScale; - if (nextTop > bottom + .9) break; - ann = anns[++i]; - bottom = getY(ann.to, false) * hScale; - } - if (bottom == top) continue; - var height = Math.max(bottom - top, 3); - - var elt = frag.appendChild(document.createElement("div")); - elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: " - + (top + this.buttonHeight) + "px; height: " + height + "px"; - elt.className = this.options.className; - if (ann.id) { - elt.setAttribute("annotation-id", ann.id); - } - } - this.div.textContent = ""; - this.div.appendChild(frag); - }; - - Annotation.prototype.clear = function() { - this.cm.off("refresh", this.resizeHandler); - this.cm.off("markerAdded", this.resizeHandler); - this.cm.off("markerCleared", this.resizeHandler); - if (this.changeHandler) this.cm.off("change", this.changeHandler); - this.div.parentNode.removeChild(this.div); - }; -}); - -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -// Highlighting text that matches the selection -// -// Defines an option highlightSelectionMatches, which, when enabled, -// will style strings that match the selection throughout the -// document. -// -// The option can be set to true to simply enable it, or to a -// {minChars, style, wordsOnly, showToken, delay} object to explicitly -// configure it. minChars is the minimum amount of characters that should be -// selected for the behavior to occur, and style is the token style to -// apply to the matches. This will be prefixed by "cm-" to create an -// actual CSS class name. If wordsOnly is enabled, the matches will be -// highlighted only if the selected text is a word. showToken, when enabled, -// will cause the current token to be highlighted when nothing is selected. -// delay is used to specify how much time to wait, in milliseconds, before -// highlighting the matches. If annotateScrollbar is enabled, the occurences -// will be highlighted on the scrollbar via the matchesonscrollbar addon. - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror"), require("./matchesonscrollbar")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror", "./matchesonscrollbar"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - - var defaults = { - style: "matchhighlight", - minChars: 2, - delay: 100, - wordsOnly: false, - annotateScrollbar: false, - showToken: false, - trim: true - } - - function State(options) { - this.options = {} - for (var name in defaults) - this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name] - this.overlay = this.timeout = null; - this.matchesonscroll = null; - } - - CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) { - if (old && old != CodeMirror.Init) { - removeOverlay(cm); - clearTimeout(cm.state.matchHighlighter.timeout); - cm.state.matchHighlighter = null; - cm.off("cursorActivity", cursorActivity); - } - if (val) { - cm.state.matchHighlighter = new State(val); - highlightMatches(cm); - cm.on("cursorActivity", cursorActivity); - } - }); - - function cursorActivity(cm) { - var state = cm.state.matchHighlighter; - clearTimeout(state.timeout); - state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay); - } - - function addOverlay(cm, query, hasBoundary, style) { - var state = cm.state.matchHighlighter; - cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style)); - if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) { - var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query; - state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false, - {className: "CodeMirror-selection-highlight-scrollbar"}); - } - } - - function removeOverlay(cm) { - var state = cm.state.matchHighlighter; - if (state.overlay) { - cm.removeOverlay(state.overlay); - state.overlay = null; - if (state.matchesonscroll) { - state.matchesonscroll.clear(); - state.matchesonscroll = null; - } - } - } - - function highlightMatches(cm) { - cm.operation(function() { - var state = cm.state.matchHighlighter; - removeOverlay(cm); - if (!cm.somethingSelected() && state.options.showToken) { - var re = state.options.showToken === true ? /[\w$]/ : state.options.showToken; - var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start; - while (start && re.test(line.charAt(start - 1))) --start; - while (end < line.length && re.test(line.charAt(end))) ++end; - if (start < end) - addOverlay(cm, line.slice(start, end), re, state.options.style); - return; - } - var from = cm.getCursor("from"), to = cm.getCursor("to"); - if (from.line != to.line) return; - if (state.options.wordsOnly && !isWord(cm, from, to)) return; - var selection = cm.getRange(from, to) - if (state.options.trim) selection = selection.replace(/^\s+|\s+$/g, "") - if (selection.length >= state.options.minChars) - addOverlay(cm, selection, false, state.options.style); - }); - } - - function isWord(cm, from, to) { - var str = cm.getRange(from, to); - if (str.match(/^\w+$/) !== null) { - if (from.ch > 0) { - var pos = {line: from.line, ch: from.ch - 1}; - var chr = cm.getRange(pos, from); - if (chr.match(/\W/) === null) return false; - } - if (to.ch < cm.getLine(from.line).length) { - var pos = {line: to.line, ch: to.ch + 1}; - var chr = cm.getRange(to, pos); - if (chr.match(/\W/) === null) return false; - } - return true; - } else return false; - } - - function boundariesAround(stream, re) { - return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) && - (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos))); - } - - function makeOverlay(query, hasBoundary, style) { - return {token: function(stream) { - if (stream.match(query) && - (!hasBoundary || boundariesAround(stream, hasBoundary))) - return style; - stream.next(); - stream.skipTo(query.charAt(0)) || stream.skipToEnd(); - }}; - } -}); - diff --git a/public/Rederly-50.png b/public/Rederly-50.png deleted file mode 100644 index d551faf44..000000000 Binary files a/public/Rederly-50.png and /dev/null differ diff --git a/public/css/bootstrap.scss b/public/css/bootstrap.scss new file mode 100644 index 000000000..5beb1d886 --- /dev/null +++ b/public/css/bootstrap.scss @@ -0,0 +1,89 @@ +/* WeBWorK Online Homework Delivery System + * Copyright © 2000-2021 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. + */ + +// Include functions first (so you can manipulate colors, SVGs, calc, etc) +@import "../node_modules/bootstrap/scss/functions"; + +// Variable overrides + +// Enable shadows and gradients. These are disabled by default. +$enable-shadows: true; + +// Use a smaller grid gutter width. The default is 1.5rem. +$grid-gutter-width: 1rem; + +// Fonts +$font-size-base: 0.85rem; +$headings-font-weight: 600; + +// Links +$link-decoration: none; +$link-hover-decoration: underline; + +// Make breadcrumb dividers and active items a bit darker. +$breadcrumb-divider-color: #495057; +$breadcrumb-active-color: #495057; + +// Include the remainder of bootstrap's scss configuration +@import "../node_modules/bootstrap/scss/variables"; +@import "../node_modules/bootstrap/scss/maps"; +@import "../node_modules/bootstrap/scss/mixins"; +@import "../node_modules/bootstrap/scss/utilities"; + +// Layout & components +@import "../node_modules/bootstrap/scss/root"; +@import "../node_modules/bootstrap/scss/reboot"; +@import "../node_modules/bootstrap/scss/type"; +@import "../node_modules/bootstrap/scss/images"; +@import "../node_modules/bootstrap/scss/containers"; +@import "../node_modules/bootstrap/scss/grid"; +@import "../node_modules/bootstrap/scss/tables"; +@import "../node_modules/bootstrap/scss/forms"; +@import "../node_modules/bootstrap/scss/buttons"; +@import "../node_modules/bootstrap/scss/transitions"; +@import "../node_modules/bootstrap/scss/dropdown"; +@import "../node_modules/bootstrap/scss/button-group"; +@import "../node_modules/bootstrap/scss/nav"; +@import "../node_modules/bootstrap/scss/navbar"; +@import "../node_modules/bootstrap/scss/card"; +@import "../node_modules/bootstrap/scss/accordion"; +@import "../node_modules/bootstrap/scss/breadcrumb"; +@import "../node_modules/bootstrap/scss/pagination"; +@import "../node_modules/bootstrap/scss/badge"; +@import "../node_modules/bootstrap/scss/alert"; +@import "../node_modules/bootstrap/scss/placeholders"; +@import "../node_modules/bootstrap/scss/progress"; +@import "../node_modules/bootstrap/scss/list-group"; +@import "../node_modules/bootstrap/scss/close"; +@import "../node_modules/bootstrap/scss/toasts"; +@import "../node_modules/bootstrap/scss/modal"; +@import "../node_modules/bootstrap/scss/tooltip"; +@import "../node_modules/bootstrap/scss/popover"; +@import "../node_modules/bootstrap/scss/carousel"; +@import "../node_modules/bootstrap/scss/spinners"; +@import "../node_modules/bootstrap/scss/offcanvas"; + +// Helpers +@import "../node_modules/bootstrap/scss/helpers"; + +// Utilities +@import "../node_modules/bootstrap/scss/utilities/api"; + +// Overrides +a:not(.btn):focus { + color: $link-hover-color; + outline-style: solid; + outline-color: $link-hover-color; + outline-width: 1px; +} diff --git a/public/crt-display.css b/public/css/crt-display.css similarity index 100% rename from public/crt-display.css rename to public/css/crt-display.css diff --git a/public/filebrowser.css b/public/css/filebrowser.css similarity index 100% rename from public/filebrowser.css rename to public/css/filebrowser.css diff --git a/public/navbar.css b/public/css/navbar.css similarity index 100% rename from public/navbar.css rename to public/css/navbar.css diff --git a/public/opl-flex.css b/public/css/opl-flex.css similarity index 100% rename from public/opl-flex.css rename to public/css/opl-flex.css diff --git a/public/css/rtl.css b/public/css/rtl.css new file mode 100644 index 000000000..979d21a46 --- /dev/null +++ b/public/css/rtl.css @@ -0,0 +1,20 @@ +/* WeBWorK Online Homework Delivery System + * Copyright © 2000-2022 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/public/tags.css b/public/css/tags.css similarity index 100% rename from public/tags.css rename to public/css/tags.css diff --git a/public/twocolumn.css b/public/css/twocolumn.css similarity index 100% rename from public/twocolumn.css rename to public/css/twocolumn.css diff --git a/public/typing-sim.css b/public/css/typing-sim.css similarity index 100% rename from public/typing-sim.css rename to public/css/typing-sim.css diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index 10893e0d6..000000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/generate-assets.js b/public/generate-assets.js new file mode 100755 index 000000000..fa9a061ce --- /dev/null +++ b/public/generate-assets.js @@ -0,0 +1,219 @@ +#!/usr/bin/env node + +/* eslint-env node */ + +const yargs = require('yargs'); +const chokidar = require('chokidar'); +const path = require('path'); +const { minify } = require('terser'); +const fs = require('fs'); +const crypto = require('crypto'); +const sass = require('sass'); +const autoprefixer = require('autoprefixer'); +const postcss = require('postcss'); +const rtlcss = require('rtlcss'); +const cssMinify = require('cssnano'); + +const argv = yargs + .usage('$0 Options').version(false).alias('help', 'h').wrap(100) + .option('enable-sourcemaps', { + alias: 's', + description: 'Generate source maps. (Not for use in production!)', + type: 'boolean' + }) + .option('watch-files', { + alias: 'w', + description: 'Continue to watch files for changes. (Developer tool)', + type: 'boolean' + }) + .option('clean', { + alias: 'd', + description: 'Delete all generated files.', + type: 'boolean' + }) + .argv; + +const assetFile = path.resolve(__dirname, 'static-assets.json'); +const assets = {}; + +const cleanDir = (dir) => { + for (const file of fs.readdirSync(dir, { withFileTypes: true })) { + if (file.isDirectory()) { + cleanDir(path.resolve(dir, file.name)); + } else { + if (/.[a-z0-9]{8}.min.(css|js)$/.test(file.name)) { + const fullPath = path.resolve(dir, file.name); + console.log(`\x1b[34mRemoving ${fullPath} from previous build.\x1b[0m`); + fs.unlinkSync(fullPath); + } + } + } +} + +// The is set to true after all files are processed for the first time. +let ready = false; + +const processFile = async (file, _details) => { + if (file) { + const baseName = path.basename(file); + + if (/(? { + // If a file is deleted, then also delete the corresponding generated file. + if (assets[file]) { + console.log(`\x1b[34mDeleting minified file for ${file}.\x1b[0m`); + fs.unlinkSync(path.resolve(__dirname, assets[file])); + delete assets[file]; + } + }) + .on('error', (error) => console.log(`\x1b[32m${error}\x1b[0m`)); diff --git a/public/iframeResizer.contentWindow.map b/public/iframeResizer.contentWindow.map deleted file mode 100644 index 6f732004f..000000000 --- a/public/iframeResizer.contentWindow.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["iframeResizer.contentWindow.js"],"names":["undefined","window","autoResize","base","bodyBackground","bodyMargin","bodyMarginStr","bodyObserver","bodyPadding","calculateWidth","doubleEventList","resize","click","eventCancelTimer","firstRun","height","heightCalcModeDefault","heightCalcMode","initLock","initMsg","inPageLinks","interval","intervalTimer","logging","msgID","msgIdLen","length","myID","resetRequiredMethods","max","min","bodyScroll","documentElementScroll","resizeFrom","sendPermit","target","parent","targetOriginDefault","tolerance","triggerLocked","triggerLockedTimer","throttledTimer","width","widthCalcModeDefault","widthCalcMode","win","onMessage","warn","onReady","onPageInfo","customCalcMethods","document","documentElement","offsetHeight","body","scrollWidth","eventHandlersByName","passiveSupported","options","Object","create","passive","get","addEventListener","noop","removeEventListener","error","func","context","args","result","timeout","previous","getNow","Date","now","getTime","getHeight","bodyOffset","getComputedStyle","offset","scrollHeight","custom","documentElementOffset","Math","apply","getAllMeasurements","grow","lowestElement","getMaxElement","getAllElements","taggedElement","getTaggedElements","getWidth","offsetWidth","scroll","rightMostElement","sizeIFrameThrottled","sizeIFrame","remaining","this","arguments","clearTimeout","setTimeout","later","event","processRequestFromParent","init","data","source","reset","log","triggerReset","sendSize","moveToAnchor","findTarget","getData","inPageLink","pageInfo","msgBody","JSON","parse","message","getMessageType","split","substr","indexOf","isInitMsg","true","false","callFromParent","messageType","module","exports","jQuery","prototype","chkLateLoaded","el","evt","capitalizeFirstLetter","string","charAt","toUpperCase","slice","formatLogMsg","msg","console","strBool","str","Number","enable","readDataFromParent","location","href","setupCustomCalcMethods","calcMode","calcFunc","iFrameResizer","constructor","stringify","keys","forEach","depricate","targetOrigin","heightCalculationMethod","widthCalculationMethod","readData","readDataFromPage","setBodyStyle","attr","value","chkCSS","setMargin","clearFix","createElement","style","clear","display","appendChild","injectClearFixIntoBodyElement","checkHeightMode","checkWidthMode","parentIFrame","startEventListeners","manageEventListeners","disconnect","clearInterval","sendMsg","close","getId","getPageInfo","callback","hash","resetIFrame","scrollTo","x","y","scrollToOffset","sendMessage","setHeightCalculationMethod","setWidthCalculationMethod","setTargetOrigin","size","customHeight","customWidth","getElementPosition","elPosition","getBoundingClientRect","pagePosition","pageXOffset","scrollLeft","pageYOffset","scrollTop","parseInt","left","top","jumpPosition","hashData","decodeURIComponent","getElementById","getElementsByName","checkLocationHash","bindAnchors","Array","call","querySelectorAll","getAttribute","e","preventDefault","setupInPageLinks","key","splitName","name","manageTriggerEvent","listener","add","eventName","handleEvent","eventType","remove","eventNames","map","method","checkCalcMode","calcModeDefault","modes","type","forceIntervalTimer","MutationObserver","WebKitMutationObserver","initInterval","addImageLoadListners","mutation","addImageLoadListener","element","complete","src","imageLoaded","imageError","elements","push","attributeName","removeImageLoadListener","splice","removeFromArray","imageEventTriggered","typeDesc","mutationObserved","mutations","observer","querySelector","observe","attributes","attributeOldValue","characterData","characterDataOldValue","childList","subtree","createMutationObserver","setupBodyMutationObserver","setupMutationObserver","setInterval","abs","prop","retVal","defaultView","side","elementsLength","elVal","maxVal","Side","timer","i","chkEventThottle","dimention","tag","triggerEvent","triggerEventDesc","currentHeight","currentWidth","checkTolarance","a","b","lockTrigger","resetPage","hcm","postMessage","readyState"],"mappings":";;;;;;;;CAWC,SAAWA,GACV,GAAsB,oBAAXC,OAAX,CAEA,IAAIC,GAAa,EACfC,EAAO,GACPC,EAAiB,GACjBC,EAAa,EACbC,EAAgB,GAChBC,EAAe,KACfC,EAAc,GACdC,GAAiB,EACjBC,EAAkB,CAAEC,OAAQ,EAAGC,MAAO,GACtCC,EAAmB,IACnBC,GAAW,EACXC,EAAS,EACTC,EAAwB,aACxBC,EAAiBD,EACjBE,GAAW,EACXC,EAAU,GACVC,EAAc,GACdC,EAAW,GACXC,EAAgB,KAChBC,GAAU,EACVC,EAAQ,gBACRC,EAAWD,EAAME,OACjBC,EAAO,GACPC,EAAuB,CACrBC,IAAK,EACLC,IAAK,EACLC,WAAY,EACZC,sBAAuB,GAEzBC,EAAa,QACbC,GAAa,EACbC,EAASlC,OAAOmC,OAChBC,EAAsB,IACtBC,EAAY,EACZC,GAAgB,EAChBC,EAAqB,KACrBC,EAAiB,GACjBC,EAAQ,EACRC,EAAuB,SACvBC,EAAgBD,EAChBE,EAAM5C,OACN6C,EAAY,WACVC,GAAK,mCAEPC,EAAU,aACVC,EAAa,aACbC,EAAoB,CAClBnC,OAAQ,WAEN,OADAgC,GAAK,kDACEI,SAASC,gBAAgBC,cAElCX,MAAO,WAEL,OADAK,GAAK,iDACEI,SAASG,KAAKC,cAGzBC,EAAsB,GACtBC,GAAmB,EAIrB,IACE,IAAIC,EAAUC,OAAOC,OACnB,GACA,CACEC,QAAS,CACPC,IAAK,WACHL,GAAmB,MAK3BxD,OAAO8D,iBAAiB,OAAQC,GAAMN,GACtCzD,OAAOgE,oBAAoB,OAAQD,GAAMN,GACzC,MAAOQ,IAkET,IAjDkBC,EACZC,EACFC,EACAC,EACAC,EACAC,EA4CAC,EACFC,KAAKC,KACL,WAEE,OAAO,IAAID,MAAOE,WA4vBlBC,EAAY,CACZC,WAAY,WACV,OACE3B,SAASG,KAAKD,aACd0B,GAAiB,aACjBA,GAAiB,iBAIrBC,OAAQ,WACN,OAAOH,EAAUC,cAGnB/C,WAAY,WACV,OAAOoB,SAASG,KAAK2B,cAGvBC,OAAQ,WACN,OAAOhC,EAAkBnC,UAG3BoE,sBAAuB,WACrB,OAAOhC,SAASC,gBAAgBC,cAGlCrB,sBAAuB,WACrB,OAAOmB,SAASC,gBAAgB6B,cAGlCpD,IAAK,WACH,OAAOuD,KAAKvD,IAAIwD,MAAM,KAAMC,GAAmBT,KAGjD/C,IAAK,WACH,OAAOsD,KAAKtD,IAAIuD,MAAM,KAAMC,GAAmBT,KAGjDU,KAAM,WACJ,OAAOV,EAAUhD,OAGnB2D,cAAe,WACb,OAAOJ,KAAKvD,IACVgD,EAAUC,cAAgBD,EAAUM,wBACpCM,GAAc,SAAUC,QAI5BC,cAAe,WACb,OAAOC,GAAkB,SAAU,wBAGvCC,EAAW,CACT9D,WAAY,WACV,OAAOoB,SAASG,KAAKC,aAGvBuB,WAAY,WACV,OAAO3B,SAASG,KAAKwC,aAGvBZ,OAAQ,WACN,OAAOhC,EAAkBR,SAG3BV,sBAAuB,WACrB,OAAOmB,SAASC,gBAAgBG,aAGlC4B,sBAAuB,WACrB,OAAOhC,SAASC,gBAAgB0C,aAGlCC,OAAQ,WACN,OAAOX,KAAKvD,IAAIgE,EAAS9D,aAAc8D,EAAS7D,0BAGlDH,IAAK,WACH,OAAOuD,KAAKvD,IAAIwD,MAAM,KAAMC,GAAmBO,KAGjD/D,IAAK,WACH,OAAOsD,KAAKtD,IAAIuD,MAAM,KAAMC,GAAmBO,KAGjDG,iBAAkB,WAChB,OAAOP,GAAc,QAASC,OAGhCC,cAAe,WACb,OAAOC,GAAkB,QAAS,uBAmEpCK,GA98Bc9B,EA88BiB+B,GA18B/B3B,EAAU,KACVC,EAAW,EAWN,WACL,IAAIG,EAAMF,IAMN0B,EAAY1D,GAAkBkC,GAHhCH,EADGA,GACQG,IAyBb,OApBAP,EAAUgC,KACV/B,EAAOgC,UAEHF,GAAa,GAAiB1D,EAAZ0D,GAChB5B,IACF+B,aAAa/B,GACbA,EAAU,MAGZC,EAAWG,EACXL,EAASH,EAAKkB,MAAMjB,EAASC,GAExBE,IAEHH,EAAUC,EAAO,OAGnBE,EADUA,GACAgC,WAAWC,GAAOL,GAGvB7B,IA4mCXP,GAAiB9D,OAAQ,UAjHzB,SAAkBwG,GAChB,IAAIC,EAA2B,CAC7BC,KAAM,WACJxF,EAAUsF,EAAMG,KAChBzE,EAASsE,EAAMI,OAEfF,KACA7F,GAAW,EACXyF,WAAW,WACTrF,GAAW,GACVL,IAGLiG,MAAO,WACA5F,EAIH6F,GAAI,+BAHJA,GAAI,gCACJC,GAAa,eAMjBrG,OAAQ,WACNsG,GAAS,eAAgB,uCAG3BC,aAAc,WACZ9F,EAAY+F,WAAWC,MAEzBC,WAAY,WACVjB,KAAKc,gBAGPI,SAAU,WACR,IAAIC,EAAUH,IACdL,GAAI,0CAA4CQ,GAChDtE,EAAWuE,KAAKC,MAAMF,IACtBR,GAAI,QAGNW,QAAS,WACP,IAAIH,EAAUH,IAEdL,GAAI,iCAAmCQ,GAEvCzE,EAAU0E,KAAKC,MAAMF,IACrBR,GAAI,SAQR,SAASY,IACP,OAAOlB,EAAMG,KAAKgB,MAAM,KAAK,GAAGA,MAAM,KAAK,GAG7C,SAASR,IACP,OAAOX,EAAMG,KAAKiB,OAAOpB,EAAMG,KAAKkB,QAAQ,KAAO,GAWrD,SAASC,IAGP,OAAOtB,EAAMG,KAAKgB,MAAM,KAAK,IAAM,CAAEI,KAAM,EAAGC,MAAO,GAGvD,SAASC,IACP,IAAIC,EAAcR,IAEdQ,KAAezB,EACjBA,EAAyByB,MAhBJ,oBAAXC,SAA0BA,OAAOC,UACzC,iBAAkBpI,QACnB,WAAYA,QAAU,iBAAkBA,OAAOqI,OAAOC,WAe1BR,KAC7BhF,GAAK,uBAAyB0D,EAAMG,KAAO,KA/BtCpF,KAAW,GAAKiF,EAAMG,MAAMiB,OAAO,EAAGpG,MAoCzC,IAAUX,EACZoH,IACSH,IACTrB,EAAyBC,OAEzBI,GACE,4BACEY,IACA,yCAmBV5D,GAAiB9D,OAAQ,mBAAoBuI,IAC7CA,KA3rCA,SAASxE,MAmBT,SAASD,GAAiB0E,EAAIC,EAAKvE,EAAMT,GACvC+E,EAAG1E,iBAAiB2E,EAAKvE,IAAMV,IAAmBC,GAAW,KAO/D,SAASiF,GAAsBC,GAC7B,OAAOA,EAAOC,OAAO,GAAGC,cAAgBF,EAAOG,MAAM,GA4DvD,SAASC,GAAaC,GACpB,OAAOzH,EAAQ,IAAMG,EAAO,KAAOsH,EAGrC,SAASlC,GAAIkC,GACP1H,GAAW,iBAAoBtB,OAAOiJ,SAExCA,QAAQnC,IAAIiC,GAAaC,IAI7B,SAASlG,GAAKkG,GACR,iBAAoBhJ,OAAOiJ,SAE7BA,QAAQnG,KAAKiG,GAAaC,IAI9B,SAAStC,MAkBT,WACE,SAASwC,EAAQC,GACf,MAAO,SAAWA,EAGpB,IAAIxC,EAAOzF,EAAQ0G,OAAOpG,GAAUmG,MAAM,KAE1CjG,EAAOiF,EAAK,GACZvG,EAAaL,IAAc4G,EAAK,GAAKyC,OAAOzC,EAAK,IAAMvG,EACvDI,EAAiBT,IAAc4G,EAAK,GAAKuC,EAAQvC,EAAK,IAAMnG,EAC5Dc,EAAUvB,IAAc4G,EAAK,GAAKuC,EAAQvC,EAAK,IAAMrF,EACrDF,EAAWrB,IAAc4G,EAAK,GAAKyC,OAAOzC,EAAK,IAAMvF,EACrDnB,EAAaF,IAAc4G,EAAK,GAAKuC,EAAQvC,EAAK,IAAM1G,EACxDI,EAAgBsG,EAAK,GACrB3F,EAAiBjB,IAAc4G,EAAK,GAAKA,EAAK,GAAK3F,EACnDb,EAAiBwG,EAAK,GACtBpG,EAAcoG,EAAK,IACnBtE,EAAYtC,IAAc4G,EAAK,IAAMyC,OAAOzC,EAAK,KAAOtE,EACxDlB,EAAYkI,OAAStJ,IAAc4G,EAAK,KAAMuC,EAAQvC,EAAK,KAC3D3E,EAAajC,IAAc4G,EAAK,IAAMA,EAAK,IAAM3E,EACjDW,EAAgB5C,IAAc4G,EAAK,IAAMA,EAAK,IAAMhE,EArCpD2G,GACAxC,GAAI,wBAA0ByC,SAASC,KAAO,KAyDhD,WAqBE,SAASC,EAAuBC,EAAUC,GAOxC,MANI,mBAAsBD,IACxB5C,GAAI,gBAAkB6C,EAAW,cACjC1G,EAAkB0G,GAAYD,EAC9BA,EAAW,UAGNA,EAIP,kBAAmB1J,QACnB0D,SAAW1D,OAAO4J,cAAcC,cAhClC,WACE,IAAIlD,EAAO3G,OAAO4J,cAElB9C,GAAI,2BAA6BS,KAAKuC,UAAUnD,IAChDjD,OAAOqG,KAAKpD,GAAMqD,QAAQC,GAAWtD,GAErC9D,EAAY,cAAe8D,EAAOA,EAAK9D,UAAYA,EACnDE,EAAU,YAAa4D,EAAOA,EAAK5D,QAAUA,EAC7CX,EACE,iBAAkBuE,EAAOA,EAAKuD,aAAe9H,EAC/CpB,EACE,4BAA6B2F,EACzBA,EAAKwD,wBACLnJ,EACN2B,EACE,2BAA4BgE,EACxBA,EAAKyD,uBACLzH,EAiBN0H,GACArJ,EAAiByI,EAAuBzI,EAAgB,UACxD2B,EAAgB8G,EAAuB9G,EAAe,UAGxDmE,GAAI,mCAAqC1E,GAhGzCkI,GAkHF,WAEMvK,IAAcM,IAChBA,EAAgBD,EAAa,MAG/BmK,GAAa,SArBf,SAAgBC,EAAMC,IACf,IAAMA,EAAM5C,QAAQ,OACvB/E,GAAK,kCAAoC0H,GACzCC,EAAQ,IAEV,OAAOA,EAgBgBC,CAAO,SAAUrK,IAvHxCsK,GACAJ,GAAa,aAAcpK,GAC3BoK,GAAa,UAAWhK,GA4U1B,WACE,IAAIqK,EAAW1H,SAAS2H,cAAc,OACtCD,EAASE,MAAMC,MAAQ,OAEvBH,EAASE,MAAME,QAAU,QACzBJ,EAASE,MAAMhK,OAAS,IACxBoC,SAASG,KAAK4H,YAAYL,GAjV1BM,GACAC,KACAC,KAsHAlI,SAASC,gBAAgB2H,MAAMhK,OAAS,GACxCoC,SAASG,KAAKyH,MAAMhK,OAAS,GAC7BgG,GAAI,oCAgVJA,GAAI,yBAEJlE,EAAIyI,aAAe,CACjBpL,WAAY,SAAqBS,GAS/B,OARI,IAASA,IAAU,IAAUT,GAC/BA,GAAa,EACbqL,OACS,IAAU5K,IAAU,IAAST,IACtCA,GAAa,EAlJnBsL,GAAqB,UAPjB,OAASjL,GAEXA,EAAakL,aAOfC,cAAcpK,IAmJVqK,GAAQ,EAAG,EAAG,aAAcnE,KAAKuC,UAAU7J,IACpCA,GAGT0L,MAAO,WACLD,GAAQ,EAAG,EAAG,UAIhBE,MAAO,WACL,OAAOlK,GAGTmK,YAAa,SAAsBC,GAC7B,mBAAsBA,GACxB9I,EAAa8I,EACbJ,GAAQ,EAAG,EAAG,cAEd1I,EAAa,aACb0I,GAAQ,EAAG,EAAG,kBAIlBzE,aAAc,SAAuB8E,GACnC5K,EAAY+F,WAAW6E,IAGzBlF,MAAO,WACLmF,GAAY,uBAGdC,SAAU,SAAmBC,EAAGC,GAC9BT,GAAQS,EAAGD,EAAG,aAGhBE,eAAgB,SAAmBF,EAAGC,GACpCT,GAAQS,EAAGD,EAAG,mBAGhBG,YAAa,SAAsBrD,EAAKkB,GACtCwB,GAAQ,EAAG,EAAG,UAAWnE,KAAKuC,UAAUd,GAAMkB,IAGhDoC,2BAA4B,SAC1BnC,GAEAnJ,EAAiBmJ,EACjBgB,MAGFoB,0BAA2B,SACzBnC,GAEAzH,EAAgByH,EAChBgB,MAGFoB,gBAAiB,SAA0BtC,GACzCpD,GAAI,qBAAuBoD,GAC3B9H,EAAsB8H,GAGxBuC,KAAM,SAAeC,EAAcC,GAGjC3F,GACE,OACA,uBAHM0F,GAAgB,KAAOC,EAAc,IAAMA,EAAc,KAG5B,IACnCD,EACAC,KArhBNrB,KACAnK,EA8UF,WAcE,SAASyL,EAAmBpE,GAC1B,IAAIqE,EAAarE,EAAGsE,wBAClBC,EAdK,CACLb,EACElM,OAAOgN,cAAgBjN,EACnBC,OAAOgN,YACP9J,SAASC,gBAAgB8J,WAC/Bd,EACEnM,OAAOkN,cAAgBnN,EACnBC,OAAOkN,YACPhK,SAASC,gBAAgBgK,WAQjC,MAAO,CACLjB,EAAGkB,SAASP,EAAWQ,KAAM,IAAMD,SAASL,EAAab,EAAG,IAC5DC,EAAGiB,SAASP,EAAWS,IAAK,IAAMF,SAASL,EAAaZ,EAAG,KAI/D,SAASjF,EAAWqC,GAelB,IAbMgE,EAaFxB,EAAOxC,EAAS5B,MAAM,KAAK,IAAM4B,EACnCiE,EAAWC,mBAAmB1B,GAC9B7J,EACEgB,SAASwK,eAAeF,IACxBtK,SAASyK,kBAAkBH,GAAU,GAErCzN,IAAcmC,GAnBZqL,EAAeX,EAoBN1K,GAlBb4E,GACE,4BACEiF,EACA,WACAwB,EAAarB,EACb,OACAqB,EAAapB,GAEjBT,GAAQ6B,EAAapB,EAAGoB,EAAarB,EAAG,oBAYxCpF,GACE,kBACEiF,EACA,+CAEJL,GAAQ,EAAG,EAAG,aAAc,IAAMK,IAItC,SAAS6B,IACH,KAAOrE,SAASwC,MAAQ,MAAQxC,SAASwC,MAC3C7E,EAAWqC,SAASC,MAIxB,SAASqE,IAcPC,MAAMxF,UAAU0B,QAAQ+D,KACtB7K,SAAS8K,iBAAiB,gBAd5B,SAAmBxF,GAQb,MAAQA,EAAGyF,aAAa,SAC1BnK,GAAiB0E,EAAI,QARvB,SAAqB0F,GACnBA,EAAEC,iBAGFjH,EAAWf,KAAK8H,aAAa,aAqC/B9M,EAAYkI,OAZVyE,MAAMxF,UAAU0B,SAAW9G,SAAS8K,kBACtClH,GAAI,qCACJ+G,IAZF/J,GAAiB9D,OAAQ,aAAc4N,GAKvCtH,WAAWsH,EAAmBhN,IAW5BkC,GACE,2FAQJgE,GAAI,+BAGN,MAAO,CACLI,WAAYA,GA/bAkH,GACdpH,GAAS,OAAQ,+BACjBjE,IA0BF,SAASkH,GAAUoE,GACjB,IAAIC,EAAYD,EAAI1G,MAAM,YAE1B,GAAyB,IAArB2G,EAAU7M,OAAc,CAC1B,IAAI8M,EACF,KAAOD,EAAU,GAAG1F,OAAO,GAAGC,cAAgByF,EAAU,GAAGxF,MAAM,GACnE3C,KAAKoI,GAAQpI,KAAKkI,UACXlI,KAAKkI,GACZvL,GACE,gBACEuL,EACA,uBACAE,EACA,iEAwDR,SAAShE,GAAaC,EAAMC,GACtB1K,IAAc0K,GAAS,KAAOA,GAAS,SAAWA,GAEpD3D,GAAI,QAAU0D,EAAO,aADrBtH,SAASG,KAAKyH,MAAMN,GAAQC,GACe,KAmB/C,SAAS+D,GAAmB/K,GAC1B,IAAIgL,EAAW,CACbC,IAAK,SAAUC,GACb,SAASC,IACP5H,GAASvD,EAAQkL,UAAWlL,EAAQoL,WAGtCtL,EAAoBoL,GAAaC,EAEjC9K,GAAiB9D,OAAQ2O,EAAWC,EAAa,CAAEhL,SAAS,KAE9DkL,OAAQ,SAAUH,GAChB,IAAIC,EAAcrL,EAAoBoL,UAC/BpL,EAAoBoL,GApOjC,SAA6BnG,EAAIC,EAAKvE,GACpCsE,EAAGxE,oBAAoByE,EAAKvE,GAAM,GAqO9BF,CAAoBhE,OAAQ2O,EAAWC,KAIvCnL,EAAQsL,YAAcjB,MAAMxF,UAAU0G,KACxCvL,EAAQkL,UAAYlL,EAAQsL,WAAW,GACvCtL,EAAQsL,WAAWC,IAAIP,EAAShL,EAAQwL,UAExCR,EAAShL,EAAQwL,QAAQxL,EAAQkL,WAGnC7H,GACE4B,GAAsBjF,EAAQwL,QAC5B,oBACAxL,EAAQoL,WAId,SAAStD,GAAqB0D,GAC5BT,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,kBACXE,WAAY,CAAC,iBAAkB,0BAEjCP,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,sBACXE,WAAY,CAAC,qBAAsB,8BAErCP,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,gBACXE,WAAY,CAAC,eAAgB,wBAE/BP,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,QACXF,UAAW,UAEbH,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,WACXF,UAAW,YAEbH,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,aACXF,UAAW,cAEbH,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,qBACXF,UAAW,sBAEbH,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,QACXF,UAAW,CAAC,aAAc,iBAE5BH,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,qBACXF,UAAW,qBAEbH,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,cACXF,UAAW,eAEbH,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,YACXF,UAAW,aAEbH,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,eACXF,UAAW,gBAEbH,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,mBACXE,WAAY,CACV,kBACA,wBACA,oBACA,mBACA,sBAGJP,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,uBACXE,WAAY,CACV,sBACA,4BACA,wBACA,uBACA,0BAGJP,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,iBACXE,WAAY,CACV,gBACA,sBACA,kBACA,iBACA,oBAGA,UAAY/M,GACdwM,GAAmB,CACjBS,OAAQA,EACRJ,UAAW,iBACXF,UAAW,WAKjB,SAASO,GAAcxF,EAAUyF,EAAiBC,EAAOC,GAWvD,OAVIF,IAAoBzF,IAChBA,KAAY0F,IAChBtM,GACE4G,EAAW,8BAAgC2F,EAAO,sBAEpD3F,EAAWyF,GAEbrI,GAAIuI,EAAO,+BAAiC3F,EAAW,MAGlDA,EAGT,SAASyB,KACPnK,EAAiBkO,GACflO,EACAD,EACA6D,EACA,UAIJ,SAASwG,KACPzI,EAAgBuM,GACdvM,EACAD,EACAkD,EACA,SAIJ,SAAS0F,MACH,IAASrL,GACXsL,GAAqB,OA8VzB,WACE,IAAI+D,EAAyBlO,EAAJ,EAIvBpB,OAAOuP,kBACPvP,OAAOwP,uBAEHF,EACFG,KAEAnP,EArGN,WACE,SAASoP,EAAqBC,GAC5B,SAASC,EAAqBC,IACxB,IAAUA,EAAQC,WACpBhJ,GAAI,uBAAyB+I,EAAQE,KACrCF,EAAQ/L,iBAAiB,OAAQkM,GAAa,GAC9CH,EAAQ/L,iBAAiB,QAASmM,GAAY,GAC9CC,EAASC,KAAKN,IAII,eAAlBF,EAASN,MAAoD,QAA3BM,EAASS,cAC7CR,EAAqBD,EAASzN,QACH,cAAlByN,EAASN,MAClBvB,MAAMxF,UAAU0B,QAAQ+D,KACtB4B,EAASzN,OAAO8L,iBAAiB,OACjC4B,GASN,SAASS,EAAwBR,GAC/B/I,GAAI,yBAA2B+I,EAAQE,KACvCF,EAAQ7L,oBAAoB,OAAQgM,GAAa,GACjDH,EAAQ7L,oBAAoB,QAASiM,GAAY,GAPnD,SAAyBJ,GACvBK,EAASI,OAAOJ,EAASrI,QAAQgI,GAAU,GAO3CU,CAAgBV,GAGlB,SAASW,EAAoBhK,EAAO6I,EAAMoB,GACxCJ,EAAwB7J,EAAMtE,QAC9B8E,GAASqI,EAAMoB,EAAW,KAAOjK,EAAMtE,OAAO6N,IAAKhQ,EAAWA,GAGhE,SAASiQ,EAAYxJ,GACnBgK,EAAoBhK,EAAO,YAAa,gBAG1C,SAASyJ,EAAWzJ,GAClBgK,EAAoBhK,EAAO,kBAAmB,qBAGhD,SAASkK,EAAiBC,GACxB3J,GACE,mBACA,qBAAuB2J,EAAU,GAAGzO,OAAS,IAAMyO,EAAU,GAAGtB,MAIlEsB,EAAU3G,QAAQ0F,GAsBpB,IAAIQ,EAAW,GACbX,EACEvP,OAAOuP,kBAAoBvP,OAAOwP,uBACpCoB,EAtBF,WACE,IAAI1O,EAASgB,SAAS2N,cAAc,QAepC,OALAD,EAAW,IAAIrB,EAAiBmB,GAEhC5J,GAAI,gCACJ8J,EAASE,QAAQ5O,EAZN,CACP6O,YAAY,EACZC,mBAAmB,EACnBC,eAAe,EACfC,uBAAuB,EACvBC,WAAW,EACXC,SAAS,IAQNR,EAMIS,GAEb,MAAO,CACL7F,WAAY,WACN,eAAgBoF,IAClB9J,GAAI,oCACJ8J,EAASpF,aACT0E,EAASlG,QAAQqG,MAiBJiB,IAGjBxK,GAAI,mDACJ2I,MA5WA8B,IAEAzK,GAAI,wBAsPR,SAAS2I,KACH,IAAMrO,IACR0F,GAAI,gBAAkB1F,EAAW,MACjCC,EAAgBmQ,YAAY,WAC1BxK,GAAS,WAAY,gBAAkB5F,IACtC+D,KAAKsM,IAAIrQ,KAqHhB,SAAS0D,GAAiB4M,EAAMlJ,GAC9B,IAAImJ,EAAS,EAMb,OALAnJ,EAAKA,GAAMtF,SAASG,KAGpBsO,EAAS,QADTA,EAASzO,SAAS0O,YAAY9M,iBAAiB0D,EAAI,OACxBmJ,EAAOD,GAAQ,EAEnCtE,SAASuE,EAAQzR,GAW1B,SAASsF,GAAcqM,EAAM3B,GAO3B,IANA,IAAI4B,EAAiB5B,EAASzO,OAC5BsQ,EAAQ,EACRC,EAAS,EACTC,EAAOvJ,GAAsBmJ,GAC7BK,EAAQ1N,IAED2N,EAAI,EAAGA,EAAIL,EAAgBK,IAItBH,GAHZD,EACE7B,EAASiC,GAAGrF,wBAAwB+E,GACpC/M,GAAiB,SAAWmN,EAAM/B,EAASiC,OAE3CH,EAASD,GAWb,OAPAG,EAAQ1N,IAAW0N,EAEnBpL,GAAI,UAAYgL,EAAiB,kBACjChL,GAAI,kCAAoCoL,EAAQ,MA3BlD,SAAyBA,GACX1P,EAAiB,EAAzB0P,GAEFpL,GAAI,gCADJtE,EAAiB,EAAI0P,GACiC,MA0BxDE,CAAgBF,GAETF,EAGT,SAAS3M,GAAmBgN,GAC1B,MAAO,CACLA,EAAUxN,aACVwN,EAAUvQ,aACVuQ,EAAUnN,wBACVmN,EAAUtQ,yBAId,SAAS4D,GAAkBkM,EAAMS,GAM/B,IAAIpC,EAAWhN,SAAS8K,iBAAiB,IAAMsE,EAAM,KAIrD,OAFI,IAAMpC,EAASzO,SANjBqB,GAAK,uBAAyBwP,EAAM,mBAC7BpP,SAAS8K,iBAAiB,WAO5BxI,GAAcqM,EAAM3B,GAG7B,SAASzK,KACP,OAAOvC,SAAS8K,iBAAiB,UAiGnC,SAAS/H,GACPsM,EACAC,EACA9F,EACAC,GAiDA,IAAI8F,EAAeC,EAvCjB,SAASC,EAAeC,EAAGC,GAEzB,QADa1N,KAAKsM,IAAImB,EAAIC,IAAMxQ,GAIlCoQ,EACE1S,IAAc2M,EAAeA,EAAe9H,EAAU5D,KACxD0R,EACE3S,IAAc4M,EAAcA,EAAc/G,EAASjD,KAGnDgQ,EAAe7R,EAAQ2R,IACtBjS,GAAkBmS,EAAelQ,EAAOiQ,IA6Bf,SAAWH,GACvCO,KA9CApH,GAHA5K,EAAS2R,EACThQ,EAAQiQ,EAEeH,IAqBdA,IAAgB,CAAE7L,KAAM,EAAGtF,SAAU,EAAGqL,KAAM,MAKrDzL,KAAkBW,GACjBnB,GAAkBmC,KAAiBhB,GAWzB4Q,IAAgB,CAAEnR,SAAU,IANzC0F,GAAI,8BAKFkF,GAAYwG,GAx7BN,SAARjM,KACEhC,EAAWC,IACXF,EAAU,KACVD,EAASH,EAAKkB,MAAMjB,EAASC,GACxBE,IAEHH,EAAUC,EAAO,MAo8BzB,SAAS4C,GAASuL,EAAcC,EAAkB9F,EAAcC,GAQrDrK,GAAiBiQ,KAAgB9R,EAgBxCqG,GAAI,4BAA8ByL,IAtB5BA,IAAgB,CAAE1L,MAAO,EAAGkM,UAAW,EAAGrM,KAAM,IACpDI,GAAI,kBAAoB0L,GAUL,SAAjBD,EACFtM,GAAWsM,EAAcC,EAAkB9F,EAAcC,GAEzD3G,EACEuM,EACAC,EACA9F,EACAC,IAQR,SAASmG,KACFxQ,IACHA,GAAgB,EAChBwE,GAAI,0BAENT,aAAa9D,GACbA,EAAqB+D,WAAW,WAC9BhE,GAAgB,EAChBwE,GAAI,0BACJA,GAAI,OACHlG,GAGL,SAASmG,GAAawL,GACpBzR,EAAS8D,EAAU5D,KACnByB,EAAQmD,EAASjD,KAEjB+I,GAAQ5K,EAAQ2B,EAAO8P,GAGzB,SAASvG,GAAYwG,GACnB,IAAIQ,EAAMhS,EACVA,EAAiBD,EAEjB+F,GAAI,wBAA0B0L,GAC9BM,KACA/L,GAAa,SAEb/F,EAAiBgS,EAGnB,SAAStH,GAAQ5K,EAAQ2B,EAAO8P,EAAcvJ,EAAKkB,GASjD,IAEIzC,GAYA,IAASxF,IArBPlC,IAAcmK,EAChBA,EAAe9H,EAEf0E,GAAI,yBAA2BoD,GAcjCpD,GAAI,kCARFW,EACE/F,EACA,KAHOZ,EAAS,IAAM2B,GAKtB,IACA8P,GACCxS,IAAciJ,EAAM,IAAMA,EAAM,KAEY,KACjD9G,EAAO+Q,YAAY1R,EAAQkG,EAASyC,IAoHxC,SAAS3B,KACH,YAAcrF,SAASgQ,YACzBlT,OAAOmC,OAAO8Q,YAAY,4BAA6B,MAnvC5D","file":"iframeResizer.contentWindow.min.js"} \ No newline at end of file diff --git a/public/iframeResizer.contentWindow.min.js b/public/iframeResizer.contentWindow.min.js deleted file mode 100644 index 5f94fc43d..000000000 --- a/public/iframeResizer.contentWindow.min.js +++ /dev/null @@ -1,10 +0,0 @@ -/*! iFrame Resizer (iframeSizer.contentWindow.min.js) - v4.2.11 - 2020-06-02 - * Desc: Include this file in any page being loaded into an iframe - * to force the iframe to resize to the content size. - * Requires: iframeResizer.min.js on host page. - * Copyright: (c) 2020 David J. Bradshaw - dave@bradshaw.net - * License: MIT - */ - -!function(d){if("undefined"!=typeof window){var n=!0,o=10,i="",r=0,a="",t=null,u="",c=!1,s={resize:1,click:1},l=128,f=!0,m=1,h="bodyOffset",g=h,p=!0,v="",y={},b=32,w=null,T=!1,E="[iFrameSizer]",O=E.length,S="",M={max:1,min:1,bodyScroll:1,documentElementScroll:1},I="child",N=!0,A=window.parent,C="*",z=0,k=!1,e=null,R=16,x=1,L="scroll",F=L,P=window,D=function(){re("onMessage function not defined")},j=function(){},q=function(){},H={height:function(){return re("Custom height calculation function not defined"),document.documentElement.offsetHeight},width:function(){return re("Custom width calculation function not defined"),document.body.scrollWidth}},W={},B=!1;try{var J=Object.create({},{passive:{get:function(){B=!0}}});window.addEventListener("test",ee,J),window.removeEventListener("test",ee,J)}catch(e){}var U,V,K,Q,X,Y,G=Date.now||function(){return(new Date).getTime()},Z={bodyOffset:function(){return document.body.offsetHeight+pe("marginTop")+pe("marginBottom")},offset:function(){return Z.bodyOffset()},bodyScroll:function(){return document.body.scrollHeight},custom:function(){return H.height()},documentElementOffset:function(){return document.documentElement.offsetHeight},documentElementScroll:function(){return document.documentElement.scrollHeight},max:function(){return Math.max.apply(null,ye(Z))},min:function(){return Math.min.apply(null,ye(Z))},grow:function(){return Z.max()},lowestElement:function(){return Math.max(Z.bodyOffset()||Z.documentElementOffset(),ve("bottom",we()))},taggedElement:function(){return be("bottom","data-iframe-height")}},$={bodyScroll:function(){return document.body.scrollWidth},bodyOffset:function(){return document.body.offsetWidth},custom:function(){return H.width()},documentElementScroll:function(){return document.documentElement.scrollWidth},documentElementOffset:function(){return document.documentElement.offsetWidth},scroll:function(){return Math.max($.bodyScroll(),$.documentElementScroll())},max:function(){return Math.max.apply(null,ye($))},min:function(){return Math.min.apply(null,ye($))},rightMostElement:function(){return ve("right",we())},taggedElement:function(){return be("right","data-iframe-width")}},_=(U=Te,X=null,Y=0,function(){var e=G(),t=R-(e-(Y=Y||e));return V=this,K=arguments,t<=0||RM[c]["max"+e])throw new Error("Value for min"+e+" can not be greater than max"+e)}c in M&&"iFrameResizer"in i?E(c,"Ignored iFrame, already setup."):(d=(d=e)||{},M[c]={firstRun:!0,iframe:i,remoteHost:i.src&&i.src.split("/").slice(0,3).join("/")},function(e){if("object"!=typeof e)throw new TypeError("Options is not an object")}(d),Object.keys(d).forEach(n,d),function(e){for(var n in w)Object.prototype.hasOwnProperty.call(w,n)&&(M[c][n]=Object.prototype.hasOwnProperty.call(e,n)?e[n]:w[n])}(d),M[c]&&(M[c].targetOrigin=!0===M[c].checkOrigin?function(e){return""===e||null!==e.match(/^(about:blank|javascript:|file:\/\/)/)?"*":e}(M[c].remoteHost):"*"),function(){switch(R(c,"IFrame scrolling "+(M[c]&&M[c].scrolling?"enabled":"disabled")+" for "+c),i.style.overflow=!1===(M[c]&&M[c].scrolling)?"hidden":"auto",M[c]&&M[c].scrolling){case"omit":break;case!0:i.scrolling="yes";break;case!1:i.scrolling="no";break;default:i.scrolling=M[c]?M[c].scrolling:"no"}}(),f("Height"),f("Width"),u("maxHeight"),u("minHeight"),u("maxWidth"),u("minWidth"),"number"!=typeof(M[c]&&M[c].bodyMargin)&&"0"!==(M[c]&&M[c].bodyMargin)||(M[c].bodyMarginV1=M[c].bodyMargin,M[c].bodyMargin=M[c].bodyMargin+"px"),r=L(c),(s=p())&&(a=s,i.parentNode&&new a(function(e){e.forEach(function(e){Array.prototype.slice.call(e.removedNodes).forEach(function(e){e===i&&C(i)})})}).observe(i.parentNode,{childList:!0})),z(i,"load",function(){B("iFrame.onload",r,i,l,!0),function(){var e=M[c]&&M[c].firstRun,n=M[c]&&M[c].heightCalculationMethod in h;!e&&n&&j({iframe:i,height:0,width:0,type:"init"})}()}),B("init",r,i,l,!0),M[c]&&(M[c].iframe.iFrameResizer={close:C.bind(null,M[c].iframe),removeListeners:b.bind(null,M[c].iframe),resize:B.bind(null,"Window resize","resize",M[c].iframe),moveToAnchor:function(e){B("Move to anchor","moveToAnchor:"+e,M[c].iframe,c)},sendMessage:function(e){B("Send Message","message:"+(e=JSON.stringify(e)),M[c].iframe,c)}}))}function c(e,n){null===i&&(i=setTimeout(function(){i=null,e()},n))}function u(){"hidden"!==document.visibilityState&&(R("document","Trigger event: Visiblity change"),c(function(){f("Tab Visable","resize")},16))}function f(n,i){Object.keys(M).forEach(function(e){!function(e){return M[e]&&"parent"===M[e].resizeFrom&&M[e].autoResize&&!M[e].firstRun}(e)||B(n,i,M[e].iframe,e)})}function y(){z(window,"message",n),z(window,"resize",function(){!function(e){R("window","Trigger event: "+e),c(function(){f("Window "+e,"resize")},16)}("resize")}),z(document,"visibilitychange",u),z(document,"-webkit-visibilitychange",u)}function q(){function i(e,n){n&&(function(){if(!n.tagName)throw new TypeError("Object is not a valid DOM element");if("IFRAME"!==n.tagName.toUpperCase())throw new TypeError("Expected