mod_survey

Adds survey resources: user-defined forms that can be created in the admin interface and filled in by site visitors. The module renders multi-page forms, validates and stores answers, supports quizzes, and reports or exports the collected results.

Survey question types

TypeDescription
likertA five-point scale from completely disagree (1) to completely agree (5).
short answerA single-line text field with optional validation such as email, date, or numeric.
long answerA multi-line text field.
matchingMatch each item to one of the supplied options.
thurstoneTranslatable single or multiple choice, optionally submitting immediately after a choice.
multiple choiceSimple choices. Numeric choice values can also be totaled in printable and chart results. Use thurstone for translatable choices.
true or falseA boolean question with configurable option text.
yes or noA boolean question with configurable option text.
narrativeInline questions embedded in narrative text.
categorySelect one resource from a category.
countrySelect a country.
hiddenA hidden value, for example to record that a page was submitted.
uploadA file upload for the final page. The upload question has no stored result; a custom survey handler must process the file.
headerA subheading between questions.
promptAn additional prompt block.
text blockExplanatory text between questions.

Tests and quizzes

Thurstone and matching questions can be marked as test questions with is_test. This selects their test rendering and makes the configured correct answers available to the feedback templates. is_test_direct shows feedback while filling in the question.

Scoring is currently implemented for thurstone questions. Each choice can have an is_correct flag and points_int; the default is one point. A single-choice question awards the selected correct answer. A multiple-choice question can award correct selections and correctly omitted wrong selections. With is_test_neg, wrong selections and omitted correct answers subtract their configured points. The score for an individual question never goes below zero. Matching test questions support direct correctness feedback but currently add no points to the stored total.

survey_test_results stores the calculated total in the answer's points field and annotates the stored answers with their points. Set the survey property survey_test_percentage to the percentage needed to pass. The survey_show_results setting controls whether the final page shows only the thank-you text, the respondent's result, the respondent's result only after a pass, or aggregated results.

Templates can use survey_test_max_points to retrieve the maximum score and the survey_any_correct_answer and survey_any_wrong_answer filters when rendering answer feedback.

Editing submitted answers

The survey_multiple resource property controls repeat submissions and edits:

  • 0 means one final submission; it cannot be edited by the respondent.
  • 1 allows multiple submissions, each stored as a new answer.
  • 2 allows one submission and lets a logged-in respondent edit their own stored answer.

People who can edit the survey resource can edit any of its saved answers by answer id. A participant can update only their own answer, and only in mode 2. Anonymous respondents cannot edit a final submission in this mode. These checks are made server-side with the update_result ACL action; hiding an edit button is not the access control.

m_survey:replace_survey_submission/4 recalculates the test score and updates the answer's modified and modifier_id fields. The submitted date is set when a new answer is stored. On an edit it is refreshed only when the current user owns the saved answer; an editor changing somebody else's answer leaves the original submitted date intact.

Saving intermediate progress is separate from editing a submitted answer. Also note that edit-after-submit and submission-count limits depend on the selected survey handler storing results in survey_answers.

Editor-only questions

Set is_editor_only on a question block when only people with edit permission on the survey should supply that answer. Editors see the normal input and can change it. A non-editor sees the question in read-only form, including its prompt and explanation, and also sees the stored answer when one is present.

This restriction is enforced in both the form flow and the storage layer. Submitted values for editor-only questions are discarded for non-editors, including the expanded input names used by narrative and matching questions. When a non-editor updates an answer, any existing editor-only values are copied from the stored answer instead of being overwritten. An editor-only question marked required is not considered missing for a non-editor. When an editor uses Save & Email, editor-only answers are included in the respondent email even if the survey's normal confirmation settings omit open or all answers.

Answer status

Every saved answer has optional workflow metadata, separate from the answer audit fields:

  • status - a non-negative numeric status index, or undefined for no status. The bundled interface presents status values 0 through 5 as color swatches.
  • status_date - UTC date and time of the last status change.
  • status_note - an optional internal note. It is trimmed and limited to 65,536 Unicode characters; longer input is truncated safely.
  • status_modifier_id - the user who last changed the status.

Use m_survey:set_answer_status/5 to replace all four status fields together. The answer id must belong to the supplied survey, and the current user must be allowed to edit that survey. Changing status does not update modified or modifier_id, because those fields describe changes to the submitted answers.

Status is internal editor information. m_survey:single_result/3 and single_result/4 remove all status fields when the current user cannot edit the survey. The read-only _survey_answer_status.tpl and the editable _survey_answer_status_edit.tpl apply the same editor check. The latter is included by _dialog_survey_answer_status.tpl, which handles saving through the survey_answer_status submit event.

_dialog_survey_result_view.tpl shows an entire saved result without paging. For editors it combines the status fields with editable editor-only questions; all respondent answers are read-only and unanswered respondent questions are omitted. Its survey_result_view_save event accepts only status and editor-only values. Both this view and the final page of the full paged editor offer Save & Email; the extra mail is sent only after the same server-side answer-edit permission check as a normal save. Post-submit content such as the reserved survey_feedback block, and resultless survey flow-control blocks, are omitted from the saved-answer view.

Both status templates accept an optional status_labels list for values 0 through 5. Without labels they show only the color swatches. For example:

{% include "_survey_answer_status.tpl"
    id=id
    answer_id=answer_id
    status_labels=[_"New", _"Accepted", _"Rejected"]
%}

Intercepting survey submissions

When a survey is submitted, the module sends a first #survey_submit{} notification with these fields:

  • id - id of the submitted survey.
  • handler - selected handler name, described below.
  • answers - normalized answers.
  • missing - names of required answers that were missing.
  • answers_raw - unprocessed submitted values.

An observer can handle a submission by returning ok:

observe_survey_submit(#survey_submit{id = SurveyId}, Context) ->
    ?DEBUG(SurveyId),
    ok.

Creating a custom survey handler

The survey edit page has a dropdown of survey handlers. A handler is a survey resource property that selects how a submission is processed. Handlers are collected with the #survey_get_handlers{} fold notification:

observe_survey_get_handlers(#survey_get_handlers{}, All, Context) ->
    [
        {<<"email_me">>,
            ?__(<<"E-mail me when the survey is submitted">>, Context)}
        | All
    ].

The selected value is passed in the handler field of #survey_submit{}. An observer should match its handler and return undefined for the others:

observe_survey_submit(
        #survey_submit{handler = <<"email_me">>, id = SurveyId},
        Context) ->
    %% Handle and, if desired, store this submission.
    ok;
observe_survey_submit(#survey_submit{}, _Context) ->
    undefined.

Configuration keys

The result editor can link an answer to a newly created person. Its category and content group are configured with:

  • mod_survey.person_category, default person.
  • mod_survey.person_content_group, default default_content_group. If empty, the active ACL module selects the default content group.

Accepted events

This module handles the following notifier callbacks:

  • observe_acl_is_allowed/2 checks view, update, delete, and MQTT access for survey answers.
  • observe_admin_edit_blocks/3 adds the survey question blocks to the editor.
  • observe_admin_rscform/3 normalizes page jumps into page-break blocks.
  • observe_export_resource_filename/2, observe_export_resource_header/2, and observe_export_resource_data/2 provide the survey results download.
  • observe_rsc_merge/2 moves answers from a merged resource to the winner.
  • observe_survey_get_handlers/3 adds built-in survey handlers.
  • observe_survey_is_submit/2 checks whether a block submits the survey.
  • observe_survey_submit/2 processes submissions for built-in handlers.
  • observe_tick_24h/2 removes expired intermediate survey results.

Delegate callbacks handled by event/2 include the survey_start, survey_back, survey_remove_result, and survey_remove_result_confirm postbacks, and the survey_next, survey_answer_status, and survey_result_view_save submit events.

Edit on GitHub

Observes

Notifications

observe_acl_is_allowed/2

Check if a user is authorized to perform an operation on a an object (some resource or module). Observe this notification to do complex or more fine-grained…

Notifications

observe_export_resource_data/2

mod_export - fetch a row for the export, can return a list of rows, a binary, and optionally a continuation state. Where Values is [ term() ], i.e. a list of…

Notifications

observe_rsc_merge/2

Map to signal merging two resources. Move any information from the loser to the winner. The loser will be deleted.

Models

Models

survey

Model for survey reporting and participant state, including results, totals, per-user answers, exports, and result handlers.

Models

survey_saved

Model for saving and retrieving intermediate survey answers for users. There can only be a single intermediate result per user/survey or, for anonymous users…

Dispatch rules

Filters

Filters

survey_any_correct_answer

Determine if any answer of a question was correct. Used for showing results of thurstone test questions.

Filters

survey_any_wrong_answer

Determine if any answer of a question was wrong. Used for showing results of thurstone test questions.

Filters

survey_as_pages

Split the page blocks into pages, prepare them for easy display in the survey question editor.

Filters

survey_is_history_back

Test whether a survey respondent may return to the previous page in a history.

Filters

survey_is_save_intermediate

Check if the given survey is configured to save intermediate results. Always returns a boolean value.

Filters

survey_is_stop

Check if there is a ‘stop’ question in list of (survey) blocks

Filters

survey_is_submit

Check if the questions end with a question type that is a submit button.

Filters

survey_test_max_points

Counts the total of all points that can be received for all test questions. Non test questions are not counted.

Scomp

Scomp

poll

Show a given survey (with the id parameter) as a “poll”. This presents a simpler interface, in which the user is directly asked to enter some information, e.g.

Scomp

survey_start

Show the first page for a given survey (with the id parameter):

Referred by

Filters

survey_as_pages

Split the page blocks into pages, prepare them for easy display in the survey question editor.

Filters

survey_is_stop

Check if there is a ‘stop’ question in list of (survey) blocks

Filters

survey_test_max_points

Counts the total of all points that can be received for all test questions. Non test questions are not counted.