diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..d8120359
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,121 @@
+name: Moodle plugin CI
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ubuntu-22.04
+
+ services:
+ postgres:
+ image: postgres:13
+ env:
+ POSTGRES_USER: 'postgres'
+ POSTGRES_HOST_AUTH_METHOD: 'trust'
+ ports:
+ - 5432:5432
+ options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3
+
+ mariadb:
+ image: mariadb:10
+ env:
+ MYSQL_USER: 'root'
+ MYSQL_ALLOW_EMPTY_PASSWORD: "true"
+ MYSQL_CHARACTER_SET_SERVER: "utf8mb4"
+ MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci"
+ ports:
+ - 3306:3306
+ options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3
+
+ strategy:
+ fail-fast: false
+ matrix:
+# include:
+# - php: '8.3'
+# moodle-branch: 'MOODLE_404_STABLE'
+# database: 'pgsql'
+# - php: '8.2'
+# moodle-branch: 'MOODLE_403_STABLE'
+# database: 'mariadb'
+# - php: '8.1'
+# moodle-branch: 'MOODLE_403_STABLE'
+# database: 'pgsql'
+# - php: '8.0'
+# moodle-branch: 'MOODLE_402_STABLE'
+# database: 'mariadb'
+# - php: '7.4'
+# moodle-branch: 'MOODLE_401_STABLE'
+# database: 'pgsql'
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ path: plugin
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: ${{ matrix.extensions }}
+ ini-values: max_input_vars=5000
+ # none to use phpdbg fallback. Specify pcov (Moodle 3.10 and up) or xdebug to use them instead.
+ coverage: none
+
+ - name: Deploy moodle-plugin-ci
+ run: |
+ composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^4
+ echo $(cd ci/bin; pwd) >> $GITHUB_PATH
+ echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH
+ sudo locale-gen en_AU.UTF-8
+ # Install nvm.
+ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
+
+ - name: Install Moodle
+ # Need explicit IP to stop mysql client fail on attempt to use unix socket.
+ run: moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1
+ env:
+ DB: ${{ matrix.database }}
+ MOODLE_BRANCH: ${{ matrix.moodle-branch }}
+ IGNORE_PATHS: 'templates/local/mobile'
+ PHPDOCCHECKER_IGNORE_PATHS: /^vendor/
+
+ - name: PHP Lint
+ if: ${{ always() }}
+ run: moodle-plugin-ci phplint
+
+ - name: PHP Mess Detector
+ continue-on-error: true # This step will show errors but will not fail
+ if: ${{ always() }}
+ run: moodle-plugin-ci phpmd
+
+ - name: Moodle Code Checker
+ if: ${{ always() }}
+ run: moodle-plugin-ci codechecker || true
+
+ - name: Moodle PHPDoc Checker
+ if: ${{ always() }}
+ run: moodle-plugin-ci phpdoc || true
+
+ - name: Validating
+ if: ${{ always() }}
+ run: moodle-plugin-ci validate
+
+ - name: Check upgrade savepoints
+ if: ${{ always() }}
+ run: moodle-plugin-ci savepoints
+
+ - name: Mustache Lint
+ if: ${{ always() }}
+ run: moodle-plugin-ci mustache
+
+ - name: Grunt
+ if: ${{ always() }}
+ run: moodle-plugin-ci grunt
+
+ - name: PHPUnit tests
+ if: ${{ always() }}
+ run: moodle-plugin-ci phpunit
+
+ - name: Behat features
+ if: ${{ always() }}
+ run: moodle-plugin-ci behat --profile chrome
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 371390b7..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,93 +0,0 @@
-language: php
-
-sudo: required
-
-addons:
- firefox: "47.0.1"
- postgresql: "9.4"
- mysql: "8.0.2"
- apt:
- packages:
- - oracle-java9-installer
- - oracle-java9-set-default
-
-cache:
- directories:
- - $HOME/.composer/cache
- - $HOME/.npm
-
-php:
- - 5.6
- - 7.1
- - 7.2
-
-env:
- - MOODLE_BRANCH=MOODLE_36_STABLE DB=pgsql
- - MOODLE_BRANCH=MOODLE_36_STABLE DB=mysqli
- - MOODLE_BRANCH=MOODLE_35_STABLE DB=pgsql
- - MOODLE_BRANCH=MOODLE_35_STABLE DB=mysqli
- - MOODLE_BRANCH=MOODLE_34_STABLE DB=pgsql
- - MOODLE_BRANCH=MOODLE_34_STABLE DB=mysqli
- - MOODLE_BRANCH=MOODLE_33_STABLE DB=pgsql
- - MOODLE_BRANCH=MOODLE_33_STABLE DB=mysqli
-
-matrix:
- exclude:
- - php: 7.1
- env: MOODLE_BRANCH=MOODLE_33_STABLE DB=mysqli
- - php: 7.1
- env: MOODLE_BRANCH=MOODLE_33_STABLE DB=pgsql
- - php: 7.2
- env: MOODLE_BRANCH=MOODLE_33_STABLE DB=mysqli
- - php: 7.2
- env: MOODLE_BRANCH=MOODLE_33_STABLE DB=pgsql
- - php: 5.6
- env: MOODLE_BRANCH=MOODLE_34_STABLE DB=mysqli
- - php: 5.6
- env: MOODLE_BRANCH=MOODLE_34_STABLE DB=pgsql
- - php: 7.2
- env: MOODLE_BRANCH=MOODLE_34_STABLE DB=mysqli
- - php: 7.2
- env: MOODLE_BRANCH=MOODLE_34_STABLE DB=pgsql
- - php: 5.6
- env: MOODLE_BRANCH=MOODLE_35_STABLE DB=mysqli
- - php: 5.6
- env: MOODLE_BRANCH=MOODLE_35_STABLE DB=pgsql
- - php: 7.2
- env: MOODLE_BRANCH=MOODLE_35_STABLE DB=mysqli
- - php: 7.2
- env: MOODLE_BRANCH=MOODLE_35_STABLE DB=pgsql
- - php: 5.6
- env: MOODLE_BRANCH=MOODLE_36_STABLE DB=mysqli
- - php: 5.6
- env: MOODLE_BRANCH=MOODLE_36_STABLE DB=pgsql
- - php: 7.1
- env: MOODLE_BRANCH=MOODLE_36_STABLE DB=mysqli
- - php: 7.1
- env: MOODLE_BRANCH=MOODLE_36_STABLE DB=pgsql
-
-before_install:
- - phpenv config-rm xdebug.ini
- - nvm install node
- - nvm install 8.9
- - nvm use 8.9
- - cd ../..
- - composer selfupdate
- - composer create-project -n --no-dev --prefer-dist moodlerooms/moodle-plugin-ci ci ^2
- - export PATH="$(cd ci/bin; pwd):$(cd ci/vendor/bin; pwd):$PATH"
-
-install:
- - moodle-plugin-ci install
-
-script:
- - moodle-plugin-ci phplint
- - moodle-plugin-ci phpcpd
- - moodle-plugin-ci phpmd
- - moodle-plugin-ci codechecker
- - moodle-plugin-ci validate
- - moodle-plugin-ci savepoints
- - moodle-plugin-ci mustache
- - moodle-plugin-ci grunt
- - moodle-plugin-ci phpunit
- - moodle-plugin-ci behat
- - moodle-plugin-ci phpunit --coverage-text
\ No newline at end of file
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 00000000..493b4a37
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,38 @@
+Release Notes
+
+Release 4.1.0 (Build - 2023081100)
+
+Initial release for Moodle 4.1 forward.
+
+Release 4.1.1 (Build - 2024082900)
+
+Improvements:
+* Compatible with Moodle 4.3 and 4.4.
+* Compatible with PHP8.2.
+* PR449 - Allow localized answer options to be displayed correctly in conditions.
+* PR506 - Accessibility: improved accessibility for essay box type.
+* PR495 - Accessibility: The slider values should be associated with the labels and the slider should be programmatically associated with the question
+* PR497 - Accessibility: Rate table does not have a programmatically associated caption.
+* PR496 - Accessibility: Numeric instructions not programmatically associated with field.
+* PR505 - Accessibility: Rate form controls within the table are not accessible.
+* PR501 - Accessibility: Check boxes missing group label.
+* PR511 - Accessibility: Radio buttons & Yes/No missing group labels.
+* PR517 - Mobile: Update sectiontext questions to display on mobile.
+* PR520 - Add Slider question type compatibility with Feedback features.
+* PR526 - Have additional info text pass through filters everywhere fixes.
+* PR534 - Fix namespace issues with externallib.php file.
+* PR536 - Support for user identity fields in Download Responses.
+* PR577 - Improved headings in report page.
+* PR581 - Mobile: Adapt mobile code to ionic 7.
+* PR569 - Adopt icon size to 24×24 with a smaller content as other icons.
+* PR586, PR579, PR594 - Various deprecations fixed.
+* PR593 - Ensure "pdf" extension force.
+
+Bug Fixes:
+* PR508 - General PHP fixes.
+* PR523 - Behat activity completion fix.
+* PR514 - Section text qtype should not support feedback.
+* PR516 - Course description displays properly.
+
+(see CHANGES.md in release 4.00 for earlier changes.)
+
diff --git a/CHANGES.txt b/CHANGES.txt
deleted file mode 100644
index 41b4b81f..00000000
--- a/CHANGES.txt
+++ /dev/null
@@ -1,80 +0,0 @@
-Release Notes
-
-NOTE - This release will work on Moodle 3.6 and can be backported to Moodle 3.4 and 3.3. If doing so, please ensure that your
-questionnaire installation is at the latest version for those releases. For Moodle 3.4, this is questionnaire release 3.4.2.
-For Moodle 3.3, this is questionnaire release 3.3.3.
-
-Version 3.5.3 (Build - 2018121000)
-Bug Fixes:
-GHI162 - Fixed response by group.
-
-CONTRIB-7555 - Fixed error on recent activity block.
-
-CONTRIB-7557 - Allowed compatibility with older releases of Moodle privacy API.
-
-
-Version 3.5.2 (Build - 2018120100)
-New features:
-Code has been updated to work on Moodle 3.6
-
-The two new privacy api functions have been added (get_users_in_context, delete_data_for_users).
-
-Bug Fixes:
-The privacy api polyfill functions have been fixed so that they work correctly with PHP 5.6 under Moodle 3.3.
-(Thanks to Paul Holden - https://github.com/paulholden - https://github.com/PoetOS/moodle-mod_questionnaire/pull/166)
-
-GHI167 - Fixed the feedback scoring for boolean questions.
-
-
-Version 3.5.1 (Build - 2018110100)
-NOTE - This release can be backported to Moodle 3.4 and 3.3. If doing so, please ensure that your questionnaire installation is at
-the latest version for those releases. For Moodle 3.4, this is questionnaire release 3.4.2. For Moodle 3.3, this is questionnaire
-release 3.3.3.
-
-New features:
-The feedback options UI has been completely overhauled. The feedback UI is now accessed from the module settings menu and tabs.
-Each section can now be easily accessed and managed on one page. Questions are now assigned to sections rather than sections
-assigned to questions.
-
-Rate question usability improvements, specifically a mouse click on table cell selects the radio button as well.
-
-Report now uses number of of participants instead of number of responses for averages on questions that have optional responses
-(e.g. checkboxes).
-
-Numeric question responses now allow comma decimal separator as well as period.
-
-Data export now uses the more of the Moodle export API, and exports with 'CSV' extension rather than 'TXT'.
-
-Incomplete responses can now be selected for export with the CSV export function.
-
-In exported data, anonymous responses are assigned unique identifiers, so that responses from the same anonymous submitter can be
-grouped.
-
-Thanks to C. Jobst & Y. Annanias of the University of Leipzig and the Online-Self-Assessment project for the Faculty of Economics
-and Management Science, funded by the EU/ESF (The European Social Fund in Germany) for the resources and efforts for these features.
-
-
-Bug Fixes:
-CONTRIB-7420 - Renaming 'rank' data field to 'rankvalue' to deal with 'rank' now being a MySQL reserved word.
-
-GHI154 - Fixing delete question error caused by code improvement regression.
-
-GHI151 - Fixing download of all public questionnaire instance results, caused by attempt table merge regression.
-
-
-Code improvements:
-Merged attempts and response tables into one table.
-New functions added to remove repeated code snippets and DB calls.
-Numerous response templates added.
-Feedback and feedback section classes added.
-
-Version 3.5.0 (Build - 2018061900)
-
-New features:
-This is an early release providing the GDPR Privacy API implementation.
-
-Bug fixes:
-CONTRIB-7187 - Fixed preview mode with dependencies bug and added tests to verify.
-CONTRIB-7300 - Removed database columns from install.xml and readded the upgrade step for them.
-
-(see CHANGES.TXT in release 3.4 for earlier changes.)
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 00000000..20d40b6b
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
\ No newline at end of file
diff --git a/README.txt b/README.txt
index 8b6d78eb..5a18c293 100644
--- a/README.txt
+++ b/README.txt
@@ -2,6 +2,14 @@ The questionnaire module allows you to construct questionnaires (surveys) from a
variety of question type. It was originally based on phpESP, and Open Source
survey tool.
+--------------------------------------------------------------------------------
+Developers Note:
+
+There is no main branch. Questionnaire is maintained in MOODLE_XX_STABLE
+branches. Use the latest STABLE branch for development or installation.
+The current stable branch is MOODLE_400_STABLE, and supports Moodle 4.0 and up.
+Use the MOODLE_311_STABLE branch for Moodle 3.9 through 3.11.
+
--------------------------------------------------------------------------------
To Install:
@@ -13,29 +21,6 @@ To Upgrade:
1. Copy all of the files into your 'mod/questionnaire' directory.
2. Visit your admin page. The database will be updated.
-3. As part of the update, all existing surveys are assigned as either 'private',
- 'public' or 'temmplate'. Surveys assigned to a single questionnaire are set
- to 'private' with the questionnaire's course as the owner. Surveys assigned
- to multiple questionnaires in the same course are set to 'public' with the
- questionnaire's course as the owner. Surveys assigned to multiple
- questionnaires in multiple courses are set to 'public' with the site ID as
- the owner. Surveys that are not deleted but have no associated questionnaires
- are set to 'template' with the site ID as the owner.
-
-*** IMPORTANT ***
-
-IF YOU ARE UPGRADING TO MOODLE 2.3...
-
-Make sure that you upgrade the questionnaire module to the latest 2.2 version in
-a Moodle 2.2 install first.
--------------------------------------------------------------------------------
-Version 2.4.1 - Release date 20130519
-
-In accordance with current Moodle languages policy, all language folders other than English have been
-removed from the lang folder. All translations are now available from AMOS.
-
---------------------------------------------------------------------------------
-Please read the releasenotes.txt file for more info about successive changes
---------------------------------------------------------------------------------
-
+Please read the CHANGES.md file for more info about successive changes
diff --git a/appjs/uncheckother.js b/appjs/uncheckother.js
new file mode 100644
index 00000000..c76a4df6
--- /dev/null
+++ b/appjs/uncheckother.js
@@ -0,0 +1,4 @@
+(function(t) {
+ t.toggleRatebox = function($event, $fieldkey) {
+ }
+})(this);
\ No newline at end of file
diff --git a/backup/moodle1/lib.php b/backup/moodle1/lib.php
index c84ce0c8..bd8d7d9b 100644
--- a/backup/moodle1/lib.php
+++ b/backup/moodle1/lib.php
@@ -17,14 +17,11 @@
/**
* Provides support for the conversion of moodle1 backup to the moodle2 format
*
- * @package mod
- * @subpackage questionnaire
+ * @package mod_questionnaire
* @copyright 2011 Robin de Vries
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-defined('MOODLE_INTERNAL') || die();
-
/**
* Choice conversion handler
*/
@@ -33,7 +30,7 @@ class moodle1_mod_questionnaire_handler extends moodle1_mod_handler {
/**
* Declare the paths in moodle.xml we are able to convert
*
- * The method returns list of {@link convert_path} instances. For each path returned,
+ * The method returns list of convert_path instances. For each path returned,
* at least one of on_xxx_start(), process_xxx() and on_xxx_end() methods must be
* defined. The method process_xxx() is not executed if the associated path element is
* empty (i.e. it contains none elements or sub-paths only).
@@ -42,7 +39,7 @@ class moodle1_mod_questionnaire_handler extends moodle1_mod_handler {
* actually exist in the file. The last element with the module name was
* appended by the moodle1_converter class.
*
- * @return array of {@link convert_path} instances
+ * @return array of convert_path instances
*/
public function get_paths() {
return array(
@@ -62,16 +59,18 @@ public function get_paths() {
new convert_path('question_choice', '/MOODLE_BACKUP/COURSE/MODULES/MOD/QUESTIONNAIRE/SURVEY/QUESTION/QUESTION_CHOICE'),
);
}
+
/**
* This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/QUESTIONNAIRE
- * data available
+ * data available.
+ * @param array $data
*/
public function process_questionnaire($data) {
// Get the course module id and context id.
$instanceid = $data['id'];
- $cminfo = $this->get_cminfo($instanceid);
- $moduleid = $cminfo['id'];
- $contextid = $this->converter->get_contextid(CONTEXT_MODULE, $moduleid);
+ $cminfo = $this->get_cminfo($instanceid);
+ $moduleid = $cminfo['id'];
+ $contextid = $this->converter->get_contextid(CONTEXT_MODULE, $moduleid);
// We now have all information needed to start writing into the file.
$this->open_xml_writer("activities/questionnaire_{$moduleid}/questionnaire.xml");
@@ -88,7 +87,6 @@ public function process_questionnaire($data) {
/**
* This is executed when we reach the closing tag of our 'questionnaire' path
*/
-
public function on_questionnaire_end() {
// Close questionnaire.xml.
$this->xmlwriter->end_tag('surveys');
@@ -96,9 +94,11 @@ public function on_questionnaire_end() {
$this->xmlwriter->end_tag('activity');
$this->close_xml_writer();
}
+
/**
* This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/QUESTIONNAIRE/SURVEY
* data available
+ * @param array $data
*/
public function process_survey($data) {
$this->xmlwriter->begin_tag('survey', array('id' => $data['id']));
@@ -120,6 +120,7 @@ public function on_survey_end() {
/**
* This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/QUESTIONNAIRE/SURVEY/QUESTION
* data available
+ * @param array $data
*/
public function process_question($data) {
@@ -144,9 +145,9 @@ public function on_question_end() {
/**
* This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/QUESTIONNAIRE/SURVEY/QUESTION/QUESTION_CHOICE
* data available
+ * @param array $data
*/
public function process_question_choice($data) {
$this->write_xml('quest_choice', $data, array('/question_choice/id'));
}
-
-}
\ No newline at end of file
+}
diff --git a/backup/moodle2/backup_questionnaire_activity_task.class.php b/backup/moodle2/backup_questionnaire_activity_task.class.php
index f1473e21..808672e8 100644
--- a/backup/moodle2/backup_questionnaire_activity_task.class.php
+++ b/backup/moodle2/backup_questionnaire_activity_task.class.php
@@ -14,13 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * @package mod_questionnaire
- * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
- * @author Mike Churchward
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
defined('MOODLE_INTERNAL') || die();
// Because it exists (must).
@@ -29,8 +22,11 @@
require_once($CFG->dirroot . '/mod/questionnaire/backup/moodle2/backup_questionnaire_settingslib.php');
/**
- * questionnaire backup task that provides all the settings and steps to perform one
- * complete backup of the activity
+ * Questionnaire backup task that provides all the settings and steps to perform one complete backup of the activity.
+ * @package mod_questionnaire
+ * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_questionnaire_activity_task extends backup_activity_task {
@@ -52,8 +48,10 @@ protected function define_my_steps() {
/**
* Code the transformations to perform in the activity in
* order to get transportable (encoded) links
+ * @param string $content
+ * @return array|string|string[]|null
*/
- static public function encode_content_links($content) {
+ public static function encode_content_links($content) {
global $CFG;
$base = preg_quote($CFG->wwwroot, "/");
diff --git a/backup/moodle2/backup_questionnaire_settingslib.php b/backup/moodle2/backup_questionnaire_settingslib.php
index 8c7fd054..c8c163e0 100644
--- a/backup/moodle2/backup_questionnaire_settingslib.php
+++ b/backup/moodle2/backup_questionnaire_settingslib.php
@@ -15,14 +15,13 @@
// along with Moodle. If not, see .
/**
+ * The required settingslib file.
* @package mod_questionnaire
* @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-defined('MOODLE_INTERNAL') || die();
-
// This activity has no particular settings but the inherited from the generic
// backup_activity_task so here there isn't any class definition, like the ones
// existing in /backup/moodle2/backup_settingslib.php (activities section).
diff --git a/backup/moodle2/backup_questionnaire_stepslib.php b/backup/moodle2/backup_questionnaire_stepslib.php
index 85a1890b..747b304b 100644
--- a/backup/moodle2/backup_questionnaire_stepslib.php
+++ b/backup/moodle2/backup_questionnaire_stepslib.php
@@ -15,23 +15,21 @@
// along with Moodle. If not, see .
/**
+ * Define all the backup steps that will be used by the backup_questionnaire_activity_task.
+ *
+ * Define the complete choice structure for backup, with file and id annotations.
+ *
* @package mod_questionnaire
* @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Define all the backup steps that will be used by the backup_questionnaire_activity_task
- */
-
-/**
- * Define the complete choice structure for backup, with file and id annotations
- */
class backup_questionnaire_activity_structure_step extends backup_activity_structure_step {
+ /**
+ * Defines the backup structure.
+ * @return backup_nested_element
+ */
protected function define_structure() {
global $DB;
// To know if we are including userinfo.
@@ -53,7 +51,7 @@ protected function define_structure() {
$questions = new backup_nested_element('questions');
$question = new backup_nested_element('question', array('id'), array('surveyid', 'name', 'type_id', 'result_id',
- 'length', 'precise', 'position', 'content', 'required', 'deleted'));
+ 'length', 'precise', 'position', 'content', 'required', 'deleted', 'extradata'));
$questchoices = new backup_nested_element('quest_choices');
@@ -172,7 +170,7 @@ protected function define_structure() {
$question->set_source_table('questionnaire_question', array('surveyid' => backup::VAR_PARENTID));
$fbsection->set_source_table('questionnaire_fb_sections', array('surveyid' => backup::VAR_PARENTID));
$feedback->set_source_table('questionnaire_feedback', array('sectionid' => backup::VAR_PARENTID));
- $questchoice->set_source_table('questionnaire_quest_choice', array('question_id' => backup::VAR_PARENTID));
+ $questchoice->set_source_table('questionnaire_quest_choice', array('question_id' => backup::VAR_PARENTID), 'id ASC');
$questdependency->set_source_table('questionnaire_dependency', array('questionid' => backup::VAR_PARENTID));
// All the rest of elements only happen if we are including user info.
@@ -190,10 +188,10 @@ protected function define_structure() {
// Define id annotations.
$response->annotate_ids('user', 'userid');
}
- // Define file annotations
+ // Define file annotations.
$questionnaire->annotate_files('mod_questionnaire', 'intro', null); // This file area hasn't itemid.
- $survey->annotate_files('mod_questionnaire', 'info', 'id'); // By survey->id
+ $survey->annotate_files('mod_questionnaire', 'info', 'id'); // By survey->id.
$survey->annotate_files('mod_questionnaire', 'thankbody', 'id'); // By survey->id.
$question->annotate_files('mod_questionnaire', 'question', 'id'); // By question->id.
@@ -204,4 +202,4 @@ protected function define_structure() {
// Return the root element, wrapped into standard activity structure.
return $this->prepare_activity_structure($questionnaire);
}
-}
\ No newline at end of file
+}
diff --git a/backup/moodle2/restore_questionnaire_activity_task.class.php b/backup/moodle2/restore_questionnaire_activity_task.class.php
index da6b7d50..ebb066b7 100644
--- a/backup/moodle2/restore_questionnaire_activity_task.class.php
+++ b/backup/moodle2/restore_questionnaire_activity_task.class.php
@@ -14,21 +14,17 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * @package mod_questionnaire
- * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
- * @author Mike Churchward
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
defined('MOODLE_INTERNAL') || die();
// Because it exists (must).
require_once($CFG->dirroot . '/mod/questionnaire/backup/moodle2/restore_questionnaire_stepslib.php');
/**
- * questionnaire restore task that provides all the settings and steps to perform one
- * complete restore of the activity
+ * Questionnaire restore task that provides all the settings and steps to perform one complete restore of the activity.
+ * @package mod_questionnaire
+ * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_questionnaire_activity_task extends restore_activity_task {
@@ -51,13 +47,15 @@ protected function define_my_steps() {
* Define the contents in the activity that must be
* processed by the link decoder
*/
- static public function define_decode_contents() {
+ public static function define_decode_contents() {
$contents = array();
$contents[] = new restore_decode_content('questionnaire', array('intro'), 'questionnaire');
$contents[] = new restore_decode_content('questionnaire_survey',
- array('info', 'thank_head', 'thank_body', 'thanks_page'), 'questionnaire_survey');
+ array('info', 'thank_head', 'thank_body', 'thanks_page', 'feedbacknotes'), 'questionnaire_survey');
$contents[] = new restore_decode_content('questionnaire_question', array('content'), 'questionnaire_question');
+ $contents[] = new restore_decode_content('questionnaire_fb_sections', array('sectionheading'), 'questionnaire_fb_sections');
+ $contents[] = new restore_decode_content('questionnaire_feedback', array('feedbacktext'), 'questionnaire_feedback');
return $contents;
}
@@ -66,7 +64,7 @@ static public function define_decode_contents() {
* Define the decoding rules for links belonging
* to the activity to be executed by the link decoder
*/
- static public function define_decode_rules() {
+ public static function define_decode_rules() {
$rules = array();
$rules[] = new restore_decode_rule('QUESTIONNAIREVIEWBYID', '/mod/questionnaire/view.php?id=$1', 'course_module');
@@ -78,11 +76,11 @@ static public function define_decode_rules() {
/**
* Define the restore log rules that will be applied
- * by the {@link restore_logs_processor} when restoring
+ * by the restore_logs_processor when restoring
* questionnaire logs. It must return one array
- * of {@link restore_log_rule} objects
+ * of restore_log_rule objects
*/
- static public function define_restore_log_rules() {
+ public static function define_restore_log_rules() {
$rules = array();
$rules[] = new restore_log_rule('questionnaire', 'add', 'view.php?id={course_module}', '{questionnaire}');
@@ -97,15 +95,15 @@ static public function define_restore_log_rules() {
/**
* Define the restore log rules that will be applied
- * by the {@link restore_logs_processor} when restoring
+ * by the restore_logs_processor when restoring
* course logs. It must return one array
- * of {@link restore_log_rule} objects
+ * of restore_log_rule objects
*
* Note this rules are applied when restoring course logs
* by the restore final task, but are defined here at
* activity level. All them are rules not linked to any module instance (cmid = 0)
*/
- static public function define_restore_log_rules_for_course() {
+ public static function define_restore_log_rules_for_course() {
$rules = array();
// Fix old wrong uses (missing extension).
diff --git a/backup/moodle2/restore_questionnaire_stepslib.php b/backup/moodle2/restore_questionnaire_stepslib.php
index 0b060b2f..d5235635 100644
--- a/backup/moodle2/restore_questionnaire_stepslib.php
+++ b/backup/moodle2/restore_questionnaire_stepslib.php
@@ -13,23 +13,18 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+use mod_questionnaire\feedback\section;
/**
+ * Define all the restore steps that will be used by the restore_questionnaire_activity_task.
+ *
+ * Structure step to restore one questionnaire activity.
+ *
* @package mod_questionnaire
* @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Define all the restore steps that will be used by the restore_questionnaire_activity_task
- */
-
-/**
- * Structure step to restore one questionnaire activity
- */
class restore_questionnaire_activity_structure_step extends restore_activity_structure_step {
/**
@@ -47,6 +42,10 @@ class restore_questionnaire_activity_structure_step extends restore_activity_str
*/
protected $olddependencies = [];
+ /**
+ * Implementation of define_structure.
+ * @return mixed
+ */
protected function define_structure() {
$paths = array();
@@ -110,6 +109,10 @@ protected function define_structure() {
return $this->prepare_activity_structure($paths);
}
+ /**
+ * Implementation of process_questionnaire.
+ * @param array $data
+ */
protected function process_questionnaire($data) {
global $DB;
@@ -126,6 +129,10 @@ protected function process_questionnaire($data) {
$this->apply_activity_instance($newitemid);
}
+ /**
+ * Process the survey table.
+ * @param array $data
+ */
protected function process_questionnaire_survey($data) {
global $DB;
@@ -146,6 +153,10 @@ protected function process_questionnaire_survey($data) {
$DB->set_field('questionnaire', 'sid', $newitemid, array('id' => $this->get_new_parentid('questionnaire')));
}
+ /**
+ * Process the questions.
+ * @param array $data
+ */
protected function process_questionnaire_question($data) {
global $DB;
@@ -166,9 +177,9 @@ protected function process_questionnaire_question($data) {
}
/**
+ * Process the feedback sections.
* $qid is unused, but is needed in order to get the $key elements of the array. Suppress PHPMD warning.
- *
- * @SuppressWarnings(PHPMD.UnusedLocalVariable)
+ * @param array $data
*/
protected function process_questionnaire_fb_sections($data) {
global $DB;
@@ -179,7 +190,7 @@ protected function process_questionnaire_fb_sections($data) {
// If this questionnaire has separate sections feedbacks.
if (isset($data->scorecalculation)) {
- $scorecalculation = unserialize($data->scorecalculation);
+ $scorecalculation = section::decode_scorecalculation($data->scorecalculation);
$newscorecalculation = array();
foreach ($scorecalculation as $qid => $val) {
$newqid = $this->get_mappingid('questionnaire_question', $qid);
@@ -193,6 +204,10 @@ protected function process_questionnaire_fb_sections($data) {
$this->set_mapping('questionnaire_fb_sections', $oldid, $newitemid, true);
}
+ /**
+ * Process feedback.
+ * @param array $data
+ */
protected function process_questionnaire_feedback($data) {
global $DB;
@@ -205,15 +220,24 @@ protected function process_questionnaire_feedback($data) {
$this->set_mapping('questionnaire_feedback', $oldid, $newitemid, true);
}
+ /**
+ * Process question choices.
+ * @param array $data
+ */
protected function process_questionnaire_quest_choice($data) {
global $CFG, $DB;
$data = (object)$data;
- // Replace the = separator with :: separator in quest_choice content.
- // This fixes radio button options using old "value"="display" formats.
require_once($CFG->dirroot.'/mod/questionnaire/locallib.php');
+ // Some old systems had '' instead of NULL. Change it to NULL.
+ if ($data->value === '') {
+ $data->value = null;
+ }
+
+ // Replace the = separator with :: separator in quest_choice content.
+ // This fixes radio button options using old "value"="display" formats.
if (($data->value == null || $data->value == 'NULL') && !preg_match("/^([0-9]{1,3}=.*|!other=.*)$/", $data->content)) {
$content = questionnaire_choice_values($data->content);
if (strpos($content->text, '=')) {
@@ -229,6 +253,10 @@ protected function process_questionnaire_quest_choice($data) {
$this->set_mapping('questionnaire_quest_choice', $oldid, $newitemid);
}
+ /**
+ * Process dependencies.
+ * @param array $data
+ */
protected function process_questionnaire_dependency($data) {
$data = (object)$data;
@@ -240,11 +268,20 @@ protected function process_questionnaire_dependency($data) {
}
}
+ /**
+ * Process attempts (these are no longer used).
+ * @param array $data
+ * @return bool
+ */
protected function process_questionnaire_attempt($data) {
// New structure will be completed in process_questionnaire_response. Nothing to do here any more.
return true;
}
+ /**
+ * Process responses.
+ * @param array $data
+ */
protected function process_questionnaire_response($data) {
global $DB;
@@ -264,6 +301,11 @@ protected function process_questionnaire_response($data) {
$this->set_mapping('questionnaire_response', $oldid, $newitemid);
}
+ /**
+ * Process boolean responses.
+ * @param array $data
+ * @throws dml_exception
+ */
protected function process_questionnaire_response_bool($data) {
global $DB;
@@ -275,6 +317,11 @@ protected function process_questionnaire_response_bool($data) {
$DB->insert_record('questionnaire_response_bool', $data);
}
+ /**
+ * Process date responses.
+ * @param array $data
+ * @throws dml_exception
+ */
protected function process_questionnaire_response_date($data) {
global $DB;
@@ -286,6 +333,11 @@ protected function process_questionnaire_response_date($data) {
$DB->insert_record('questionnaire_response_date', $data);
}
+ /**
+ * Process multiple responses.
+ * @param array $data
+ * @throws dml_exception
+ */
protected function process_questionnaire_response_multiple($data) {
global $DB;
@@ -298,6 +350,11 @@ protected function process_questionnaire_response_multiple($data) {
$DB->insert_record('questionnaire_resp_multiple', $data);
}
+ /**
+ * Process other responses.
+ * @param array $data
+ * @throws dml_exception
+ */
protected function process_questionnaire_response_other($data) {
global $DB;
@@ -310,6 +367,11 @@ protected function process_questionnaire_response_other($data) {
$DB->insert_record('questionnaire_response_other', $data);
}
+ /**
+ * Process rank responses.
+ * @param array $data
+ * @throws dml_exception
+ */
protected function process_questionnaire_response_rank($data) {
global $DB;
@@ -328,6 +390,11 @@ protected function process_questionnaire_response_rank($data) {
$DB->insert_record('questionnaire_response_rank', $data);
}
+ /**
+ * Process single responses.
+ * @param array $data
+ * @throws dml_exception
+ */
protected function process_questionnaire_response_single($data) {
global $DB;
@@ -340,6 +407,10 @@ protected function process_questionnaire_response_single($data) {
$DB->insert_record('questionnaire_resp_single', $data);
}
+ /**
+ * Process text answers.
+ * @param array $data
+ */
protected function process_questionnaire_response_text($data) {
global $DB;
@@ -351,6 +422,9 @@ protected function process_questionnaire_response_text($data) {
$DB->insert_record('questionnaire_response_text', $data);
}
+ /**
+ * Stuff to do after execution.
+ */
protected function after_execute() {
global $DB;
@@ -396,5 +470,10 @@ protected function after_execute() {
$this->add_related_files('mod_questionnaire', 'question', 'questionnaire_question');
$this->add_related_files('mod_questionnaire', 'sectionheading', 'questionnaire_fb_sections');
$this->add_related_files('mod_questionnaire', 'feedback', 'questionnaire_feedback');
+
+ // Process any old rate question named degree choices after all questions and choices have been restored.
+ if ($this->task->get_old_moduleversion() < 2018110103) {
+ \mod_questionnaire\question\rate::move_all_nameddegree_choices($this->get_new_parentid('questionnaire_survey'));
+ }
}
-}
\ No newline at end of file
+}
diff --git a/classes/completion/custom_completion.php b/classes/completion/custom_completion.php
new file mode 100644
index 00000000..3e7542f8
--- /dev/null
+++ b/classes/completion/custom_completion.php
@@ -0,0 +1,85 @@
+.
+declare(strict_types=1);
+
+namespace mod_questionnaire\completion;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/mod/questionnaire/lib.php');
+
+use coding_exception;
+use core_completion\activity_custom_completion;
+use moodle_exception;
+
+/**
+ * Activity custom completion subclass for the data activity.
+ *
+ * Class for defining mod_oucontent's custom completion rules and fetching the completion statuses
+ * of the custom completion rules for a given data instance and a user.
+ *
+ * @package mod_questionnaire
+ * @copyright 2022 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class custom_completion extends activity_custom_completion {
+ /**
+ * Fetches the completion state for a given completion rule.
+ *
+ * @param string $rule
+ * @return int
+ */
+ public function get_state(string $rule): int {
+ $this->validate_rule($rule);
+ $userid = $this->userid;
+ $cm = $this->cm;
+ $status = questionnaire_get_completion_state($cm, $userid, $rule);
+ return $status ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE;
+ }
+
+ /**
+ * Fetch the list of custom completion rules that this module defines.
+ *
+ * @return array
+ */
+ public static function get_defined_custom_rules(): array {
+ return [
+ 'completionsubmit'
+ ];
+ }
+
+ /**
+ * Returns an associative array of the descriptions of custom completion rules.
+ *
+ * @return array
+ */
+ public function get_custom_rule_descriptions(): array {
+ return [
+ 'completionsubmit' => get_string('completionsubmit', 'questionnaire')
+ ];
+ }
+
+ /**
+ * Returns an array of all completion rules, in the order they should be displayed to users.
+ *
+ * @return array
+ */
+ public function get_sort_order(): array {
+ return [
+ 'completionsubmit',
+ ];
+ }
+}
diff --git a/classes/db/bulk_sql_config.php b/classes/db/bulk_sql_config.php
index 1069c0c9..dce187cd 100644
--- a/classes/db/bulk_sql_config.php
+++ b/classes/db/bulk_sql_config.php
@@ -16,8 +16,6 @@
namespace mod_questionnaire\db;
-defined('MOODLE_INTERNAL') || die();
-
/**
* For bulk sql operations on useresponses.
*
@@ -25,7 +23,7 @@
* @copyright 2015 Guy Thomas
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class bulk_sql_config {
+class bulk_sql_config {
/**
* @var string $table
@@ -53,6 +51,7 @@ class bulk_sql_config {
protected $userank = false;
/**
+ * The class constructor.
* @param string $table
* @param string $tablealias
* @param bool $usechoiceid
@@ -78,4 +77,4 @@ public function get_extra_select() {
'rankvalue' => $this->userank
];
}
-}
\ No newline at end of file
+}
diff --git a/classes/edit_question_form.php b/classes/edit_question_form.php
index 71f65a41..92098e84 100644
--- a/classes/edit_question_form.php
+++ b/classes/edit_question_form.php
@@ -15,11 +15,11 @@
// along with Moodle. If not, see .
/**
+ * The form class for editing questions.
* @package mod_questionnaire
* @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
* @author Mike Churchward & Joseph Rézeau
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questionnaire
*/
namespace mod_questionnaire;
@@ -36,6 +36,9 @@
*/
class edit_question_form extends \moodleform {
+ /**
+ * Form definition.
+ */
public function definition() {
// TODO - Find a way to not use globals. Maybe the base class allows more parameters to be passed?
global $questionnaire, $question, $SESSION;
@@ -46,15 +49,23 @@ public function definition() {
$question->required = $SESSION->questionnaire->required;
}
if (!isset($question->type_id)) {
- print_error('undefinedquestiontype', 'questionnaire');
+ throw new \moodle_exception('undefinedquestiontype', 'mod_questionnaire');
}
// Each question can provide its own form elements to the provided form, or use the default ones.
if (!$question->edit_form($this, $questionnaire)) {
- print_error("Question type had an unknown error in the edit_form method.");
+ throw new \moodle_exception('Question type had an unknown error in the edit_form method.', 'mod_questionnaire');
}
}
+ /**
+ * Form validation.
+ *
+ * @param array $data array of ("fieldname"=>value) of submitted data
+ * @param array $files array of uploaded files "element_name"=>tmp_file_path
+ * @return array of "element_name"=>"error_description" if there are errors,
+ * or an empty array if everything is OK (true allowed for backwards compatibility too).
+ */
public function validation($data, $files) {
$errors = parent::validation($data, $files);
@@ -79,6 +90,36 @@ public function validation($data, $files) {
}
}
+ // If this is a slider question.
+ if ($data['type_id'] == QUESSLIDER) {
+ if (isset($data['minrange']) && isset($data['maxrange']) && isset($data['startingvalue']) &&
+ isset($data['stepvalue'])) {
+ if ($data['minrange'] >= $data['maxrange']) {
+ $errors['maxrange'] = get_string('invalidrange', 'questionnaire');
+ }
+
+ if (($data['startingvalue'] > $data['maxrange']) || ($data['startingvalue'] < $data['minrange'])) {
+ $errors['startingvalue'] = get_string('invalidstartingvalue', 'questionnaire');
+ }
+
+ if ($data['startingvalue'] > 100 || $data['startingvalue'] < -100) {
+ $errors['startingvalue'] = get_string('invalidstartingvalue', 'questionnaire');
+ }
+
+ if (($data['stepvalue'] > $data['maxrange']) || $data['stepvalue'] < 1) {
+ $errors['stepvalue'] = get_string('invalidincrement', 'questionnaire');
+ }
+
+ if ($data['minrange'] < -100) {
+ $errors['minrange'] = get_string('invalidminmaxrange', 'questionnaire');
+ }
+
+ if ($data['maxrange'] > 100) {
+ $errors['maxrange'] = get_string('invalidminmaxrange', 'questionnaire');
+ }
+ }
+ }
+
return $errors;
}
@@ -86,7 +127,6 @@ public function validation($data, $files) {
* Magic method for getting the protected $_form MoodleQuickForm and $_customdata array properties.
* @param string $name
* @return mixed
- * @throws \coding_exception
*/
public function __get($name) {
if ($name == '_form') {
@@ -97,4 +137,4 @@ public function __get($name) {
throw new \coding_exception($name.' is not a publicly accessible property of '.get_class($this));
}
}
-}
\ No newline at end of file
+}
diff --git a/classes/event/all_responses_deleted.php b/classes/event/all_responses_deleted.php
index ce634f0b..af2b1227 100644
--- a/classes/event/all_responses_deleted.php
+++ b/classes/event/all_responses_deleted.php
@@ -24,8 +24,6 @@
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire all responses deleted event class.
*
diff --git a/classes/event/all_responses_saved_as_text.php b/classes/event/all_responses_saved_as_text.php
index 867982a6..860b27c0 100644
--- a/classes/event/all_responses_saved_as_text.php
+++ b/classes/event/all_responses_saved_as_text.php
@@ -24,8 +24,6 @@
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire all_responses_saved_as_text event class.
*
diff --git a/classes/event/all_responses_viewed.php b/classes/event/all_responses_viewed.php
index 44961418..98b59ce3 100644
--- a/classes/event/all_responses_viewed.php
+++ b/classes/event/all_responses_viewed.php
@@ -24,8 +24,6 @@
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire all_responses_viewed event class.
*
@@ -86,14 +84,4 @@ public function get_url() {
}
return new \moodle_url("/mod/questionnaire/report.php", $params);
}
-
- /**
- * Return the legacy event log data.
- *
- * @return array
- */
- protected function get_legacy_logdata() {
- return array($this->courseid, "questionnaire", "view report", "report.php?id=" . $this->contextinstanceid, $this->objectid,
- $this->contextinstanceid);
- }
}
diff --git a/classes/event/attempt_resumed.php b/classes/event/attempt_resumed.php
index ba4efccc..c2057e2c 100644
--- a/classes/event/attempt_resumed.php
+++ b/classes/event/attempt_resumed.php
@@ -24,8 +24,6 @@
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire attempt_resumed event.
*
@@ -73,4 +71,4 @@ public function get_url() {
return new \moodle_url("/mod/questionnaire/view.php", array('id' => $this->contextinstanceid));
}
-}
\ No newline at end of file
+}
diff --git a/classes/event/attempt_saved.php b/classes/event/attempt_saved.php
index 95d28013..c5ca4cea 100644
--- a/classes/event/attempt_saved.php
+++ b/classes/event/attempt_saved.php
@@ -24,8 +24,6 @@
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire attempt_saved event class.
*
@@ -72,16 +70,6 @@ public function get_url() {
return new \moodle_url("/mod/questionnaire/view.php", array('id' => $this->contextinstanceid));
}
- /**
- * Return the legacy event log data.
- *
- * @return array
- */
- protected function get_legacy_logdata() {
- return array($this->courseid, "questionnaire", "save", "view.php?id=" .
- $this->contextinstanceid, $this->other['questionnaireid'], $this->contextinstanceid);
- }
-
/**
* Custom validation.
*
diff --git a/classes/event/attempt_submitted.php b/classes/event/attempt_submitted.php
index d36366e1..274bdcbb 100644
--- a/classes/event/attempt_submitted.php
+++ b/classes/event/attempt_submitted.php
@@ -24,8 +24,6 @@
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire attempt_submitted event class.
*
@@ -72,16 +70,6 @@ public function get_url() {
return new \moodle_url("/mod/questionnaire/view.php", array('id' => $this->contextinstanceid));
}
- /**
- * Return the legacy event log data.
- *
- * @return array
- */
- protected function get_legacy_logdata() {
- return array($this->courseid, "questionnaire", "submit", "view.php?id=" .
- $this->contextinstanceid, $this->other['questionnaireid'], $this->contextinstanceid);
- }
-
/**
* Custom validation.
*
diff --git a/classes/event/course_module_instance_list_viewed.php b/classes/event/course_module_instance_list_viewed.php
index c7797f4e..f4781d73 100644
--- a/classes/event/course_module_instance_list_viewed.php
+++ b/classes/event/course_module_instance_list_viewed.php
@@ -24,7 +24,6 @@
*/
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
/**
* The mod_questionnaire instance list viewed event class.
@@ -37,4 +36,4 @@
*/
class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
// No code required here as the parent class handles it all.
-}
\ No newline at end of file
+}
diff --git a/classes/event/course_module_viewed.php b/classes/event/course_module_viewed.php
index 2f39845d..3c6dbb42 100644
--- a/classes/event/course_module_viewed.php
+++ b/classes/event/course_module_viewed.php
@@ -24,8 +24,6 @@
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_survery course module viewed event class.
*
@@ -44,14 +42,4 @@ protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
}
-
- /**
- * Return the legacy event log data.
- *
- * @return array
- */
- protected function get_legacy_logdata() {
- return array($this->courseid, $this->objecttable, 'view '. $this->other['viewed'], 'view.php?id=' .
- $this->contextinstanceid, $this->objectid, $this->contextinstanceid);
- }
}
diff --git a/classes/event/non_respondents_viewed.php b/classes/event/non_respondents_viewed.php
index 462bec23..cc27edf7 100644
--- a/classes/event/non_respondents_viewed.php
+++ b/classes/event/non_respondents_viewed.php
@@ -24,8 +24,6 @@
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire non_respondents_viewed event class.
*
diff --git a/classes/event/question_created.php b/classes/event/question_created.php
index 69aff135..b480b678 100644
--- a/classes/event/question_created.php
+++ b/classes/event/question_created.php
@@ -14,38 +14,25 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * The mod_questionnaire question_created event.
- *
- * @package mod_questionnaire
- * @copyright 2014 Joseph Rézeau
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire question_created event class.
*
* @package mod_questionnaire
- * @since Moodle 2.7
* @copyright 2014 Joseph Rézeau
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
class question_created extends \core\event\base {
- /*
+ /**
* Set basic properties for the event.
*/
-
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_TEACHING;
}
- /*
+ /**
* Return localised event name.
*
* @return string
@@ -54,7 +41,7 @@ public static function get_name() {
return get_string('event_question_created', 'mod_questionnaire');
}
- /*
+ /**
* Returns description of what happened.
*
* @return string
@@ -64,4 +51,4 @@ public function get_description() {
return "The user with id '$this->userid' has created or modified a question of type '$questiontype' for
the questionnaire with course module id '$this->contextinstanceid'.";
}
-}
\ No newline at end of file
+}
diff --git a/classes/event/question_deleted.php b/classes/event/question_deleted.php
index 456d4691..900172b3 100644
--- a/classes/event/question_deleted.php
+++ b/classes/event/question_deleted.php
@@ -14,39 +14,25 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * The mod_questionnaire question_deleted event.
- *
- * @package mod_questionnaire
- * @copyright 2014 Joseph Rézeau
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire question_deleted event class.
*
* @package mod_questionnaire
- * @since Moodle 2.7
* @copyright 2014 Joseph Rézeau
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-
class question_deleted extends \core\event\base {
- /*
+ /**
* Set basic properties for the event.
*/
-
protected function init() {
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_TEACHING;
}
- /*
+ /**
* Return localised event name.
*
* @return string
@@ -55,7 +41,7 @@ public static function get_name() {
return get_string('event_question_deleted', 'mod_questionnaire');
}
- /*
+ /**
* Returns description of what happened.
*
* @return string
@@ -65,4 +51,4 @@ public function get_description() {
return "The user with id '$this->userid' has deleted a question of type '$questiontype' for
the questionnaire with course module id '$this->contextinstanceid'.";
}
-}
\ No newline at end of file
+}
diff --git a/classes/event/questionnaire_previewed.php b/classes/event/questionnaire_previewed.php
index 296b19f2..c4607e81 100644
--- a/classes/event/questionnaire_previewed.php
+++ b/classes/event/questionnaire_previewed.php
@@ -24,8 +24,6 @@
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire questionnaire_previewed event class.
*
diff --git a/classes/event/response_deleted.php b/classes/event/response_deleted.php
index 393c62ca..2453579d 100644
--- a/classes/event/response_deleted.php
+++ b/classes/event/response_deleted.php
@@ -24,8 +24,6 @@
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire response_deleted event class.
*
diff --git a/classes/event/response_viewed.php b/classes/event/response_viewed.php
index 2ae67a88..404f3955 100644
--- a/classes/event/response_viewed.php
+++ b/classes/event/response_viewed.php
@@ -24,8 +24,6 @@
namespace mod_questionnaire\event;
-defined('MOODLE_INTERNAL') || die();
-
/**
* The mod_questionnaire response_viewed event class.
*
@@ -77,14 +75,4 @@ public function get_url() {
$params['group'] = $this->other['currentgroupid'];
return new \moodle_url("/mod/questionnaire/report.php", $params);
}
-
- /**
- * Return the legacy event log data.
- *
- * @return array
- */
- protected function get_legacy_logdata() {
- return array($this->courseid, "questionnaire", "view report", "report.php?id=" . $this->contextinstanceid, $this->objectid,
- $this->contextinstanceid);
- }
}
diff --git a/classes/feedback/section.php b/classes/feedback/section.php
index 2c8c1b4f..e4040702 100644
--- a/classes/feedback/section.php
+++ b/classes/feedback/section.php
@@ -14,41 +14,46 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * Manage feedback sections.
- *
- * @package mod_questionnaire
- * @copyright 2018 onward Mike Churchward (mike.churchward@poetopensource.org)
- * @author Mike Churchward
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
namespace mod_questionnaire\feedback;
+
defined('MOODLE_INTERNAL') || die();
use invalid_parameter_exception;
use coding_exception;
+#[\AllowDynamicProperties]
/**
* Class for describing a feedback section.
*
- * @author Mike Churchward
- * @package feedback
+ * @package mod_questionnaire
+ * @copyright 2018 onward Mike Churchward (mike.churchward@poetopensource.org)
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
class section {
+ /** @var int */
public $id = 0;
+ /** @var int */
public $surveyid = 0;
+ /** @var int */
public $section = 1;
+ /** @var array */
public $scorecalculation = [];
+ /** @var string */
public $sectionlabel = '';
+ /** @var string */
public $sectionheading = '';
+ /** @var string */
public $sectionheadingformat = FORMAT_HTML;
+ /** @var array */
public $sectionfeedback = [];
+ /** @var array */
public $questions = [];
+ /** The table name. */
const TABLE = 'questionnaire_fb_sections';
+ /** Represents the "no score" setting. */
const NOSCORE = -1;
/**
@@ -58,13 +63,13 @@ class section {
* 'surveyid' - the surveyid field of the fb_sections table (required if no 'id' field),
* 'sectionnum' - the section field of the fb_sections table (ignored if 'id' is present; defaults to 1).
*
- * @param array $params As above
* @param array $questions Array of mod_questionnaire\question objects.
+ * @param array $params As above
* @throws \dml_exception
* @throws coding_exception
* @throws invalid_parameter_exception
*/
- public function __construct($params = [], $questions) {
+ public function __construct($questions, $params = []) {
if (!is_array($params) || !is_array($questions)) {
throw new coding_exception('Invalid data provided.');
@@ -80,7 +85,9 @@ public function __construct($params = [], $questions) {
/**
* Factory method to create a new, empty section and return an instance.
- *
+ * @param int $surveyid
+ * @param string $sectionlabel
+ * @return section
*/
public static function new_section($surveyid, $sectionlabel = '') {
global $DB;
@@ -93,7 +100,7 @@ public static function new_section($surveyid, $sectionlabel = '') {
$newsection->surveyid = $surveyid;
$newsection->section = $maxsection + 1;
$newsection->sectionlabel = $sectionlabel;
- $newsection->scorecalculation = '';
+ $newsection->scorecalculation = $newsection->encode_scorecalculation([]);
$newsecid = $DB->insert_record(self::TABLE, $newsection);
$newsection->id = $newsecid;
$newsection->scorecalculation = [];
@@ -139,7 +146,7 @@ public function load_section($params) {
$this->id = $feedbackrec->id;
$this->surveyid = $feedbackrec->surveyid;
$this->section = $feedbackrec->section;
- $this->scorecalculation = $this->decode_scorecalculation($feedbackrec->scorecalculation);
+ $this->scorecalculation = $this->get_valid_scorecalculation($feedbackrec->scorecalculation);
$this->sectionlabel = $feedbackrec->sectionlabel;
$this->sectionheading = $feedbackrec->sectionheading;
$this->sectionheadingformat = $feedbackrec->sectionheadingformat;
@@ -154,11 +161,8 @@ public function load_section($params) {
/**
* Loads the section feedback record into the proper array location.
*
- * @param $feedbackrec
+ * @param \stdClass $feedbackrec
* @return int The id of the section feedback record.
- * @throws \dml_exception
- * @throws coding_exception
- * @throws invalid_parameter_exception
*/
public function load_sectionfeedback($feedbackrec) {
if (!isset($feedbackrec->id) || empty($feedbackrec->id)) {
@@ -174,8 +178,7 @@ public function load_sectionfeedback($feedbackrec) {
/**
* Updates the object and data record with a new scorecalculation. If no new score provided, uses what's in the object.
*
- * @param $scorecalculation
- * @throws \dml_exception
+ * @param array $scorecalculation
* @throws coding_exception
*/
public function set_new_scorecalculation($scorecalculation = null) {
@@ -219,13 +222,14 @@ public function delete() {
$DB->delete_records(self::TABLE, ['id' => $this->id]);
// Resequence the section numbers as necessary.
- $allsections = $DB->get_records(self::TABLE, ['surveyid' => $this->surveyid], 'section ASC');
- $count = 1;
- foreach ($allsections as $id => $section) {
- if ($section->section != $count) {
- $DB->set_field(self::TABLE, 'section', $count, ['id' => $id]);
+ if ($allsections = $DB->get_records(self::TABLE, ['surveyid' => $this->surveyid], 'section ASC')) {
+ $count = 1;
+ foreach ($allsections as $id => $section) {
+ if ($section->section != $count) {
+ $DB->set_field(self::TABLE, 'section', $count, ['id' => $id]);
+ }
+ $count++;
}
- $count++;
}
}
@@ -253,7 +257,7 @@ public function update() {
$this->scorecalculation = $this->encode_scorecalculation($this->scorecalculation);
$DB->update_record(self::TABLE, $this);
- $this->scorecalculation = $this->decode_scorecalculation($this->scorecalculation);
+ $this->scorecalculation = $this->get_valid_scorecalculation($this->scorecalculation);
foreach ($this->sectionfeedback as $sectionfeedback) {
$sectionfeedback->update();
@@ -261,11 +265,12 @@ public function update() {
}
/**
- * @param string $codedstring
- * @return mixed
+ * Decode and ensure scorecalculation is what we expect.
+ * @param string|null $codedstring
+ * @return array
* @throws coding_exception
*/
- protected function decode_scorecalculation($codedstring) {
+ public static function decode_scorecalculation(?string $codedstring): array {
// Expect a serialized data string.
if (($codedstring == null)) {
$codedstring = '';
@@ -274,11 +279,33 @@ protected function decode_scorecalculation($codedstring) {
throw new coding_exception('Invalid scorecalculation format.');
}
if (!empty($codedstring)) {
- $scorecalculation = unserialize($codedstring);
+ $scorecalculation = unserialize_array($codedstring) ?: [];
} else {
$scorecalculation = [];
}
+ if (!is_array($scorecalculation)) {
+ throw new coding_exception('Invalid scorecalculation format.');
+ }
+
+ foreach ($scorecalculation as $score) {
+ if (!empty($score) && !is_numeric($score)) {
+ throw new coding_exception('Invalid scorecalculation format.');
+ }
+ }
+
+ return $scorecalculation;
+ }
+
+ /**
+ * Return the decoded and validated calculation array.
+ * @param string $codedstring
+ * @return mixed
+ * @throws coding_exception
+ */
+ protected function get_valid_scorecalculation($codedstring) {
+ $scorecalculation = static::decode_scorecalculation($codedstring);
+
// Check for deleted questions and questions that don't support scores.
foreach ($scorecalculation as $qid => $score) {
if (!isset($this->questions[$qid])) {
@@ -292,6 +319,7 @@ protected function decode_scorecalculation($codedstring) {
}
/**
+ * Return the encoded score array as a serialized string.
* @param string $scorearray
* @return mixed
* @throws coding_exception
@@ -306,4 +334,4 @@ protected function encode_scorecalculation($scorearray) {
return $scorecalculation;
}
-}
\ No newline at end of file
+}
diff --git a/classes/feedback/sectionfeedback.php b/classes/feedback/sectionfeedback.php
index bc479359..69e56291 100644
--- a/classes/feedback/sectionfeedback.php
+++ b/classes/feedback/sectionfeedback.php
@@ -14,17 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * Manage feedback sections.
- *
- * @package mod_questionnaire
- * @copyright 2018 onward Mike Churchward (mike.churchward@poetopensource.org)
- * @author Mike Churchward
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
namespace mod_questionnaire\feedback;
-defined('MOODLE_INTERNAL') || die();
use invalid_parameter_exception;
use coding_exception;
@@ -32,28 +22,34 @@
/**
* Class for describing a feedback section's feedback definition.
*
- * @author Mike Churchward
- * @package feedback
+ * @package mod_questionnaire
+ * @copyright 2018 onward Mike Churchward (mike.churchward@poetopensource.org)
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
class sectionfeedback {
-
+ /** @var int */
public $id = 0;
+ /** @var int */
public $sectionid = 0;
+ /** @var string */
public $feedbacklabel = ''; // I don't think this is actually used?
+ /** @var string */
public $feedbacktext = '';
+ /** @var string */
public $feedbacktextformat = FORMAT_HTML;
+ /** @var float */
public $minscore = 0.0;
+ /** @var float */
public $maxscore = 0.0;
+ /** The table name. */
const TABLE = 'questionnaire_feedback';
/**
+ * Class constructor.
* @param int $id
* @param null|object $record
- * @throws \dml_exception
- * @throws coding_exception
- * @throws invalid_parameter_exception
*/
public function __construct($id = 0, $record = null) {
// Return a new section based on the data id.
@@ -70,7 +66,8 @@ public function __construct($id = 0, $record = null) {
/**
* Factory method to create a new sectionfeedback from the provided data and return an instance.
- *
+ * @param \stdClass $data
+ * @return sectionfeedback
*/
public static function new_sectionfeedback($data) {
global $DB;
@@ -99,9 +96,9 @@ public function update() {
}
/**
- * @param $id
+ * Return the record specified by the id.
+ * @param int $id
* @return mixed
- * @throws \dml_exception
*/
protected function get_sectionfeedback($id) {
global $DB;
@@ -134,4 +131,4 @@ protected function get_section($id) {
return $DB->get_record(self::TABLE, ['id' => $id]);
}
-}
\ No newline at end of file
+}
diff --git a/classes/feedback_form.php b/classes/feedback_form.php
index 4ce52137..25260847 100644
--- a/classes/feedback_form.php
+++ b/classes/feedback_form.php
@@ -14,6 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/formslib.php');
+require_once($CFG->dirroot.'/mod/questionnaire/lib.php');
+
/**
* Print the form to manage feedback settings.
*
@@ -22,25 +29,20 @@
* @author Joseph Rezeau
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
-
-namespace mod_questionnaire;
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->libdir . '/formslib.php');
-require_once($CFG->dirroot.'/mod/questionnaire/lib.php');
-
class feedback_form extends \moodleform {
+ /**
+ * Defition of the form.
+ */
public function definition() {
global $questionnaire;
$mform =& $this->_form;
// Questionnaire Feedback Sections and Messages.
+ $mform->addElement('header', 'submithdr', get_string('feedbackoptions', 'questionnaire'));
$feedbackoptions = [];
$feedbackoptions[0] = get_string('feedbacknone', 'questionnaire');
- $mform->addElement('header', 'submithdr', get_string('feedbackoptions', 'questionnaire'));
$feedbackoptions[1] = get_string('feedbackglobal', 'questionnaire');
$feedbackoptions[2] = get_string('feedbacksections', 'questionnaire');
@@ -130,8 +132,14 @@ public function definition() {
}
}
+ /**
+ * Validate the data submitted.
+ * @param array $data
+ * @param array $files
+ * @return array
+ */
public function validation($data, $files) {
$errors = parent::validation($data, $files);
return $errors;
}
-}
\ No newline at end of file
+}
diff --git a/classes/feedback_section_form.php b/classes/feedback_section_form.php
index 0f8fe73b..e566d04c 100644
--- a/classes/feedback_section_form.php
+++ b/classes/feedback_section_form.php
@@ -14,6 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/formslib.php');
+require_once($CFG->dirroot.'/mod/questionnaire/lib.php');
+
/**
* Print the form to add or edit a questionnaire-instance
*
@@ -22,18 +29,18 @@
* @author Joseph Rezeau (based on Quiz by Tim Hunt)
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
-
-namespace mod_questionnaire;
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->libdir . '/formslib.php');
-require_once($CFG->dirroot.'/mod/questionnaire/lib.php');
-
class feedback_section_form extends \moodleform {
+ /** @var mixed $_feedbacks */
protected $_feedbacks;
+ /**
+ * @var \context $context The used context.
+ */
+ public $context;
+ /**
+ * Form definition.
+ */
public function definition() {
global $questionnaire;
@@ -175,6 +182,10 @@ public function definition() {
$mform->closeHeaderBefore('buttonar');
}
+ /**
+ * Form preprocessing.
+ * @param array $toform
+ */
public function data_preprocessing(&$toform) {
if (count($this->_feedbacks)) {
$key = 0;
@@ -200,6 +211,13 @@ public function data_preprocessing(&$toform) {
}
}
}
+
+ /**
+ * Form validation.
+ * @param array $data
+ * @param array $files
+ * @return array
+ */
public function validation($data, $files) {
$errors = parent::validation($data, $files);
@@ -250,7 +268,7 @@ public function validation($data, $files) {
* form definition (new entry form); this function is used to load in data where values
* already exist and data is being edited (edit entry form).
*
- * @param mixed $default_values object or array of default values
+ * @param array $defaultvalues
*/
public function set_data($defaultvalues) {
if (is_object($defaultvalues)) {
@@ -259,4 +277,4 @@ public function set_data($defaultvalues) {
$this->data_preprocessing($defaultvalues);
parent::set_data($defaultvalues);
}
-}
\ No newline at end of file
+}
diff --git a/classes/file_storage.php b/classes/file_storage.php
new file mode 100644
index 00000000..329e9dff
--- /dev/null
+++ b/classes/file_storage.php
@@ -0,0 +1,60 @@
+.
+
+namespace mod_questionnaire;
+
+/**
+ * Defines the file stoeage class for questionnaire.
+ * @package mod_questionnaire
+ * @copyright 2020 onwards Mike Churchward (mike.churchward@poetopensource.org)
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class file_storage extends \file_storage {
+
+ /**
+ * Copy all the files in a file area from one context to another.
+ *
+ * @param int $oldcontextid the context the files are being moved from.
+ * @param int $newcontextid the context the files are being moved to.
+ * @param string $component the plugin that these files belong to.
+ * @param string $filearea the name of the file area.
+ * @param int|boolean $olditemid The identifier for the old file area if required.
+ * @param int|boolean $newitemid The identifier for the new file area if different than old.
+ * @return int the number of files copied, for information.
+ * @throws \coding_exception
+ * @throws \file_exception
+ * @throws \stored_file_creation_exception
+ */
+ public function copy_area_files_to_new_context($oldcontextid, $newcontextid, $component, $filearea, $olditemid = false,
+ $newitemid = false) {
+ $count = 0;
+
+ $oldfiles = $this->get_area_files($oldcontextid, $component, $filearea, $olditemid, 'id', false);
+ foreach ($oldfiles as $oldfile) {
+ $filerecord = new \stdClass();
+ $filerecord->contextid = $newcontextid;
+ if ($newitemid !== false) {
+ $filerecord->itemid = $newitemid;
+ } else {
+ $filerecord->itemid = $olditemid;
+ }
+ $this->create_file_from_storedfile($filerecord, $oldfile);
+ $count += 1;
+ }
+ return $count;
+ }
+}
diff --git a/classes/generator/question_response.php b/classes/generator/question_response.php
index 77050825..fc4c7221 100644
--- a/classes/generator/question_response.php
+++ b/classes/generator/question_response.php
@@ -14,22 +14,30 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\generator;
+
+use mod_questionnaire\responsetype\response\response;
+
/**
* Question response class
* @author gthomas2
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package mod_questionnaire
*/
-
-namespace mod_questionnaire\generator;
-
-defined('MOODLE_INTERNAL') || die();
-
class question_response {
+ /** @var int $questionid */
public $questionid;
+ /** @var response $response */
public $response;
+ /**
+ * Class constructor.
+ * @param int $questionid
+ * @param response $response
+ */
public function __construct($questionid, $response) {
$this->questionid = $questionid;
$this->response = $response;
}
-}
\ No newline at end of file
+}
diff --git a/classes/generator/question_response_rank.php b/classes/generator/question_response_rank.php
index 65edd13f..4ff25a7e 100644
--- a/classes/generator/question_response_rank.php
+++ b/classes/generator/question_response_rank.php
@@ -14,22 +14,30 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\generator;
+
+use mod_questionnaire\question\choice;
+
/**
* Question response rank class
* @author gthomas2
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package mod_questionnaire
*/
-
-namespace mod_questionnaire\generator;
-
-defined('MOODLE_INTERNAL') || die();
-
class question_response_rank {
+ /** @var choice $choice */
public $choice;
+ /** @var int $rankvalue */
public $rankvalue;
+ /**
+ * Class constructor.
+ * @param choice $choice
+ * @param int $rank
+ */
public function __construct($choice, $rank) {
$this->choice = $choice;
$this->rankvalue = $rank;
}
-}
\ No newline at end of file
+}
diff --git a/classes/output/completepage.php b/classes/output/completepage.php
index d3edd3db..03192113 100644
--- a/classes/output/completepage.php
+++ b/classes/output/completepage.php
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\output;
+
/**
* Contains class mod_questionnaire\output\viewpage
*
@@ -22,11 +24,6 @@
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-namespace mod_questionnaire\output;
-
-defined('MOODLE_INTERNAL') || die();
-
class completepage implements \renderable, \templatable {
/**
@@ -49,8 +46,8 @@ public function __construct($data = null) {
/**
* Add data for export.
- * @param string The index for the data.
- * @param string The content for the index.
+ * @param string $element The index for the data.
+ * @param string $content The content for the index.
*/
public function add_to_page($element, $content) {
if ($element !== 'questions') {
@@ -68,4 +65,4 @@ public function export_for_template(\renderer_base $output) {
return $this->data;
}
-}
\ No newline at end of file
+}
diff --git a/classes/output/fbsectionspage.php b/classes/output/fbsectionspage.php
index ccacc497..2c3b0cb1 100644
--- a/classes/output/fbsectionspage.php
+++ b/classes/output/fbsectionspage.php
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\output;
+
/**
* Contains class mod_questionnaire\output\fbsectionspage
*
@@ -22,11 +24,6 @@
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-namespace mod_questionnaire\output;
-
-defined('MOODLE_INTERNAL') || die();
-
class fbsectionspage implements \renderable, \templatable {
/**
@@ -49,8 +46,8 @@ public function __construct($data = null) {
/**
* Add data for export.
- * @param string The index for the data.
- * @param string The content for the index.
+ * @param string $element The index for the data.
+ * @param string $content The content for the index.
*/
public function add_to_page($element, $content) {
$this->data->{$element} = empty($this->data->{$element}) ? $content : ($this->data->{$element} . $content);
@@ -64,4 +61,4 @@ public function export_for_template(\renderer_base $output) {
return $this->data;
}
-}
\ No newline at end of file
+}
diff --git a/classes/output/feedbackpage.php b/classes/output/feedbackpage.php
index 5d464553..21eed2c3 100644
--- a/classes/output/feedbackpage.php
+++ b/classes/output/feedbackpage.php
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\output;
+
/**
* Contains class mod_questionnaire\output\feedback
*
@@ -22,11 +24,6 @@
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-namespace mod_questionnaire\output;
-
-defined('MOODLE_INTERNAL') || die();
-
class feedbackpage implements \renderable, \templatable {
/**
@@ -49,8 +46,8 @@ public function __construct($data = null) {
/**
* Add data for export.
- * @param string The index for the data.
- * @param string The content for the index.
+ * @param string $element The index for the data.
+ * @param string $content The content for the index.
*/
public function add_to_page($element, $content) {
$this->data->{$element} = empty($this->data->{$element}) ? $content : ($this->data->{$element} . $content);
@@ -64,4 +61,4 @@ public function export_for_template(\renderer_base $output) {
return $this->data;
}
-}
\ No newline at end of file
+}
diff --git a/classes/output/mobile.php b/classes/output/mobile.php
index 7ec783d1..0e3d0d28 100644
--- a/classes/output/mobile.php
+++ b/classes/output/mobile.php
@@ -14,16 +14,18 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\output;
+
+use mod_questionnaire\responsetype\response\response;
+
/**
* Mobile output class for mod_questionnaire.
*
- * @copyright 2018 Igor Sazonov
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package mod_questionnaire
+ * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-namespace mod_questionnaire\output;
-
-defined('MOODLE_INTERNAL') || die();
-
class mobile {
/**
@@ -33,261 +35,162 @@ class mobile {
* @return array HTML, javascript and other data
*/
public static function mobile_view_activity($args) {
- global $OUTPUT, $USER, $CFG, $DB, $SESSION;
-
+ global $OUTPUT, $USER, $CFG, $DB;
require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php');
+
$args = (object) $args;
+
+ $versionname = $args->appversioncode >= 44000 ? 'latest' : 'ionic5';
$cmid = $args->cmid;
+ $rid = isset($args->rid) ? $args->rid : 0;
+ $action = isset($args->action) ? $args->action : 'index';
$pagenum = (isset($args->pagenum) && !empty($args->pagenum)) ? intval($args->pagenum) : 1;
- $prevpage = 0;
- if (!empty($SESSION->prevpage)) {
- $prevpage = $SESSION->prevpage;
- if (!$prevpage) {
- $SESSION->prevpage = $pagenum;
- if ($pagenum == 1) {
- $prevpage = 0;
- } else {
- $prevpage = $pagenum;
- }
- }
- }
+ $userid = isset($args->userid) ? $args->userid : $USER->id;
+ $submit = isset($args->submit) ? $args->submit : false;
+ $completed = isset($args->completed) ? $args->completed : false;
+
+ list($cm, $course, $questionnaire) = questionnaire_get_standard_page_items($cmid);
+ $questionnaire = new \questionnaire($course, $cm, 0, $questionnaire);
+
+ $data = [];
+ $data['cmid'] = $cmid;
+ $data['userid'] = $userid;
+ $data['intro'] = $questionnaire->intro;
+ $data['autonumquestions'] = $questionnaire->autonum;
+ $data['id'] = $questionnaire->id;
+ $data['rid'] = $rid;
+ $data['surveyid'] = $questionnaire->survey->id;
+ $data['pagenum'] = $pagenum;
+ $data['prevpage'] = 0;
+ $data['nextpage'] = 0;
+
// Capabilities check.
- $cm = get_coursemodule_from_id('questionnaire', $cmid);
$context = \context_module::instance($cmid);
self::require_capability($cm, $context, 'mod/questionnaire:view');
- // Set some variables we are going to be using.
- $questionnaire = get_questionnaire_data($cmid, $USER->id);
- if (isset($questionnaire['questions'][$pagenum - 1]) && !empty($questionnaire['questions'][$pagenum - 1])) {
- $prevpage = $pagenum - 1;
- }
- $data = [
- 'questionnaire' => $questionnaire,
- 'cmid' => $cmid,
- 'courseid' => intval($cm->course),
- 'pagenum' => $pagenum,
- 'userid' => $USER->id,
- 'nextpage' => 0,
- 'prevpage' => 0,
- 'emptypage' => false
- ];
- $pagebreaks = false;
- $branching = check_mobile_branching_logic($questionnaire);
- if ($branching) {
- $pagebreaks = true;
- }
- $break = false;
- // Checking for completion below, cmid is a complettion variable.
- // Checks whether a questionnaire was touched, so created a proper completed check coming from the DB.
- if ($cmid) {
- $data['completed'] = (isset($questionnaire['response']['complete'])
- && $questionnaire['response']['complete'] == 'y') ? 1 : 0;
- $data['complete_userdate'] = (isset($questionnaire['response']['complete'])
- && $questionnaire['response']['complete'] == 'y') ?
- userdate($questionnaire['response']['submitted']) : '';
- if (isset($questionnaire['questions'][$pagenum]) && $branching == false) {
- $i = 0;
- foreach ($questionnaire['questions'][$pagenum] as $questionid => $choices) {
- if (isset($questionnaire['questionsinfo'][$pagenum][$questionid])
- && !empty($questionnaire['questionsinfo'][$pagenum][$questionid])) {
- $data['questions'][$pagenum][$i]['info'] = $questionnaire['questionsinfo'][$pagenum][$questionid];
- if ($data['questions'][$pagenum][$i]['info']['required'] == 'n') {
- unset($data['questions'][$pagenum][$i]['info']['required']);
- }
- $ii = 0;
- foreach ($choices as $k => $v) {
- $data['questions'][$pagenum][$i]['choices'][$ii] = (array) $v;
- $ii++;
- }
- if (count($choices) == 1) {
- $data['questions'][$pagenum][$i]['value'] = $data['questions'][$pagenum][$i]['choices'][0]['value'];
- }
- $i++;
- }
+ // Any notifications will be displayed on top of main page, and prevent questionnaire from being completed. This also checks
+ // appropriate capabilities.
+ $data['notifications'] = $questionnaire->user_access_messages($userid);
+ $responses = [];
+ $result = '';
+
+ $data['emptypage'] = 1;
+ $template = "mod_questionnaire/local/mobile/$versionname/main_index_page";
+
+ switch ($action) {
+ case 'index':
+ self::add_index_data($questionnaire, $data, $userid);
+ $template = "mod_questionnaire/local/mobile/$versionname/main_index_page";
+ break;
+
+ case 'submit':
+ case 'nextpage':
+ case 'previouspage':
+ if (!$data['notifications']) {
+ $result = $questionnaire->save_mobile_data($userid, $pagenum, $completed, $rid, $submit, $action, (array)$args);
}
- if (isset($data['questions'][$pagenum]) && !empty($data['questions'][$pagenum])) {
- $i = 0;
- foreach ($data['questions'][$pagenum] as $arr) {
- $data['pagequestions'][$i] = $arr;
- $i++;
+
+ case 'respond':
+ case 'resume':
+ // Completing a questionnaire.
+ if (!$data['notifications']) {
+ if ($questionnaire->user_has_saved_response($userid)) {
+ if (empty($rid)) {
+ $rid = $questionnaire->get_latest_responseid($userid);
+ }
+ $questionnaire->add_response($rid);
+ $data['rid'] = $rid;
}
- }
- if (isset($questionnaire['questions'][$pagenum + 1]) && !empty($questionnaire['questions'][$pagenum + 1])) {
- $data['nextpage'] = $pagenum + 1;
- }
- if ($prevpage) {
- $data['prevpage'] = $prevpage;
- }
- } else if (isset($questionnaire['questions'][$pagenum]) && $branching == true
- && $questionnaire['completed'] == false ) {
- $i = 0;
- foreach ($questionnaire['questions'][$pagenum] as $questionid => $choices) {
- if (isset($questionnaire['questionsinfo'][$pagenum][$questionid]) &&
- !empty($questionnaire['questionsinfo'][$pagenum][$questionid])) {
- $data['questions'][$pagenum][$i]['info'] = $questionnaire['questionsinfo'][$pagenum][$questionid];
- if ($data['questions'][$pagenum][$i]['info']['required'] == 'n') {
- unset($data['questions'][$pagenum][$i]['info']['required']);
+ $response = (isset($questionnaire->responses) && !empty($questionnaire->responses)) ?
+ end($questionnaire->responses) : \mod_questionnaire\responsetype\response\response::create_from_data([]);
+ $response->sec = $pagenum;
+ if (isset($result['warnings'])) {
+ if ($action == 'submit') {
+ $response = $result['response'];
}
- $ii = 0;
- foreach ($choices as $k => $v) {
- $data['questions'][$pagenum][$i]['choices'][$ii] = (array) $v;
- $ii++;
+ $data['notifications'] = $result['warnings'];
+ } else if ($action == 'nextpage') {
+ $pageresult = $result['nextpagenum'];
+ if ($pageresult === false) {
+ $pagenum = count($questionnaire->questionsbysec);
+ } else if (is_string($pageresult)) {
+ $data['notifications'] .= !empty($data['notifications']) ? "\n $pageresult" : $pageresult;
+ } else {
+ $pagenum = $pageresult;
}
- if (count($choices) == 1) {
- $data['questions'][$pagenum][$i]['value'] = $data['questions'][$pagenum][$i]['choices'][0]['value'];
+ } else if ($action == 'previouspage') {
+ $prevpage = $result['nextpagenum'];
+ if ($prevpage === false) {
+ $pagenum = 1;
+ } else {
+ $pagenum = $prevpage;
}
- $i++;
- }
- }
- if (isset($data['questions'][$pagenum]) && !empty($data['questions'][$pagenum])) {
- $i = 0;
- foreach ($data['questions'][$pagenum] as $arr) {
- $data['pagequestions'][$i] = $arr;
- $i++;
+ } else if ($action == 'submit') {
+ self::add_index_data($questionnaire, $data, $userid);
+ $data['action'] = 'index';
+ $template = "mod_questionnaire/local/mobile/$versionname/main_index_page";
+ break;
}
- }
- if (isset($questionnaire['questions'][$pagenum + 1]) && !empty($questionnaire['questions'][$pagenum + 1])) {
- $data['nextpage'] = $pagenum + 1;
- }
- if ($prevpage) {
- $data['prevpage'] = $prevpage;
- }
- }
-
- if ($questionnaire['completed'] == true) {
- // Branching specific logic.
- // If we are branching and the questionnaire is complete, display all the responses on one page.
- $pagecounter = 1;
- foreach ($questionnaire['questions'] as $question) {
- $i = 0;
- foreach ($questionnaire['questions'][$pagecounter] as $questionid => $choices) {
- if (isset($questionnaire['questionsinfo'][$pagecounter][$questionid]) &&
- !empty($questionnaire['questionsinfo'][$pagecounter][$questionid])) {
- $data['questions'][$pagecounter][$i]['info']
- = $questionnaire['questionsinfo'][$pagecounter][$questionid];
- if ($data['questions'][$pagecounter][$i]['info']['required'] == 'n') {
- unset($data['questions'][$pagecounter][$i]['info']['required']);
- }
- $ii = 0;
- foreach ($choices as $k => $v) {
- $data['questions'][$pagecounter][$i]['choices'][$ii] = (array) $v;
- $ii++;
- }
- if (count($choices) == 1) {
- $data['questions'][$pagecounter][$i]['value']
- = $data['questions'][$pagecounter][$i]['choices'][0]['value'];
- }
- $i++;
+ $pagequestiondata = self::add_pagequestion_data($questionnaire, $pagenum, $response);
+ $data['pagequestions'] = $pagequestiondata['pagequestions'];
+ $responses = $pagequestiondata['responses'];
+ $numpages = count($questionnaire->questionsbysec);
+ // Set some variables we are going to be using.
+ if (!empty($questionnaire->questionsbysec) && ($numpages > 1)) {
+ if ($pagenum > 1) {
+ $data['prevpage'] = true;
}
- if ($pagecounter > count($questionnaire['questions'])) {
- break;
+ if ($pagenum < $numpages) {
+ $data['nextpage'] = true;
}
}
- $pagecounter++;
- $x = 0;
- $questioncounter = 1;
- foreach ($data['questions'] as $dataq) {
- foreach ($dataq as $arr) {
- $data['pagequestions'][$x] = $arr;
- $x++;
- if ($questioncounter >= count($questionnaire['questions'])) {
- break;
- }
- }
- $questioncounter++;
- }
+ $data['pagenum'] = $pagenum;
+ $data['completed'] = 0;
+ $data['emptypage'] = 0;
+ $template = "mod_questionnaire/local/mobile/$versionname/view_activity_page";
}
+ break;
- $data['prevpage'] = 0;
- $data['nextpage'] = 0;
- $pagebreaks = false;
- }
- } else {
- $data['emptypage'] = true;
- $data['emptypage_content'] = get_string('questionnaire:submit', 'questionnaire');
- }
- // Let each pagequestions know it's current required step, and fill up the final required step.
- // Logic states that we get all the required steps and give them an counter.
- // We get the final required count and check it againts the input once it's sent to a js file.
- // If its the final required count we display the button.
- $currentrequiredresponse = 0;
- $counter = 0;
- $multichoiceflag = false;
- $completedchoices = 0;
- $finalpagerequired = false;
- $completeddisabledflag = false;
- foreach ($data['pagequestions'] as &$pagequestion) {
- if ($pagequestion['info']['required'] == 'y') {
- if (!empty($pagequestion['choices']) && $pagequestion['info']['response_table'] == 'response_rank') {
- foreach ($pagequestion['choices'] as &$choice) {
- if (empty($choice['value'])) {
- if ($currentrequiredresponse > 0 && empty($counter)) {
- $counter = $currentrequiredresponse;
+ case 'review':
+ // If reviewing a submission.
+ if ($questionnaire->capabilities->readownresponses && isset($args->submissionid) && !empty($args->submissionid)) {
+ $questionnaire->add_response($args->submissionid);
+ $response = $questionnaire->responses[$args->submissionid];
+ $qnum = 1;
+ $pagequestions = [];
+ foreach ($questionnaire->questions as $question) {
+ if ($question->supports_mobile()) {
+ $pagequestions[] = $question->mobile_question_display($qnum, $questionnaire->autonum);
+ $responses = array_merge($responses, $question->get_mobile_response_data($response));
+ if ($question->is_numbered()) {
+ $qnum++;
}
- $counter++;
- $choice['current_required_resp'] = $counter;
- } else {
- $completedchoices++;
- $completeddisabledflag = true;
}
}
- $currentrequiredresponse = $counter;
- } else {
- $currentrequiredresponse++;
- $pagequestion['info']['current_required_resp'] = $currentrequiredresponse;
+ $data['prevpage'] = 0;
+ $data['nextpage'] = 0;
+ $data['pagequestions'] = $pagequestions;
+ $data['completed'] = 1;
+ $data['emptypage'] = 0;
+ $template = "mod_questionnaire/local/mobile/$versionname/view_activity_page";
}
- if ($pagequestion['info']['qnum'] === count($data['pagequestions'])) {
- $finalpagerequired = true;
- }
- }
- }
-
- $disablesavebutton = true;
- if ($completedchoices == $currentrequiredresponse && !$finalpagerequired) {
- $disablesavebutton = false;
- } else if ($completeddisabledflag) {
- $disablesavebutton = false;
- } else {
- $disablesavebutton = true;
+ break;
}
- // Let each pagequestions know what the final required field is.
- foreach ($data['pagequestions'] as &$pagequestion) {
- $pagequestion['info']['final_required_resp'] = $currentrequiredresponse - $completedchoices;
- }
-
- $mobileviewactivity = 'mod_questionnaire/mobile_view_activity_page';
- if ($branching) {
- $mobileviewactivity = 'mod_questionnaire/mobile_view_activity_branching_page';
- }
+ $data['hasmorepages'] = $data['prevpage'] || $data['nextpage'];
- $data['pagebreak'] = true;
-
- return [
+ $return = [
'templates' => [
[
'id' => 'main',
- 'html' => $OUTPUT->render_from_template($mobileviewactivity, $data)
+ 'html' => $OUTPUT->render_from_template($template, $data)
],
],
- 'javascript' => file_get_contents($CFG->dirroot . '/mod/questionnaire/javascript/mobile_questionnaire.js'),
- 'otherdata' => [
- 'fields' => json_encode($questionnaire['fields']),
- 'questionsinfo' => json_encode($questionnaire['questionsinfo']),
- 'questions' => json_encode($questionnaire['questions']),
- 'pagequestions' => json_encode($data['pagequestions']),
- 'responses' => json_encode($questionnaire['responses']),
- 'pagenum' => $pagenum,
- 'nextpage' => $data['nextpage'],
- 'prevpage' => $data['prevpage'],
- 'completed' => $data['completed'],
- 'intro' => $questionnaire['questionnaire']['intro'],
- 'string_required' => get_string('required'),
- 'string_dropdown' => get_string('selectdropdowntext', 'mod_questionnaire'),
- 'disable_save' => $disablesavebutton,
- ],
+ 'javascript' => file_get_contents($CFG->dirroot . '/mod/questionnaire/appjs/uncheckother.js'),
+ 'otherdata' => $responses,
'files' => null
];
+ return $return;
}
/**
@@ -302,169 +205,74 @@ protected static function require_capability(\stdClass $cm, \context $context, s
require_capability($cap, $context);
}
- public static function mobile_view_activity_branching($args) {
- global $OUTPUT, $USER, $CFG, $DB, $SESSION;
-
- require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php');
- require_once($CFG->dirroot . '/mod/questionnaire/lib.php');
- $args = (object) $args;
- $cmid = $args->cmid;
- $pagenum = (isset($args->pagenum) && !empty($args->pagenum)) ? intval($args->pagenum) : 1;
- $prevpage = 0;
- if (!empty($SESSION->prevpage)) {
- $prevpage = $SESSION->prevpage;
- if (!$prevpage) {
- $SESSION->prevpage = $pagenum;
- if ($pagenum == 1) {
- $prevpage = 0;
- } else {
- $prevpage = $pagenum;
- }
+ /**
+ * Add the submissions.
+ * @param \questionnaire $questionnaire
+ * @param array $data
+ * @param int $userid
+ */
+ protected static function add_index_data($questionnaire, &$data, $userid) {
+ // List any existing submissions, if user is allowed to review them.
+ if ($questionnaire->capabilities->readownresponses) {
+ $questionnaire->add_user_responses();
+ $submissions = [];
+ foreach ($questionnaire->responses as $response) {
+ $submissions[] = ['submissiondate' => userdate($response->submitted), 'submissionid' => $response->id];
}
+ if (!empty($submissions)) {
+ $data['submissions'] = $submissions;
+ } else {
+ $data['emptypage'] = 1;
+ }
+ if ($questionnaire->user_has_saved_response($userid)) {
+ $data['resume'] = 1;
+ }
+ $data['emptypage'] = 0;
}
- $quesitonnaireresponses = (!empty($args->responses)) ? $args->responses : [];
- $branching = (isset($args->branching) && !empty($args->branching)) ? intval($args->branching) : 0;
- // Capabilities check.
- $cm = get_coursemodule_from_id('questionnaire', $cmid);
- $context = \context_module::instance($cmid);
- self::require_capability($cm, $context, 'mod/questionnaire:view');
- // Set some variables we are going to be using.
- $questionnaire = get_questionnaire_data($cmid, $USER->id);
- if (isset($questionnaire['questions'][$pagenum - 1]) && !empty($questionnaire['questions'][$pagenum - 1])) {
- $prevpage = $pagenum - 1;
- }
+ }
- $branching = check_mobile_branching_logic($questionnaire);
- $pagenum = get_mobile_questionnaire($questionnaire, $pagenum, $branching);
- $newpagenum = $pagenum['pagenum'];
- $newprevpagenum = $prevpage;
- $newnextpagenum = $pagenum['nextpage'];
- $pagenum = $newpagenum;
+ /**
+ * Ass the questions for the page.
+ * @param \questionnaire $questionnaire
+ * @param int $pagenum
+ * @param response $response
+ * @return array
+ */
+ protected static function add_pagequestion_data($questionnaire, $pagenum, $response=null) {
+ $qnum = 1;
+ $pagequestions = [];
+ $responses = [];
- $data = [
- 'questionnaire' => $questionnaire,
- 'cmid' => $cmid,
- 'courseid' => intval($cm->course),
- 'pagenum' => $pagenum,
- 'userid' => $USER->id,
- 'nextpage' => 0,
- 'prevpage' => 0,
- 'emptypage' => false
- ];
- $data['completed'] = (isset($questionnaire['response']['complete'])
- && $questionnaire['response']['complete'] == 'y') ? 1 : 0;
- $data['complete_userdate'] = (isset($questionnaire['response']['complete'])
- && $questionnaire['response']['complete'] == 'y') ?
- userdate($questionnaire['response']['submitted']) : '';
- if (isset($questionnaire['questions'][$pagenum])) {
- $i = 0;
- foreach ($questionnaire['questions'][$pagenum] as $questionid => $choices) {
- if (isset($questionnaire['questionsinfo'][$pagenum][$questionid])
- && !empty($questionnaire['questionsinfo'][$pagenum][$questionid])) {
- $data['questions'][$pagenum][$i]['info'] = $questionnaire['questionsinfo'][$pagenum][$questionid];
- if ($data['questions'][$pagenum][$i]['info']['required'] == 'n') {
- unset($data['questions'][$pagenum][$i]['info']['required']);
- }
- $ii = 0;
- foreach ($choices as $k => $v) {
- $data['questions'][$pagenum][$i]['choices'][$ii] = (array) $v;
- $ii++;
- }
- if (count($choices) == 1) {
- $data['questions'][$pagenum][$i]['value'] = $data['questions'][$pagenum][$i]['choices'][0]['value'];
+ // Find out what question number we are on $i New fix for question numbering.
+ $i = 0;
+ if ($pagenum > 1) {
+ for ($j = 2; $j <= $pagenum; $j++) {
+ foreach ($questionnaire->questionsbysec[$j - 1] as $questionid) {
+ if ($questionnaire->questions[$questionid]->type_id < QUESPAGEBREAK) {
+ $i++;
}
- $i++;
}
}
- if (isset($data['questions'][$pagenum]) && !empty($data['questions'][$pagenum])) {
- $i = 0;
- foreach ($data['questions'][$pagenum] as $arr) {
- $data['pagequestions'][$i] = $arr;
- $i++;
- }
- }
- if (isset($questionnaire['questions'][$pagenum + 1]) && !empty($questionnaire['questions'][$pagenum + 1])) {
- $data['nextpage'] = $pagenum + 1;
- }
- if ($prevpage) {
- $data['prevpage'] = $prevpage;
- }
}
- // Let each pagequestions know it's current required step, and fill up the final required step.
- // Logic states that we get all the required steps and give them an counter.
- // We get the final required count and check it againts the input once it's sent to a js file.
- // If its the final required count we display the button.
- $currentrequiredresponse = 0;
- $counter = 0;
- $multichoiceflag = false;
- $completedchoices = 0;
- $finalpagerequired = false;
- $completeddisabledflag = false;
- foreach ($data['pagequestions'] as &$pagequestion) {
- if ($pagequestion['info']['required'] == 'y') {
- if (!empty($pagequestion['choices']) && $pagequestion['info']['response_table'] == 'response_rank') {
- foreach ($pagequestion['choices'] as &$choice) {
- if (empty($choice['value'])) {
- if ($currentrequiredresponse > 0 && empty($counter)) {
- $counter = $currentrequiredresponse;
- }
- $counter++;
- $choice['current_required_resp'] = $counter;
- } else {
- $completedchoices++;
- $completeddisabledflag = true;
- }
- }
- $currentrequiredresponse = $counter;
- } else {
- $currentrequiredresponse++;
- $pagequestion['info']['current_required_resp'] = $currentrequiredresponse;
+ $qnum = $i + 1;
+
+ foreach ($questionnaire->questionsbysec[$pagenum] as $questionid) {
+ $question = $questionnaire->questions[$questionid];
+ if ($question->supports_mobile()) {
+ $pagequestions[] = $question->mobile_question_display($qnum, $questionnaire->autonum);
+ $mobileotherdata = $question->mobile_otherdata();
+ if (!empty($mobileotherdata)) {
+ $responses = array_merge($responses, $mobileotherdata);
}
- if ($pagequestion['info']['qnum'] === count($data['pagequestions'])) {
- $finalpagerequired = true;
+ if (($response !== null) && isset($response->answers[$questionid])) {
+ $responses = array_merge($responses, $question->get_mobile_response_data($response));
+ }
+ if ($question->is_numbered()) {
+ $qnum++;
}
}
}
- $disablesavebutton = true;
- if ($completedchoices == $currentrequiredresponse && !$finalpagerequired) {
- $disablesavebutton = false;
- } else if ($completeddisabledflag) {
- $disablesavebutton = false;
- } else {
- $disablesavebutton = true;
- }
-
- // Let each pagequestions know what the final required field is.
- foreach ($data['pagequestions'] as &$pagequestion) {
- $pagequestion['info']['final_required_resp'] = $currentrequiredresponse;
- }
- $data['pagebreak'] = true; // Branching logic is always true.
-
- return [
- 'templates' => [
- [
- 'id' => 'main',
- 'html' => $OUTPUT->render_from_template('mod_questionnaire/mobile_view_activity_branching_page', $data)
- ],
- ],
- 'javascript' => file_get_contents($CFG->dirroot . '/mod/questionnaire/javascript/mobile_questionnaire.js'),
- 'otherdata' => [
- 'fields' => json_encode($questionnaire['fields']),
- 'questionsinfo' => json_encode($questionnaire['questionsinfo']),
- 'questions' => json_encode($questionnaire['questions']),
- 'pagequestions' => json_encode($data['pagequestions']),
- 'responses' => json_encode($questionnaire['responses']),
- 'pagenum' => $pagenum,
- 'nextpage' => $newnextpagenum,
- 'prevpage' => $newprevpagenum,
- 'completed' => $data['completed'],
- 'intro' => $questionnaire['questionnaire']['intro'],
- 'string_required' => get_string('required'),
- 'string_dropdown' => get_string('selectdropdowntext', 'mod_questionnaire'),
- 'disable_save' => $disablesavebutton,
- ],
- 'files' => null
- ];
+ return ['pagequestions' => $pagequestions, 'responses' => $responses];
}
-}
\ No newline at end of file
+}
diff --git a/classes/output/nonrespondentspage.php b/classes/output/nonrespondentspage.php
index 2f8b6312..4771ebf0 100644
--- a/classes/output/nonrespondentspage.php
+++ b/classes/output/nonrespondentspage.php
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\output;
+
/**
* Contains class mod_questionnaire\output\viewpage
*
@@ -22,11 +24,6 @@
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-namespace mod_questionnaire\output;
-
-defined('MOODLE_INTERNAL') || die();
-
class nonrespondentspage implements \renderable, \templatable {
/**
@@ -49,8 +46,8 @@ public function __construct($data = null) {
/**
* Add data for export.
- * @param string The index for the data.
- * @param string The content for the index.
+ * @param string $element The index for the data.
+ * @param string $content The content for the index.
*/
public function add_to_page($element, $content) {
$this->data->{$element} = empty($this->data->{$element}) ? $content : ($this->data->{$element} . $content);
@@ -64,4 +61,4 @@ public function export_for_template(\renderer_base $output) {
return $this->data;
}
-}
\ No newline at end of file
+}
diff --git a/classes/output/previewpage.php b/classes/output/previewpage.php
index af2c5ba9..82267de3 100644
--- a/classes/output/previewpage.php
+++ b/classes/output/previewpage.php
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\output;
+
/**
* Contains class mod_questionnaire\output\viewpage
*
@@ -22,11 +24,6 @@
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-namespace mod_questionnaire\output;
-
-defined('MOODLE_INTERNAL') || die();
-
class previewpage implements \renderable, \templatable {
/**
@@ -49,8 +46,8 @@ public function __construct($data = null) {
/**
* Add data for export.
- * @param string The index for the data.
- * @param string The content for the index.
+ * @param string $element The index for the data.
+ * @param string $content The content for the index.
*/
public function add_to_page($element, $content) {
if ($element === 'questions') {
@@ -68,4 +65,4 @@ public function export_for_template(\renderer_base $output) {
return $this->data;
}
-}
\ No newline at end of file
+}
diff --git a/classes/output/qsettingspage.php b/classes/output/qsettingspage.php
index 7b4be1ad..311a18d1 100644
--- a/classes/output/qsettingspage.php
+++ b/classes/output/qsettingspage.php
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\output;
+
/**
* Contains class mod_questionnaire\output\viewpage
*
@@ -22,11 +24,6 @@
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-namespace mod_questionnaire\output;
-
-defined('MOODLE_INTERNAL') || die();
-
class qsettingspage implements \renderable, \templatable {
/**
@@ -49,8 +46,8 @@ public function __construct($data = null) {
/**
* Add data for export.
- * @param string The index for the data.
- * @param string The content for the index.
+ * @param string $element The index for the data.
+ * @param string $content The content for the index.
*/
public function add_to_page($element, $content) {
$this->data->{$element} = empty($this->data->{$element}) ? $content : ($this->data->{$element} . $content);
@@ -64,4 +61,4 @@ public function export_for_template(\renderer_base $output) {
return $this->data;
}
-}
\ No newline at end of file
+}
diff --git a/classes/output/questionspage.php b/classes/output/questionspage.php
index 2624687c..09ea8dd7 100644
--- a/classes/output/questionspage.php
+++ b/classes/output/questionspage.php
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\output;
+
/**
* Contains class mod_questionnaire\output\viewpage
*
@@ -22,11 +24,6 @@
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-namespace mod_questionnaire\output;
-
-defined('MOODLE_INTERNAL') || die();
-
class questionspage implements \renderable, \templatable {
/**
@@ -49,8 +46,8 @@ public function __construct($data = null) {
/**
* Add data for export.
- * @param string The index for the data.
- * @param string The content for the index.
+ * @param string $element The index for the data.
+ * @param string $content The content for the index.
*/
public function add_to_page($element, $content) {
$this->data->{$element} = empty($this->data->{$element}) ? $content : ($this->data->{$element} . $content);
@@ -63,4 +60,4 @@ public function add_to_page($element, $content) {
public function export_for_template(\renderer_base $output) {
return $this->data;
}
-}
\ No newline at end of file
+}
diff --git a/classes/output/renderer.php b/classes/output/renderer.php
old mode 100644
new mode 100755
index 1dbbc99c..456370e3
--- a/classes/output/renderer.php
+++ b/classes/output/renderer.php
@@ -14,6 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\output;
+
+use mod_questionnaire\question\question;
+
/**
* Contains class mod_questionnaire\output\renderer
*
@@ -22,11 +26,6 @@
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-namespace mod_questionnaire\output;
-
-defined('MOODLE_INTERNAL') || die();
-
class renderer extends \plugin_renderer_base {
/**
* Main view page.
@@ -58,6 +57,16 @@ public function render_reportpage($page) {
return $this->render_from_template('mod_questionnaire/reportpage', $data);
}
+ /**
+ * Fill out the PDF report page.
+ * @param \templateable $page
+ * @return string | boolean
+ */
+ public function render_reportpagepdf($page) {
+ $data = $page->export_for_template($this);
+ return $this->render_from_template('mod_questionnaire/reportpagepdf', $data);
+ }
+
/**
* Fill out the qsettings page.
* @param \templateable $page
@@ -138,6 +147,7 @@ public function complete_formstart($action, $hiddeninputs=[]) {
foreach ($hiddeninputs as $name => $value) {
$output .= \html_writer::empty_tag('input', ['type' => 'hidden', 'name' => $name, 'value' => $value]) . "\n";
}
+ $this->page->requires->js_init_call('M.mod_questionnaire.init_attempt_form', null, false, questionnaire_get_js_module());
return $output;
}
@@ -157,14 +167,14 @@ public function complete_formend($inputs=[]) {
/**
* Render the completion form control buttons.
- * @param array | string $inputs Name/(Type/attribute) array of input types and values used by the form.
+ * @param array|string $inputs Name/(Type/attribute) array of input types and values used by the form.
* @return string The output for the page.
*/
public function complete_controlbuttons($inputs=null) {
$output = '';
if (is_array($inputs)) {
foreach ($inputs as $name => $attributes) {
- $output .= \html_writer::empty_tag('input', array_merge(['name' => $name], $attributes));
+ $output .= \html_writer::empty_tag('input', array_merge(['name' => $name], $attributes)) . ' ';
}
} else if (is_string($inputs)) {
$output .= \html_writer::tag('p', $inputs);
@@ -172,18 +182,51 @@ public function complete_controlbuttons($inputs=null) {
return $output;
}
+ /**
+ * Calculate the progress and return it.
+ * @param int $section
+ * @param array $questionsbysec
+ * @return float
+ */
+ private function calculate_progress($section, $questionsbysec) {
+ $done = 0;
+ $todo = 0;
+ for ($i = 1; $i <= count($questionsbysec); $i++) {
+ if ($i < $section) {
+ $done += count($questionsbysec[$i]);
+ } else {
+ $todo += count($questionsbysec[$i]);
+ }
+ }
+
+ return round($done / ($done + $todo) * 100);
+ }
+
+ /**
+ * Render the progress bar and return it.
+ * @param int $section
+ * @param array $questionsbysec
+ * @return bool|string
+ */
+ public function render_progress_bar($section, $questionsbysec) {
+ $templatecontext['percent'] = $this->calculate_progress($section, $questionsbysec);
+ $helpicon = new \help_icon('progresshelp', 'mod_questionnaire');
+ $templatecontext['progresshelp'] = $helpicon->export_for_template($this);
+ return $this->render_from_template('mod_questionnaire/progressbar', $templatecontext);
+ }
+
/**
* Render a question for a survey.
- * @param mod_questionnaire\question\base $question The question object.
- * @param array $formdata Any returned form data.
- * @param array $dependants Array of all questions/choices depending on $question.
+ * @param \mod_questionnaire\question\question $question The question object.
+ * @param \mod_questionnaire\responsetype\response\response $response Any current response data.
* @param int $qnum The question number.
* @param boolean $blankquestionnaire Used for printing a blank one.
+ * @param array $dependants Array of all questions/choices depending on $question.
* @return string The output for the page.
*/
- public function question_output($question, $formdata, $dependants=[], $qnum, $blankquestionnaire) {
+ public function question_output($question, $response, $qnum, $blankquestionnaire, $dependants=[]) {
- $pagetags = $question->question_output($formdata, $dependants, $qnum, $blankquestionnaire);
+ $pagetags = $question->question_output($response, $blankquestionnaire, $dependants, $qnum);
// If the question has a template, then render it from the 'qformelement' context. If no template, then 'qformelement'
// already contains HTML.
@@ -203,17 +246,22 @@ public function question_output($question, $formdata, $dependants=[], $qnum, $bl
/**
* Render a question response.
- * @param mod_questionnaire\question\base $question The question object.
- * @param stdClass $data All of the response data.
+ * @param \mod_questionnaire\question\question $question The question object.
+ * @param \mod_questionnaire\responsetype\response\response $response The response object.
* @param int $qnum The question number.
+ * @param bool $pdf
* @return string The output for the page.
+ * @throws \moodle_exception
*/
- public function response_output($question, $data, $qnum=null) {
- $pagetags = $question->response_output($data, $qnum);
+ public function response_output($question, $response, $qnum=null, $pdf=false) {
+ $pagetags = $question->response_output($response, $qnum);
// If the response has a template, then render it from the 'qformelement' context. If no template, then 'qformelement'
// already contains HTML.
if (($template = $question->response_template())) {
+ if ($pdf) {
+ $pagetags->qformelement->pdf = 1;
+ }
$pagetags->qformelement = $this->render_from_template($template, $pagetags->qformelement);
}
@@ -223,34 +271,40 @@ public function response_output($question, $data, $qnum=null) {
$pagetags->notifications = $this->notification($notification, \core\output\notification::NOTIFY_ERROR);
}
}
- return $this->render_from_template('mod_questionnaire/question_container', $pagetags);
+ if (!$pdf) {
+ return $this->render_from_template('mod_questionnaire/question_container', $pagetags);
+ } else {
+ return $this->render_from_template('mod_questionnaire/questionpdf_container', $pagetags);
+ }
}
/**
* Render all responses for a question.
- * @param stdClass $data All of the response data.
+ * @param array|string $responses
+ * @param array $questions
* @return string The output for the page.
*/
- public function all_response_output($data=null) {
+ public function all_response_output($responses, $questions = null) {
$output = '';
- if (is_string($data)) {
- $output .= $data;
+ if (is_string($responses)) {
+ $output .= $responses;
} else {
- foreach ($data as $qnum => $responses) {
- $question = $responses['question'];
- $pagetags = $question->questionstart_survey_display($qnum);
- foreach ($responses as $item => $response) {
- if ($item !== 'question') {
- $resptags = $question->response_output($response['respdata']);
- // If the response has a template, then render it from the 'qformelement' context.
- // If no template, then 'qformelement' already contains HTML.
- if (($template = $question->response_template())) {
- $resptags->qformelement = $this->render_from_template($template, $resptags->qformelement);
- }
- $resptags->respdate = $response['respdate'];
- $pagetags->responses[] = $resptags;
+ $qnum = 1;
+ foreach ($questions as $question) {
+ if (empty($pagetags = $question->questionstart_survey_display($qnum))) {
+ continue;
+ }
+ foreach ($responses as $response) {
+ $resptags = $question->response_output($response);
+ // If the response has a template, then render it from the 'qformelement' context.
+ // If no template, then 'qformelement' already contains HTML.
+ if (($template = $question->response_template())) {
+ $resptags->qformelement = $this->render_from_template($template, $resptags->qformelement);
}
+ $resptags->respdate = userdate($response->submitted);
+ $pagetags->responses[] = $resptags;
}
+ $qnum++;
$output .= $this->render_from_template('mod_questionnaire/response_container', $pagetags);
}
}
@@ -259,17 +313,18 @@ public function all_response_output($data=null) {
/**
* Render a question results summary.
- * @param mod_questionnaire\question\base $question The question object.
+ * @param question $question The question object.
* @param array $rids The response ids.
* @param string $sort The sort order being used.
* @param string $anonymous The value of the anonymous setting.
+ * @param bool $pdf
* @return string The output for the page.
*/
- public function results_output($question, $rids, $sort, $anonymous) {
+ public function results_output($question, $rids, $sort, $anonymous, $pdf = false) {
$pagetags = $question->display_results($rids, $sort, $anonymous);
// If the response has a template, then render it from $pagetags. If no template, then $pagetags already contains HTML.
- if (($template = $question->results_template())) {
+ if (($template = $question->results_template($pdf))) {
return $this->render_from_template($template, $pagetags);
} else {
return $pagetags;
@@ -322,9 +377,10 @@ public function print_preview_pagenumber($content) {
public function print_preview_formend($url, $submitstr, $resetstr) {
$output = '';
$output .= \html_writer::start_tag('div');
- $output .= \html_writer::empty_tag('input', ['type' => 'submit', 'name' => 'submit', 'value' => $submitstr]);
+ $output .= \html_writer::empty_tag('input', ['type' => 'submit', 'name' => 'submit', 'value' => $submitstr,
+ 'class' => 'btn btn-primary']);
$output .= ' ';
- $output .= \html_writer::tag('a', $resetstr, ['href' => $url]);
+ $output .= \html_writer::tag('a', $resetstr, ['href' => $url, 'class' => 'btn btn-secondary mr-1']);
$output .= \html_writer::end_tag('div') . "\n";
$output .= \html_writer::end_tag('form') . "\n";
return $output;
@@ -339,16 +395,17 @@ public function print_preview_formend($url, $submitstr, $resetstr) {
public function homelink($url, $text) {
$output = '';
$output .= \html_writer::start_tag('div', ['class' => 'homelink']);
- $output .= \html_writer::tag('a', $text, ['href' => $url]);
+ $output .= \html_writer::tag('a', $text, ['href' => $url, 'class' => 'btn btn-primary']);
$output .= \html_writer::end_tag('div');
return $output;
}
/**
- * @param $children
- * @param $langstring
- * @param $strnum
+ * Generate and return any dependency warnings.
+ * @param array $children
+ * @param string $langstring
+ * @param int $strnum
* @return string
*/
public function dependency_warnings($children, $langstring, $strnum) {
@@ -408,8 +465,8 @@ public function dependency_warnings($children, $langstring, $strnum) {
/**
* Get displayable list of parents for the question in questions_form.
- * @param $qid The question id.
- * @param $dependencies Array of dependency records for a question.
+ * @param int $qid The question id.
+ * @param array $dependencies Array of dependency records for a question.
* @return string
*/
public function get_dependency_html($qid, $dependencies) {
@@ -461,4 +518,50 @@ public function flexible_table(\flexible_table $table, $buffering = false) {
return $o;
}
-}
\ No newline at end of file
+
+ /**
+ * Returns a dataformat selection and download form
+ *
+ * @param string $label A text label
+ * @param moodle_url|string $base The download page url
+ * @param string $name The query param which will hold the type of the download
+ * @param array $params Extra params sent to the download page
+ * @param string $extrafields HTML for extra form fields
+ * @return string HTML fragment
+ */
+ public function download_dataformat_selector($label, $base, $name = 'dataformat', $params = array(), $extrafields = '') {
+
+ $formats = \core_plugin_manager::instance()->get_plugins_of_type('dataformat');
+ $options = array();
+ foreach ($formats as $format) {
+ if ($format->is_enabled()) {
+ $options[] = array(
+ 'value' => $format->name,
+ 'label' => get_string('dataformat', $format->component),
+ );
+ }
+ }
+ $hiddenparams = array();
+ foreach ($params as $key => $value) {
+ $hiddenparams[] = array(
+ 'name' => $key,
+ 'value' => $value,
+ );
+ }
+ $data = array(
+ 'label' => $label,
+ 'base' => $base,
+ 'name' => $name,
+ 'params' => $hiddenparams,
+ 'options' => $options,
+ 'extrafields' => $extrafields,
+ 'sesskey' => sesskey(),
+ 'submit' => get_string('download'),
+ 'emailroleshelp' => $this->help_icon('emailroles', 'questionnaire'),
+ 'emailextrahelp' => $this->help_icon('emailextra', 'questionnaire'),
+ 'allowemailreporting' => get_config('questionnaire', 'allowemailreporting'),
+ );
+
+ return $this->render_from_template('mod_questionnaire/dataformat_selector', $data);
+ }
+}
diff --git a/classes/output/reportpage.php b/classes/output/reportpage.php
index 0d7c52b9..48a218a8 100644
--- a/classes/output/reportpage.php
+++ b/classes/output/reportpage.php
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\output;
+
/**
* Contains class mod_questionnaire\output\viewpage
*
@@ -22,11 +24,6 @@
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-namespace mod_questionnaire\output;
-
-defined('MOODLE_INTERNAL') || die();
-
class reportpage implements \renderable, \templatable {
/**
@@ -49,8 +46,8 @@ public function __construct($data = null) {
/**
* Add data for export.
- * @param string The index for the data.
- * @param string The content for the index.
+ * @param string $element The index for the data.
+ * @param string $content The content for the index.
*/
public function add_to_page($element, $content) {
if ($element === 'responses') {
@@ -68,4 +65,4 @@ public function export_for_template(\renderer_base $output) {
return $this->data;
}
-}
\ No newline at end of file
+}
diff --git a/classes/output/reportpagepdf.php b/classes/output/reportpagepdf.php
new file mode 100644
index 00000000..9e74f6cf
--- /dev/null
+++ b/classes/output/reportpagepdf.php
@@ -0,0 +1,68 @@
+.
+
+namespace mod_questionnaire\output;
+
+/**
+ * Contains class mod_questionnaire\output\reportpagepdf
+ *
+ * @package mod_questionnaire
+ * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class reportpagepdf implements \renderable, \templatable {
+
+ /**
+ * The data to be exported.
+ * @var array
+ */
+ protected $data;
+
+ /**
+ * Construct the renderable.
+ * @param object $data The template data for export.
+ */
+ public function __construct($data = null) {
+ if ($data !== null) {
+ $this->data = $data;
+ } else {
+ $this->data = new \stdClass();
+ }
+ }
+
+ /**
+ * Add data for export.
+ * @param string $element The index for the data.
+ * @param string $content The content for the index.
+ */
+ public function add_to_page($element, $content) {
+ if ($element === 'responses') {
+ $this->data->{$element}[] = $content;
+ } else {
+ $this->data->{$element} = empty($this->data->{$element}) ? $content : ($this->data->{$element} . $content);
+ }
+ }
+
+ /**
+ * Export the data for template.
+ * @param \renderer_base $output
+ */
+ public function export_for_template(\renderer_base $output) {
+ return $this->data;
+ }
+
+}
diff --git a/classes/output/responsepagepdf.php b/classes/output/responsepagepdf.php
new file mode 100644
index 00000000..ce7cbfab
--- /dev/null
+++ b/classes/output/responsepagepdf.php
@@ -0,0 +1,68 @@
+.
+
+namespace mod_questionnaire\output;
+
+/**
+ * Contains class mod_questionnaire\output\responsepagepdf
+ *
+ * @package mod_questionnaire
+ * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class responsepagepdf implements \renderable, \templatable {
+
+ /**
+ * The data to be exported.
+ * @var array
+ */
+ protected $data;
+
+ /**
+ * Construct the renderable.
+ * @param object $data The template data for export.
+ */
+ public function __construct($data = null) {
+ if ($data !== null) {
+ $this->data = $data;
+ } else {
+ $this->data = new \stdClass();
+ }
+ }
+
+ /**
+ * Add data for export.
+ * @param string $element The index for the data.
+ * @param string $content The content for the index.
+ */
+ public function add_to_page($element, $content) {
+ if ($element === 'responses') {
+ $this->data->{$element}[] = $content;
+ } else {
+ $this->data->{$element} = empty($this->data->{$element}) ? $content : ($this->data->{$element} . $content);
+ }
+ }
+
+ /**
+ * Export the data for template.
+ * @param \renderer_base $output
+ */
+ public function export_for_template(\renderer_base $output) {
+ return $this->data;
+ }
+
+}
diff --git a/classes/output/viewpage.php b/classes/output/viewpage.php
index 3939810f..ab7462f5 100644
--- a/classes/output/viewpage.php
+++ b/classes/output/viewpage.php
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\output;
+
/**
* Contains class mod_questionnaire\output\viewpage
*
@@ -22,11 +24,6 @@
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
-namespace mod_questionnaire\output;
-
-defined('MOODLE_INTERNAL') || die();
-
class viewpage implements \renderable, \templatable {
/**
@@ -49,8 +46,8 @@ public function __construct($data = null) {
/**
* Add data for export.
- * @param string The index for the data.
- * @param string The content for the index.
+ * @param string $element The index for the data.
+ * @param string $content The content for the index.
*/
public function add_to_page($element, $content) {
$this->data->{$element} = empty($this->data->{$element}) ? $content : ($this->data->{$element} . $content);
@@ -64,4 +61,4 @@ public function export_for_template(\renderer_base $output) {
return $this->data;
}
-}
\ No newline at end of file
+}
diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php
index f1dfe97b..3388857d 100644
--- a/classes/privacy/provider.php
+++ b/classes/privacy/provider.php
@@ -14,6 +14,14 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\privacy;
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\contextlist;
+use \core_privacy\local\request\userlist;
+use \core_privacy\local\request\approved_contextlist;
+use \core_privacy\local\request\approved_userlist;
+
/**
* Contains class mod_questionnaire\privacy\provider
*
@@ -22,45 +30,23 @@
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+class provider implements
+ // This plugin has data.
+ \core_privacy\local\metadata\provider,
-namespace mod_questionnaire\privacy;
-
-defined('MOODLE_INTERNAL') || die();
-
-// The core_userlist_provider was introduced in 3.6, 3.5.3 and 3.4.6. It will not work in any release supporting the privacy API
-// below those. This code will not use it if it does not exist and continue to work on under 3.5.3, under 3.4.6 and 3.3.*.
-if (interface_exists('\core_privacy\local\request\core_userlist_provider')) {
- abstract class provider_helper implements
- // This plugin has data.
- \core_privacy\local\metadata\provider,
-
- // This plugin is capable of determining which users have data within it.
- \core_privacy\local\request\core_userlist_provider,
+ // This plugin is capable of determining which users have data within it.
+ \core_privacy\local\request\core_userlist_provider,
- // This plugin currently implements the original plugin_provider interface.
- \core_privacy\local\request\plugin\provider {
- }
-} else {
- abstract class provider_helper implements
- // This plugin has data.
- \core_privacy\local\metadata\provider,
-
- // This plugin currently implements the original plugin_provider interface.
- \core_privacy\local\request\plugin\provider {
- }
-}
-
-class provider extends provider_helper {
-
- use \core_privacy\local\legacy_polyfill;
+ // This plugin currently implements the original plugin_provider interface.
+ \core_privacy\local\request\plugin\provider {
/**
* Returns meta data about this system.
*
- * @param collection $items The collection to add metadata to.
+ * @param collection $collection The collection to add metadata to.
* @return collection The array of metadata
*/
- public static function _get_metadata(\core_privacy\local\metadata\collection $collection) {
+ public static function get_metadata(collection $collection): collection {
// Add all of the relevant tables and fields to the collection.
$collection->add_database_table('questionnaire_response', [
@@ -124,8 +110,8 @@ public static function _get_metadata(\core_privacy\local\metadata\collection $co
* @param int $userid The user to search.
* @return contextlist $contextlist The list of contexts used in this plugin.
*/
- public static function _get_contexts_for_userid($userid) {
- $contextlist = new \core_privacy\local\request\contextlist();
+ public static function get_contexts_for_userid(int $userid): contextlist {
+ $contextlist = new contextlist();
$sql = "SELECT c.id
FROM {context} c
@@ -134,7 +120,7 @@ public static function _get_contexts_for_userid($userid) {
INNER JOIN {questionnaire} q ON q.id = cm.instance
INNER JOIN {questionnaire_response} qr ON qr.questionnaireid = q.id
WHERE qr.userid = :attemptuserid
- ";
+ ";
$params = [
'modname' => 'questionnaire',
@@ -153,7 +139,7 @@ public static function _get_contexts_for_userid($userid) {
* @param \core_privacy\local\request\userlist $userlist The userlist containing the list of users who have data in this
* context/plugin combination.
*/
- public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) {
+ public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!$context instanceof \context_module) {
@@ -177,7 +163,7 @@ public static function get_users_in_context(\core_privacy\local\request\userlist
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
- public static function _export_user_data(\core_privacy\local\request\approved_contextlist $contextlist) {
+ public static function export_user_data(approved_contextlist $contextlist) {
global $DB, $CFG;
require_once($CFG->dirroot . '/mod/questionnaire/questionnaire.class.php');
@@ -222,7 +208,7 @@ public static function _export_user_data(\core_privacy\local\request\approved_co
$lastcmid = $response->cmid;
$course = $DB->get_record("course", ["id" => $response->qcourse]);
$cm = get_coursemodule_from_instance("questionnaire", $response->qid, $course->id);
- $questionnaire = new \questionnaire($response->qid, null, $course, $cm);
+ $questionnaire = new \questionnaire($course, $cm, $response->qid, null);
}
$responsedata['responses'][] = [
'complete' => (($response->complete == 'y') ? get_string('yes') : get_string('no')),
@@ -248,7 +234,7 @@ public static function _export_user_data(\core_privacy\local\request\approved_co
*
* @param context $context Context to delete data from.
*/
- public static function _delete_data_for_all_users_in_context(\context $context) {
+ public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
if (!($context instanceof \context_module)) {
@@ -275,7 +261,7 @@ public static function _delete_data_for_all_users_in_context(\context $context)
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
- public static function _delete_data_for_user(\core_privacy\local\request\approved_contextlist $contextlist) {
+ public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
if (empty($contextlist->count())) {
@@ -310,7 +296,7 @@ public static function _delete_data_for_user(\core_privacy\local\request\approve
* @param \core_privacy\local\request\approved_userlist $userlist The approved context and user information to delete
* information for.
*/
- public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) {
+ public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
$context = $userlist->get_context();
@@ -334,9 +320,9 @@ public static function delete_data_for_users(\core_privacy\local\request\approve
/**
* Helper function to delete all the response records for a recordset array of responses.
*
- * @param recordset $responses The list of response records to delete for.
+ * @param \moodle_recordset $responses The list of response records to delete for.
*/
- private static function delete_responses($responses) {
+ private static function delete_responses(\moodle_recordset $responses) {
global $DB;
foreach ($responses as $response) {
@@ -349,4 +335,4 @@ private static function delete_responses($responses) {
$DB->delete_records('questionnaire_response_text', ['response_id' => $response->id]);
}
}
-}
\ No newline at end of file
+}
diff --git a/classes/question/check.php b/classes/question/check.php
index 387524d0..7af517fe 100644
--- a/classes/question/check.php
+++ b/classes/question/check.php
@@ -14,24 +14,30 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\question;
+
/**
* This file contains the parent class for check question types.
*
* @author Mike Churchward
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questiontypes
+ * @package mod_questionnaire
*/
+class check extends question {
-namespace mod_questionnaire\question;
-defined('MOODLE_INTERNAL') || die();
-use \html_writer;
-
-class check extends base {
-
+ /**
+ * Return the responseclass used.
+ * @return string
+ */
protected function responseclass() {
- return '\\mod_questionnaire\\response\\multiple';
+ return '\\mod_questionnaire\\responsetype\\multiple';
}
+ /**
+ * Return the help name.
+ * @return string
+ */
public function helpname() {
return 'checkboxes';
}
@@ -45,7 +51,7 @@ public function has_choices() {
/**
* Override and return a form template if provided. Output of question_survey_display is iterpreted based on this.
- * @return boolean | string
+ * @return string
*/
public function question_template() {
return 'mod_questionnaire/question_check';
@@ -53,7 +59,7 @@ public function question_template() {
/**
* Override and return a form template if provided. Output of response_survey_display is iterpreted based on this.
- * @return boolean | string
+ * @return string
*/
public function response_template() {
return 'mod_questionnaire/response_check';
@@ -69,31 +75,24 @@ public function allows_dependents() {
/**
* Return the context tags for the check question template.
- * @param object $data
+ * @param \mod_questionnaire\responsetype\response\response $response
* @param array $dependants Array of all questions/choices depending on this question.
* @param boolean $blankquestionnaire
- * @return object The check question context tags.
+ * @return \stdClass The check question context tags.
*
*/
- protected function question_survey_display($data, $dependants, $blankquestionnaire=false) {
+ protected function question_survey_display($response, $dependants, $blankquestionnaire = false) {
// Check boxes.
$otherempty = false;
- if (!empty($data) ) {
- if (!isset($data->{'q'.$this->id}) || !is_array($data->{'q'.$this->id})) {
- $data->{'q'.$this->id} = array();
- }
+ if (!empty($response)) {
// Verify that number of checked boxes (nbboxes) is within set limits (length = min; precision = max).
- if ( $data->{'q'.$this->id} ) {
+ if (!empty($response->answers[$this->id])) {
$otherempty = false;
- $boxes = $data->{'q'.$this->id};
- $nbboxes = count($boxes);
- foreach ($boxes as $box) {
- $pos = strpos($box, 'other_');
- if (is_int($pos) == true) {
- $resp = 'q'.$this->id.''.substr($box, 5);
- if (isset($data->$resp) && (trim($data->$resp) == false)) {
- $otherempty = true;
- }
+ $nbboxes = count($response->answers[$this->id]);
+ foreach ($response->answers[$this->id] as $answer) {
+ $choice = $this->choices[$answer->choiceid];
+ if ($choice->is_other_choice()) {
+ $otherempty = empty($answer->value);
}
}
$nbchoices = count($this->choices);
@@ -109,7 +108,7 @@ protected function question_survey_display($data, $dependants, $blankquestionnai
if ($nbboxes < $min || $nbboxes > $max) {
$msg = get_string('boxesnbreq', 'questionnaire');
if ($min == $max) {
- $msg .= ' '.get_string('boxesnbexact', 'questionnaire', $min);
+ $msg .= get_string('boxesnbexact', 'questionnaire', $min);
} else {
if ($min && ($nbboxes < $min)) {
$msg .= get_string('boxesnbmin', 'questionnaire', $min);
@@ -130,160 +129,191 @@ protected function question_survey_display($data, $dependants, $blankquestionnai
$choicetags = new \stdClass();
$choicetags->qelements = [];
foreach ($this->choices as $id => $choice) {
-
- $other = strpos($choice->content, '!other');
$checkbox = new \stdClass();
- if ($other !== 0) { // This is a normal check box.
- $contents = questionnaire_choice_values($choice->content);
- $checked = false;
- if (!empty($data) ) {
- $checked = in_array($id, $data->{'q'.$this->id});
- }
- $checkbox->name = 'q'.$this->id.'[]';
- $checkbox->value = $id;
- $checkbox->id = 'checkbox_'.$id;
- $checkbox->label = format_text($contents->text, FORMAT_HTML, ['noclean' => true]).$contents->image;
- if ($checked) {
- $checkbox->checked = $checked;
- }
- } else { // Check box with associated !other text field.
- // In case length field has been used to enter max number of choices, set it to 20.
- $othertext = preg_replace(
- array("/^!other=/", "/^!other/"),
- array('', get_string('other', 'questionnaire')),
- $choice->content);
- $cid = 'q'.$this->id.'_'.$id;
- if (!empty($data) && isset($data->$cid) && (trim($data->$cid) != false)) {
- $checked = true;
- } else {
- $checked = false;
- }
- $name = 'q'.$this->id.'[]';
- $value = 'other_'.$id;
-
- $checkbox->name = $name;
- $checkbox->oname = $cid;
- $checkbox->value = $value;
- $checkbox->ovalue = (isset($data->$cid) && !empty($data->$cid) ? stripslashes($data->$cid) : '');
- $checkbox->id = 'checkbox_'.$id;
- $checkbox->label = format_text($othertext.'', FORMAT_HTML, ['noclean' => true]);
- if ($checked) {
- $checkbox->checked = $checked;
- }
+ $contents = questionnaire_choice_values($choice->content);
+ $checked = false;
+ if (!empty($response->answers[$this->id]) ) {
+ $checked = isset($response->answers[$this->id][$id]);
+ }
+ $checkbox->name = 'q'.$this->id.'['.$id.']';
+ $checkbox->value = $id;
+ $checkbox->id = 'checkbox_'.$id;
+ $checkbox->label = format_text($contents->text, FORMAT_HTML, ['noclean' => true]).$contents->image;
+ if ($checked) {
+ $checkbox->checked = $checked;
+ }
+ if ($choice->is_other_choice()) {
+ $checkbox->oname = 'q'.$this->id.'['.$choice->other_choice_name().']';
+ $checkbox->ovalue = (isset($response->answers[$this->id][$id]) && !empty($response->answers[$this->id][$id]) ?
+ format_string(stripslashes($response->answers[$this->id][$id]->value)) : '');
+ $checkbox->label = format_text($choice->other_choice_display().'', FORMAT_HTML, ['noclean' => true]);
+ }
+ if (!empty($this->qlegend)) {
+ $checkbox->alabel = strip_tags("{$this->qlegend} {$checkbox->label}");
}
$choicetags->qelements[] = (object)['choice' => $checkbox];
}
if ($otherempty) {
$this->add_notification(get_string('otherempty', 'questionnaire'));
}
-
return $choicetags;
}
/**
* Return the context tags for the check response template.
- * @param object $data
- * @return object The check question response context tags.
- *
+ * @param \mod_questionnaire\responsetype\response\response $response
+ * @return \stdClass The check question response context tags.
*/
- protected function response_survey_display($data) {
+ protected function response_survey_display($response) {
static $uniquetag = 0; // To make sure all radios have unique names.
$resptags = new \stdClass();
$resptags->choices = [];
- if (!isset($data->{'q'.$this->id}) || !is_array($data->{'q'.$this->id})) {
- $data->{'q'.$this->id} = array();
+ if (!isset($response->answers[$this->id])) {
+ $response->answers[$this->id][] = new \mod_questionnaire\responsetype\answer\answer();
}
foreach ($this->choices as $id => $choice) {
$chobj = new \stdClass();
- if (strpos($choice->content, '!other') !== 0) {
+ if (!$choice->is_other_choice()) {
$contents = questionnaire_choice_values($choice->content);
$choice->content = $contents->text.$contents->image;
- if (in_array($id, $data->{'q'.$this->id})) {
+ if (isset($response->answers[$this->id][$id])) {
$chobj->selected = 1;
}
$chobj->name = $id.$uniquetag++;
$chobj->content = (($choice->content === '') ? $id : format_text($choice->content, FORMAT_HTML,
['noclean' => true]));
} else {
- $othertext = preg_replace(
- array("/^!other=/", "/^!other/"),
- array('', get_string('other', 'questionnaire')),
- $choice->content);
- $cid = 'q'.$this->id.'_'.$id;
-
- if (isset($data->$cid)) {
+ $othertext = $choice->other_choice_display();
+ if (isset($response->answers[$this->id][$id])) {
+ $oresp = $response->answers[$this->id][$id]->value;
$chobj->selected = 1;
- $chobj->othercontent = (!empty($data->$cid) ? htmlspecialchars($data->$cid) : ' ');
+ $chobj->othercontent = (!empty($oresp) ? htmlspecialchars($oresp) : ' ');
}
$chobj->name = $id.$uniquetag++;
$chobj->content = (($othertext === '') ? $id : $othertext);
}
+ if (!empty($this->qlegend)) {
+ $chobj->alabel = strip_tags("{$this->qlegend} {$chobj->content}");
+ }
$resptags->choices[] = $chobj;
}
return $resptags;
}
/**
- * Check question's form data for valid response. Override this is type has specific format requirements.
+ * Check question's form data for complete response.
*
* @param object $responsedata The data entered into the response.
* @return boolean
*/
+ public function response_complete($responsedata) {
+ if (isset($responsedata->{'q'.$this->id}) && $this->required() &&
+ is_array($responsedata->{'q'.$this->id})) {
+ foreach ($responsedata->{'q' . $this->id} as $key => $choice) {
+ // If only an 'other' choice is selected and empty, question is not completed.
+ if ((strpos($key, 'o') === 0) && empty($choice)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ }
+ return parent::response_complete($responsedata);
+ }
+
+ /**
+ * Check question's form data for valid response. Override this is type has specific format requirements.
+ *
+ * @param \stdClass $responsedata The data entered into the response.
+ * @return boolean
+ */
public function response_valid($responsedata) {
+ $nbrespchoices = 0;
$valid = true;
- if (isset($responsedata->{'q'.$this->id})) {
- $nbrespchoices = 0;
- foreach ($responsedata->{'q'.$this->id} as $resp) {
- if (strpos($resp, 'other_') !== false) {
+ if (is_a($responsedata, 'mod_questionnaire\responsetype\response\response')) {
+ // If $responsedata is a response object, look through the answers.
+ if (isset($responsedata->answers[$this->id]) && !empty($responsedata->answers[$this->id])) {
+ foreach ($responsedata->answers[$this->id] as $answer) {
+ if (isset($this->choices[$answer->choiceid]) && $this->choices[$answer->choiceid]->is_other_choice()) {
+ $valid = !empty($answer->value);
+ } else {
+ $nbrespchoices++;
+ }
+ }
+ }
+ } else if (isset($responsedata->{'q'.$this->id})) {
+ foreach ($responsedata->{'q'.$this->id} as $key => $answer) {
+ if (strpos($key, 'o') === 0) {
// ..."other" choice is checked but text box is empty.
- $othercontent = "q".$this->id.substr($resp, 5);
- if (trim($responsedata->$othercontent) == false) {
+ $okey = substr($key, 1);
+ if (isset($responsedata->{'q'.$this->id}[$okey]) && empty(trim($answer))) {
$valid = false;
break;
}
- $nbrespchoices++;
- } else if (is_numeric($resp)) {
+ } else if (is_numeric($key)) {
$nbrespchoices++;
}
}
- $nbquestchoices = count($this->choices);
- $min = $this->length;
- $max = $this->precise;
- if ($max == 0) {
- $max = $nbquestchoices;
- }
- if ($min > $max) {
- $min = $max; // Sanity check.
- }
- $min = min($nbquestchoices, $min);
- if ($nbrespchoices && (($nbrespchoices < $min) || ($nbrespchoices > $max))) {
- // Number of ticked boxes is not within min and max set limits.
- $valid = false;
- }
} else {
- $valid = parent::response_valid($responsedata);
+ return parent::response_valid($responsedata);
+ }
+
+ $nbquestchoices = count($this->choices);
+ $min = $this->length;
+ $max = $this->precise;
+ if ($max == 0) {
+ $max = $nbquestchoices;
+ }
+ if ($min > $max) {
+ $min = $max; // Sanity check.
+ }
+ $min = min($nbquestchoices, $min);
+ if ($nbrespchoices && (($nbrespchoices < $min) || ($nbrespchoices > $max))) {
+ // Number of ticked boxes is not within min and max set limits.
+ $valid = false;
}
return $valid;
}
+ /**
+ * Return the length form element.
+ * @param \MoodleQuickForm $mform
+ * @param string $helptext
+ */
protected function form_length(\MoodleQuickForm $mform, $helptext = '') {
return parent::form_length($mform, 'minforcedresponses');
}
+ /**
+ * Return the precision form element.
+ * @param \MoodleQuickForm $mform
+ * @param string $helptext
+ */
protected function form_precise(\MoodleQuickForm $mform, $helptext = '') {
return parent::form_precise($mform, 'maxforcedresponses');
}
+ /**
+ * True if question provides mobile support.
+ *
+ * @return bool
+ */
+ public function supports_mobile() {
+ return true;
+ }
+
/**
* Preprocess choice data.
+ * @param \stdClass $formdata
+ * @return bool
*/
protected function form_preprocess_choicedata($formdata) {
if (empty($formdata->allchoices)) {
- error (get_string('enterpossibleanswers', 'questionnaire'));
+ throw new \moodle_exception('enterpossibleanswers', 'mod_questionnaire');
} else {
// Sanity checks for min and max checked boxes.
$allchoices = $formdata->allchoices;
@@ -296,8 +326,61 @@ protected function form_preprocess_choicedata($formdata) {
if ($formdata->precise > $nbvalues) {
$formdata->precise = $nbvalues;
}
- $formdata->precise = max($formdata->length, $formdata->precise);
+ if ($formdata->precise != 0) {
+ $formdata->precise = max($formdata->length, $formdata->precise);
+ }
}
return true;
}
-}
\ No newline at end of file
+
+ /**
+ * Return the mobile question display.
+ * @param int $qnum
+ * @param bool $autonum
+ * @return \stdClass
+ */
+ public function mobile_question_display($qnum, $autonum = false) {
+ $mobiledata = parent::mobile_question_display($qnum, $autonum);
+ $mobiledata->ischeckbox = true;
+ return $mobiledata;
+ }
+
+ /**
+ * Return the mobile question choices display.
+ * @return array
+ */
+ public function mobile_question_choices_display() {
+ $choices = parent::mobile_question_choices_display();
+ foreach ($choices as $choicenum => $choice) {
+ // Add a fieldkey for each choice.
+ $choices[$choicenum]->choicefieldkey = $this->mobile_fieldkey($choice->id);
+ if ($choice->is_other_choice()) {
+ $choices[$choicenum]->otherchoicekey = $this->mobile_fieldkey($choice->other_choice_name());
+ $choices[$choicenum]->content = format_text($choice->other_choice_display(), FORMAT_HTML, ['noclean' => true]);
+ }
+ }
+ return $choices;
+ }
+
+ /**
+ * Return the mobile response data.
+ * @param \stdClass $response
+ * @return array
+ */
+ public function get_mobile_response_data($response) {
+ $resultdata = [];
+ if (isset($response->answers[$this->id])) {
+ foreach ($response->answers[$this->id] as $answer) {
+ if (isset($this->choices[$answer->choiceid])) {
+ // Add a fieldkey for each choice.
+ $resultdata[$this->mobile_fieldkey($answer->choiceid)] = 1;
+ if ($this->choices[$answer->choiceid]->is_other_choice()) {
+ $resultdata[$this->mobile_fieldkey($this->choices[$answer->choiceid]->other_choice_name())] =
+ $answer->value;
+ }
+ }
+ }
+ }
+ return $resultdata;
+ }
+}
diff --git a/classes/question/choice.php b/classes/question/choice.php
new file mode 100644
index 00000000..567d271c
--- /dev/null
+++ b/classes/question/choice.php
@@ -0,0 +1,210 @@
+.
+
+namespace mod_questionnaire\question;
+
+/**
+ * This defines a structured class to hold question choices.
+ *
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package mod_questionnaire
+ * @copyright 2019, onwards Poet
+ */
+class choice {
+
+ // Class properties.
+
+ /** The table name. */
+ const TABLE = 'questionnaire_quest_choice';
+
+ /** @var int $id The id of the question choice this applies to. */
+ public $id;
+
+ /** @var int $questionid The id of the question this choice applies to. */
+ public $questionid;
+
+ /** @var string $content The display content for this choice. */
+ public $content;
+
+ /** @var string $value Optional value assigned to this choice. */
+ public $value;
+
+ /**
+ * Choice constructor.
+ * @param int $id
+ * @param int $questionid
+ * @param string $content
+ * @param mixed $value
+ */
+ public function __construct($id = null, $questionid = null, $content = null, $value = null) {
+ $this->id = $id;
+ $this->questionid = $questionid;
+ $this->content = $content;
+ $this->value = $value;
+ }
+
+ /**
+ * Create and return a choice object from a data id. If not found, an empty object is returned.
+ *
+ * @param int $id The data id to load.
+ * @return choice
+ */
+ public static function create_from_id($id) {
+ global $DB;
+
+ // Rename the data field question_id to questionid to conform with code conventions. Eventually, data table should be
+ // changed.
+ if ($record = $DB->get_record(self::tablename(), ['id' => $id], 'id,question_id as questionid,content,value')) {
+ return new choice($id, $record->questionid, $record->content, $record->value);
+ } else {
+ return new choice();
+ }
+ }
+
+ /**
+ * Create and return a choice object from data.
+ *
+ * @param \stdclass|array $choicedata The data to load.
+ * @return choice
+ */
+ public static function create_from_data($choicedata) {
+ if (!is_array($choicedata)) {
+ $choicedata = (array)$choicedata;
+ }
+
+ $properties = array_keys(get_class_vars(__CLASS__));
+ foreach ($properties as $property) {
+ if (!isset($choicedata[$property])) {
+ $choicedata[$property] = null;
+ }
+ }
+ // Since the data table uses 'question_id' instead of 'questionid', look for that field as well. Hack that should be fixed
+ // by renaming the data table column.
+ if (!empty($choicedata['question_id'])) {
+ $choicedata['questionid'] = $choicedata['question_id'];
+ }
+
+ return new choice($choicedata['id'], $choicedata['questionid'], $choicedata['content'], $choicedata['value']);
+ }
+
+ /**
+ * Return the table name for choice.
+ */
+ public static function tablename() {
+ return self::TABLE;
+ }
+
+ /**
+ * Delete the choice record.
+ * @param int $id
+ * @return bool
+ */
+ public static function delete_from_db_by_id($id) {
+ global $DB;
+ return $DB->delete_records(self::tablename(), ['id' => $id]);
+ }
+
+ /**
+ * Delete this record from the DB.
+ * @return bool
+ */
+ public function delete_from_db() {
+ return self::delete_from_db_by_id($this->id);
+ }
+
+ /**
+ * Return true if the content string is an "other" choice.
+ *
+ * @param string $content
+ * @return bool
+ */
+ public static function content_is_other_choice($content) {
+ return (strpos($content, '!other') === 0);
+ }
+
+ /**
+ * Return true if the choice object is an "other" choice.
+ *
+ * @return bool
+ */
+ public function is_other_choice() {
+ return (self::content_is_other_choice($this->content));
+ }
+
+ /**
+ * Return the string to display for an "other" option content string. If the option is not an "other", return false.
+ *
+ * @param string $content
+ * @return string|bool
+ */
+ public static function content_other_choice_display($content) {
+ if (!self::content_is_other_choice($content)) {
+ return false;
+ }
+
+ // If there is a defined string display after the "=", return it. Otherwise the "other" language string.
+ return preg_replace(["/^!other=/", "/^!other/"], ['', get_string('other', 'questionnaire')], $content);
+ }
+
+ /**
+ * Return the string to display for an "other" option for this object. If the option is not an "other", return false.
+ *
+ * @return string|bool
+ */
+ public function other_choice_display() {
+ return self::content_other_choice_display($this->content);
+ }
+
+ /**
+ * Is the content a named degree rate choice.
+ * @param string $content
+ * @return array|bool
+ */
+ public static function content_is_named_degree_choice($content) {
+ if (preg_match("/^([0-9]{1,3})=(.*)$/", $content, $ndegrees)) {
+ return [$ndegrees[1] => $ndegrees[2]];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Is the choice object a named degree rate choice.
+ * @return array|bool
+ */
+ public function is_named_degree_choice() {
+ return self::content_is_named_degree_choice($this->content);
+ }
+
+ /**
+ * Return the string to use as an input name for an other choice.
+ *
+ * @param int $choiceid
+ * @return string
+ */
+ public static function id_other_choice_name($choiceid) {
+ return 'o' . $choiceid;
+ }
+
+ /**
+ * Return the string to use as an input name for an other choice.
+ * @return string
+ */
+ public function other_choice_name() {
+ return self::id_other_choice_name($this->id);
+ }
+}
diff --git a/classes/question/date.php b/classes/question/date.php
index 1462d061..c564c34b 100644
--- a/classes/question/date.php
+++ b/classes/question/date.php
@@ -14,24 +14,30 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\question;
+
/**
* This file contains the parent class for date question types.
*
* @author Mike Churchward
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questiontypes
+ * @package mod_questionnaire
*/
+class date extends question {
-namespace mod_questionnaire\question;
-defined('MOODLE_INTERNAL') || die();
-use \html_writer;
-
-class date extends base {
-
+ /**
+ * Return the responseclass used.
+ * @return string
+ */
protected function responseclass() {
- return '\\mod_questionnaire\\response\\date';
+ return '\\mod_questionnaire\\responsetype\\date';
}
+ /**
+ * Return the help name.
+ * @return string
+ */
public function helpname() {
return 'date';
}
@@ -54,32 +60,29 @@ public function response_template() {
/**
* Return the context tags for the check question template.
- * @param object $data
- * @param string $descendantdata
+ * @param \mod_questionnaire\responsetype\response\response $response
+ * @param array $descendantsdata
* @param boolean $blankquestionnaire
* @return object The check question context tags.
- *
*/
- protected function question_survey_display($data, $descendantsdata, $blankquestionnaire=false) {
+ protected function question_survey_display($response, $descendantsdata, $blankquestionnaire=false) {
// Date.
$questiontags = new \stdClass();
- if (!empty($data->{'q'.$this->id})) {
- $dateentered = $data->{'q'.$this->id};
- $setdate = questionnaire_check_date ($dateentered, false);
- if ($setdate == 'wrongdateformat') {
+ if (!empty($response->answers[$this->id])) {
+ $dateentered = $response->answers[$this->id][0]->value;
+ $setdate = $this->check_date_format($dateentered);
+ if (!$setdate) {
$msg = get_string('wrongdateformat', 'questionnaire', $dateentered);
$this->add_notification($msg);
- } else if ($setdate == 'wrongdaterange') {
- $msg = get_string('wrongdaterange', 'questionnaire');
- $this->add_notification($msg);
} else {
- $data->{'q'.$this->id} = $setdate;
+ $response->answers[$this->id][0]->value = $dateentered;
}
}
$choice = new \stdClass();
+ $choice->type = 'date'; // Using HTML5 date input.
$choice->onkeypress = 'return event.keyCode != 13;';
$choice->name = 'q'.$this->id;
- $choice->value = (isset($data->{'q'.$this->id}) ? $data->{'q'.$this->id} : '');
+ $choice->value = (isset($response->answers[$this->id][0]->value) ? $response->answers[$this->id][0]->value : '');
$questiontags->qelements = new \stdClass();
$questiontags->qelements->choice = $choice;
return $questiontags;
@@ -87,14 +90,14 @@ protected function question_survey_display($data, $descendantsdata, $blankquesti
/**
* Return the context tags for the check response template.
- * @param object $data
+ * @param \mod_questionnaire\responsetype\response\response $response
* @return object The check question response context tags.
- *
*/
- protected function response_survey_display($data) {
+ protected function response_survey_display($response) {
$resptags = new \stdClass();
- if (isset($data->{'q'.$this->id})) {
- $resptags->content = $data->{'q'.$this->id};
+ if (isset($response->answers[$this->id])) {
+ $answer = reset($response->answers[$this->id]);
+ $resptags->content = $answer->value;
}
return $resptags;
}
@@ -102,26 +105,128 @@ protected function response_survey_display($data) {
/**
* Check question's form data for valid response. Override this is type has specific format requirements.
*
- * @param object $responsedata The data entered into the response.
+ * @param \stdClass $responsedata The data entered into the response.
* @return boolean
*/
public function response_valid($responsedata) {
- if (isset($responsedata->{'q'.$this->id})) {
- $checkdateresult = '';
- if ($responsedata->{'q'.$this->id} != '') {
- $checkdateresult = questionnaire_check_date($responsedata->{'q'.$this->id});
+ $responseval = false;
+ if (is_a($responsedata, 'mod_questionnaire\responsetype\response\response')) {
+ // If $responsedata is a response object, look through the answers.
+ if (isset($responsedata->answers[$this->id]) && !empty($responsedata->answers[$this->id])) {
+ $answer = $responsedata->answers[$this->id][0];
+ $responseval = $answer->value;
}
- return (substr($checkdateresult, 0, 5) != 'wrong');
+ } else if (isset($responsedata->{'q'.$this->id})) {
+ $responseval = $responsedata->{'q' . $this->id};
+ }
+ if ($responseval !== false) {
+ $checkdateresult = true;
+ if ($responseval != '') {
+ $checkdateresult = $this->check_date_format($responseval);
+ }
+ return $checkdateresult;
} else {
return parent::response_valid($responsedata);
}
}
+ /**
+ * Return the form length.
+ * @param \MoodleQuickForm $mform
+ * @param string $helpname
+ * @return \MoodleQuickForm|void
+ */
protected function form_length(\MoodleQuickForm $mform, $helpname = '') {
- return base::form_length_hidden($mform);
+ return question::form_length_hidden($mform);
}
+ /**
+ * Return the form precision.
+ * @param \MoodleQuickForm $mform
+ * @param string $helpname
+ * @return \MoodleQuickForm|void
+ */
protected function form_precise(\MoodleQuickForm $mform, $helpname = '') {
- return base::form_precise_hidden($mform);
+ return question::form_precise_hidden($mform);
+ }
+
+ /**
+ * Verify that the date provided is in the proper YYYY-MM-DD format.
+ * @param string $date
+ * @return bool
+ */
+ public function check_date_format($date) {
+ $datepieces = explode('-', $date);
+ $return = true;
+ if (count($datepieces) != 3) {
+ $return = false;
+ } else {
+ foreach ($datepieces as $piece => $datepiece) {
+ if (!is_numeric($datepiece)) {
+ $return = false;
+ break;
+ }
+ switch ($piece) {
+ // Year check.
+ case 0:
+ if ((strlen($datepiece) != 4) || ($datepiece <= 0)) {
+ $return = false;
+ break 2;
+ }
+ break;
+ // Month check.
+ case 1:
+ if ((strlen($datepiece) != 2) || ((int)$datepiece < 1) || ((int)$datepiece > 12)) {
+ $return = false;
+ break 2;
+ }
+ break;
+ // Day check.
+ case 2:
+ if ((strlen($datepiece) != 2) || ((int)$datepiece < 1) || ((int)$datepiece > 31)) {
+ $return = false;
+ break 2;
+ }
+ break;
+ }
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * True if question provides mobile support.
+ *
+ * @return bool
+ */
+ public function supports_mobile() {
+ return true;
+ }
+
+ /**
+ * Does the question support mobile display.
+ * @param int $qnum
+ * @param bool $autonum
+ * @return \stdClass
+ */
+ public function mobile_question_display($qnum, $autonum = false) {
+ $mobiledata = parent::mobile_question_display($qnum, $autonum);
+ $mobiledata->isdate = true;
+ return $mobiledata;
+ }
+
+ /**
+ * Does the question have mobile choices.
+ * @return mixed
+ */
+ public function mobile_question_choices_display() {
+ $choices = [];
+ $choices[0] = new \stdClass();
+ $choices[0]->id = 0;
+ $choices[0]->choice_id = 0;
+ $choices[0]->question_id = $this->id;
+ $choices[0]->content = '';
+ $choices[0]->value = null;
+ return $choices;
}
-}
\ No newline at end of file
+}
diff --git a/classes/question/drop.php b/classes/question/drop.php
index 635e83cb..17c49667 100644
--- a/classes/question/drop.php
+++ b/classes/question/drop.php
@@ -14,24 +14,33 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\question;
+use \html_writer;
+use mod_questionnaire\question\choice;
+use mod_questionnaire\responsetype\response\response;
+
/**
* This file contains the parent class for drop question types.
*
* @author Mike Churchward
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questiontypes
+ * @package mod_questionnaire
*/
+class drop extends question {
-namespace mod_questionnaire\question;
-defined('MOODLE_INTERNAL') || die();
-use \html_writer;
-
-class drop extends base {
-
+ /**
+ * Each question type must define its response class.
+ * @return string The response object based off of questionnaire_response_base.
+ */
protected function responseclass() {
- return '\\mod_questionnaire\\response\\single';
+ return '\\mod_questionnaire\\responsetype\\single';
}
+ /**
+ * Short name for this question type - no spaces, etc..
+ * @return string
+ */
public function helpname() {
return 'dropdown';
}
@@ -45,7 +54,7 @@ public function has_choices() {
/**
* Override and return a form template if provided. Output of question_survey_display is iterpreted based on this.
- * @return boolean | string
+ * @return string
*/
public function question_template() {
return 'mod_questionnaire/question_drop';
@@ -53,7 +62,7 @@ public function question_template() {
/**
* Override and return a form template if provided. Output of response_survey_display is iterpreted based on this.
- * @return boolean | string
+ * @return string
*/
public function response_template() {
return 'mod_questionnaire/response_drop';
@@ -61,7 +70,7 @@ public function response_template() {
/**
* Override this and return true if the question type allows dependent questions.
- * @return boolean
+ * @return bool
*/
public function allows_dependents() {
return true;
@@ -69,6 +78,7 @@ public function allows_dependents() {
/**
* True if question type supports feedback options. False by default.
+ * @return bool
*/
public function supports_feedback() {
return true;
@@ -76,19 +86,18 @@ public function supports_feedback() {
/**
* Return the context tags for the check question template.
- * @param object $data
+ * @param \mod_questionnaire\responsetype\response\response $response
* @param array $dependants Array of all questions/choices depending on this question.
* @param boolean $blankquestionnaire
* @return object The check question context tags.
*
*/
- protected function question_survey_display($data, $dependants, $blankquestionnaire=false) {
+ protected function question_survey_display($response, $dependants, $blankquestionnaire=false) {
// Drop.
$options = [];
$choicetags = new \stdClass();
$choicetags->qelements = new \stdClass();
- $selected = isset($data->{'q'.$this->id}) ? $data->{'q'.$this->id} : false;
$options[] = (object)['value' => '', 'label' => get_string('choosedots')];
foreach ($this->choices as $key => $choice) {
if ($pos = strpos($choice->content, '=')) {
@@ -96,8 +105,8 @@ protected function question_survey_display($data, $dependants, $blankquestionnai
}
$option = new \stdClass();
$option->value = $key;
- $option->label = $choice->content;
- if (($selected !== false) && ($key == $selected)) {
+ $option->label = format_string($choice->content);
+ if (isset($response->answers[$this->id][$key])) {
$option->selected = true;
}
$options[] = $option;
@@ -114,11 +123,10 @@ protected function question_survey_display($data, $dependants, $blankquestionnai
/**
* Return the context tags for the drop response template.
- * @param object $data
- * @return object The check question response context tags.
- *
+ * @param \mod_questionnaire\responsetype\response\response $response
+ * @return \stdClass The check question response context tags.
*/
- protected function response_survey_display($data) {
+ protected function response_survey_display($response) {
static $uniquetag = 0; // To make sure all radios have unique names.
$resptags = new \stdClass();
@@ -126,12 +134,18 @@ protected function response_survey_display($data) {
$resptags->id = 'menu' . $resptags->name;
$resptags->class = 'select custom-select ' . $resptags->id;
$resptags->options = [];
+ $resptags->options[] = (object)['value' => '', 'label' => get_string('choosedots')];
+
+ if (!isset($response->answers[$this->id])) {
+ $response->answers[$this->id][] = new \mod_questionnaire\responsetype\answer\answer();
+ }
+
foreach ($this->choices as $id => $choice) {
$contents = questionnaire_choice_values($choice->content);
$chobj = new \stdClass();
$chobj->value = $id;
$chobj->label = format_text($contents->text, FORMAT_HTML, ['noclean' => true]);
- if (isset($data->{'q'.$this->id}) && ($id == $data->{'q'.$this->id})) {
+ if (isset($response->answers[$this->id][$id])) {
$chobj->selected = 1;
$resptags->selectedlabel = $chobj->label;
}
@@ -141,11 +155,57 @@ protected function response_survey_display($data) {
return $resptags;
}
+ /**
+ * Return the length form element.
+ * @param \MoodleQuickForm $mform
+ * @param string $helpname
+ */
protected function form_length(\MoodleQuickForm $mform, $helpname = '') {
- return base::form_length_hidden($mform);
+ return question::form_length_hidden($mform);
}
+ /**
+ * Return the precision form element.
+ * @param \MoodleQuickForm $mform
+ * @param string $helpname
+ */
protected function form_precise(\MoodleQuickForm $mform, $helpname = '') {
- return base::form_precise_hidden($mform);
+ return question::form_precise_hidden($mform);
+ }
+
+ /**
+ * True if question provides mobile support.
+ * @return bool
+ */
+ public function supports_mobile() {
+ return true;
+ }
+
+ /**
+ * Return the mobile question display.
+ * @param int $qnum
+ * @param bool $autonum
+ * @return \stdClass
+ */
+ public function mobile_question_display($qnum, $autonum = false) {
+ $mobiledata = parent::mobile_question_display($qnum, $autonum);
+ $mobiledata->isselect = true;
+ return $mobiledata;
+ }
+
+ /**
+ * Return the mobile response data.
+ * @param response $response
+ * @return array
+ */
+ public function get_mobile_response_data($response) {
+ $resultdata = [];
+ if (isset($response->answers[$this->id])) {
+ foreach ($response->answers[$this->id] as $answer) {
+ // Add a fieldkey for each choice.
+ $resultdata[$this->mobile_fieldkey()] = $answer->choiceid;
+ }
+ }
+ return $resultdata;
}
-}
\ No newline at end of file
+}
diff --git a/classes/question/essay.php b/classes/question/essay.php
index 042a4564..3e8591bc 100644
--- a/classes/question/essay.php
+++ b/classes/question/essay.php
@@ -14,29 +14,60 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\question;
+use \html_writer;
+use mod_questionnaire\responsetype\response\response;
+
/**
* This file contains the parent class for essay question types.
*
* @author Mike Churchward
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questiontypes
+ * @package mod_questionnaire
*/
+class essay extends text {
-namespace mod_questionnaire\question;
-defined('MOODLE_INTERNAL') || die();
-use \html_writer;
-
-class essay extends base {
-
+ /**
+ * Each question type must define its response class.
+ * @return object The response object based off of questionnaire_response_base.
+ */
protected function responseclass() {
- return '\\mod_questionnaire\\response\\text';
+ return '\\mod_questionnaire\\responsetype\\text';
}
+ /**
+ * Short name for this question type - no spaces, etc..
+ * @return string
+ */
public function helpname() {
return 'essaybox';
}
- protected function question_survey_display($data, $descendantsdata, $blankquestionnaire=false) {
+ /**
+ * Override and return a form template if provided. Output of question_survey_display is iterpreted based on this.
+ * @return boolean | string
+ */
+ public function question_template() {
+ return false;
+ }
+
+ /**
+ * Override and return a response template if provided. Output of response_survey_display is iterpreted based on this.
+ * @return boolean | string
+ */
+ public function response_template() {
+ return false;
+ }
+
+ /**
+ * Question specific display method.
+ * @param response $response
+ * @param array $descendantsdata
+ * @param bool $blankquestionnaire
+ *
+ */
+ protected function question_survey_display($response, $descendantsdata, $blankquestionnaire=false) {
$output = '';
// Essay.
@@ -53,8 +84,8 @@ protected function question_survey_display($data, $descendantsdata, $blankquesti
$rows = $this->precise > 1 ? $this->precise : $this->length;
}
$name = 'q'.$this->id;
- if (isset($data->{'q'.$this->id})) {
- $value = $data->{'q'.$this->id};
+ if (isset($response->answers[$this->id][0])) {
+ $value = $response->answers[$this->id][0]->value;
} else {
$value = '';
}
@@ -62,38 +93,67 @@ protected function question_survey_display($data, $descendantsdata, $blankquesti
$editor = editors_get_preferred_editor();
$editor->use_editor($name, questionnaire_get_editor_options($this->context));
$texteditor = html_writer::tag('textarea', $value,
- array('id' => $name, 'name' => $name, 'rows' => $rows, 'cols' => $cols, 'aria-labelledby' => 'Text box'));
+ ['id' => $name, 'name' => $name, 'rows' => $rows, 'cols' => $cols, 'class' => 'form-control']);
} else {
$editor = FORMAT_PLAIN;
$texteditor = html_writer::tag('textarea', $value,
- array('id' => $name, 'name' => $name, 'rows' => $rows, 'cols' => $cols, 'aria-labelledby' => 'Text box'));
+ ['id' => $name, 'name' => $name, 'rows' => $rows, 'cols' => $cols]);
}
$output .= $texteditor;
return $output;
}
- protected function response_survey_display($data) {
+ /**
+ * Question specific response display method.
+ * @param \stdClass $response
+ *
+ */
+ protected function response_survey_display($response) {
+ if (isset($response->answers[$this->id])) {
+ $answer = reset($response->answers[$this->id]);
+ $answer = format_text($answer->value, FORMAT_HTML);
+ } else {
+ $answer = ' ';
+ }
$output = '';
$output .= '
';
+ $options = ['noclean' => true, 'para' => false, 'filter' => true,
+ 'context' => $this->context, 'overflowdiv' => true];
+ $choice->content .= format_text($extrahtml, FORMAT_HTML, $options);
+ }
+
+ if (!in_array($choiceid, $excludes)) {
+ $choice->choice_id = $choiceid;
+ if ($choice->value == null) {
+ $choice->value = '';
+ }
+ $choices[$cnum] = $choice;
+ }
+ $cnum++;
+ }
+
+ return $choices;
+ }
+
+ /**
+ * Display the rates question for mobile.
+ * @return array
+ */
+ public function mobile_question_rates_display() {
+ $rates = [];
+ if (!empty($this->nameddegrees)) {
+ foreach ($this->nameddegrees as $value => $label) {
+ $rates[] = (object)['value' => $value, 'label' => $label];
+ }
+ } else {
+ for ($i = 1; $i <= $this->length; $i++) {
+ $rates[] = (object)['value' => $i, 'label' => $i];
+ }
+ }
+ return $rates;
+ }
+
+ /**
+ * Return the mobile response data.
+ * @param response $response
+ * @return array
+ */
+ public function get_mobile_response_data($response) {
+ $resultdata = [];
+ if (isset($response->answers[$this->id])) {
+ foreach ($response->answers[$this->id] as $answer) {
+ // Add a fieldkey for each choice.
+ if (!empty($this->nameddegrees)) {
+ if (isset($this->nameddegrees[$answer->value])) {
+ $resultdata[$this->mobile_fieldkey($answer->choiceid)] = $this->nameddegrees[$answer->value];
+ } else {
+ $resultdata[$this->mobile_fieldkey($answer->choiceid)] = $answer->value;
+ }
+ } else {
+ $resultdata[$this->mobile_fieldkey($answer->choiceid)] = $answer->value;
+ }
+ }
+ }
+ return $resultdata;
+ }
+
+ /**
+ * Add the nameddegrees property.
+ */
+ private function add_nameddegrees_from_extradata() {
+ if (!empty($this->extradata)) {
+ $this->nameddegrees = json_decode($this->extradata, true);
+ }
+ }
+
+ /**
+ * Insert nameddegress to the extradata database field.
+ * @param array $nameddegrees
+ * @return bool
+ * @throws \dml_exception
+ */
+ public function insert_nameddegrees(array $nameddegrees) {
+ return $this->insert_extradata(json_encode($nameddegrees));
+ }
+
+ /**
+ * Helper function used to move existing named degree choices for the specified question from the "quest_choice" table to the
+ * "question" table.
+ * @param int $qid
+ * @param null|\stdClass $questionrec
+ */
+ public static function move_nameddegree_choices(int $qid = 0, \stdClass $questionrec = null) {
+ global $DB;
+
+ if ($qid !== 0) {
+ $question = new rate($qid);
+ } else {
+ $question = new rate(0, $questionrec);
+ }
+ $nameddegrees = [];
+ $oldchoiceids = [];
+ // There was an issue where rate values were being stored as 1..n, no matter what the named degree value was. We need to fix
+ // the old responses now. This also assumes that the values are now 1 based rather than 0 based.
+ $newvalues = [];
+ $oldval = 1;
+ foreach ($question->choices as $choice) {
+ if ($nameddegree = $choice->is_named_degree_choice()) {
+ $nameddegrees += $nameddegree;
+ $oldchoiceids[] = $choice->id;
+ reset($nameddegree);
+ $newvalues[$oldval++] = key($nameddegree);
+ }
+ }
+
+ if (!empty($nameddegrees)) {
+ if ($question->insert_nameddegrees($nameddegrees)) {
+ // Remove the old named desgree from the choices table.
+ foreach ($oldchoiceids as $choiceid) {
+ \mod_questionnaire\question\choice::delete_from_db_by_id($choiceid);
+ }
+
+ // First get all existing rank responses for this question.
+ $responses = $DB->get_recordset('questionnaire_response_rank', ['question_id' => $question->id]);
+ // Iterating over each response record ensures we won't change an existing record more than once.
+ foreach ($responses as $response) {
+ // Then, if the old value exists, set it to the new one.
+ if (isset($newvalues[$response->rankvalue])) {
+ $DB->set_field('questionnaire_response_rank', 'rankvalue', $newvalues[$response->rankvalue],
+ ['id' => $response->id]);
+ }
+ }
+ $responses->close();
+ }
+ }
+ }
+
+ /**
+ * Helper function to move named degree choices for all questions, optionally for a specific surveyid.
+ * This should only be called for an upgrade from before '2018110103', or from a restore operation for a version of a
+ * questionnaire before '2018110103'.
+ * @param int|null $surveyid
+ */
+ public static function move_all_nameddegree_choices(int $surveyid = null) {
+ global $DB;
+
+ // This operation might take a while. Cancel PHP timeouts for this.
+ \core_php_time_limit::raise();
+
+ // First, let's adjust all rate answers from zero based to one based (see GHI223).
+ // If a specific survey is being dealt with, only use the questions from that survey.
+ $skip = false;
+ if ($surveyid !== null) {
+ $qids = $DB->get_records_menu('questionnaire_question', ['surveyid' => $surveyid, 'type_id' => QUESRATE],
+ '', 'id,surveyid');
+ if (!empty($qids)) {
+ list($qsql, $qparams) = $DB->get_in_or_equal(array_keys($qids));
+ } else {
+ // No relevant questions, so no need to do this step.
+ $skip = true;
+ }
+ }
+
+ // If we're doing this step, let's do it.
+ if (!$skip) {
+ $select = 'UPDATE {questionnaire_response_rank} ' .
+ 'SET rankvalue = (rankvalue + 1) ' .
+ 'WHERE (rankvalue >= 0)';
+ if ($surveyid !== null) {
+ $select .= ' AND (question_id ' . $qsql . ')';
+ } else {
+ $qparams = [];
+ }
+ $DB->execute($select, $qparams);
+ }
+
+ $args = ['type_id' => QUESRATE];
+ if ($surveyid !== null) {
+ $args['surveyid'] = $surveyid;
+ }
+ $ratequests = $DB->get_recordset('questionnaire_question', $args);
+ foreach ($ratequests as $questionrec) {
+ self::move_nameddegree_choices(0, $questionrec);
+ }
+ $ratequests->close();
+ }
+
+ /**
+ * Set label for per column inside rate table.
+ *
+ * @param int $rowposition
+ * @param string $choicetitle
+ * @param int $colposition
+ * @param string $choiceanswer
+ * @return string
+ */
+ private function set_label(int $rowposition, string $choicetitle, int $colposition, string $choiceanswer): string {
+ $a = (object) [
+ 'rowposition' => $rowposition,
+ 'choicetitle' => $choicetitle,
+ 'colposition' => $colposition,
+ 'choiceanswer' => $choiceanswer,
+ ];
+ return get_string('accessibility:rate:choice', 'questionnaire', $a);
+ }
+}
diff --git a/classes/question/sectiontext.php b/classes/question/sectiontext.php
index f0ee6273..107e6845 100644
--- a/classes/question/sectiontext.php
+++ b/classes/question/sectiontext.php
@@ -14,23 +14,32 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\question;
+
+use mod_questionnaire\feedback\section;
+
/**
* This file contains the parent class for sectiontext question types.
*
* @author Mike Churchward
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questiontypes
+ * @package mod_questionnaire
*/
+class sectiontext extends question {
-namespace mod_questionnaire\question;
-defined('MOODLE_INTERNAL') || die();
-
-class sectiontext extends base {
-
+ /**
+ * Each question type must define its response class.
+ * @return object The response object based off of questionnaire_response_base.
+ */
protected function responseclass() {
return '';
}
+ /**
+ * Short name for this question type - no spaces, etc..
+ * @return string
+ */
public function helpname() {
return 'sectiontext';
}
@@ -47,9 +56,47 @@ public function required() {
* True if question type supports feedback options. False by default.
*/
public function supports_feedback() {
+ return false;
+ }
+
+ /**
+ * True if question provides mobile support.
+ * @return bool
+ */
+ public function supports_mobile() {
return true;
}
+ /**
+ * Display on mobile.
+ *
+ * @param int $qnum
+ * @param bool $autonum
+ */
+ public function mobile_question_display($qnum, $autonum = false) {
+ $options = ['noclean' => true, 'para' => false, 'filter' => true,
+ 'context' => $this->context, 'overflowdiv' => true];
+ $mobiledata = (object)[
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'type_id' => $this->type_id,
+ 'length' => $this->length,
+ 'content' => format_text(file_rewrite_pluginfile_urls($this->content, 'pluginfile.php', $this->context->id,
+ 'mod_questionnaire', 'question', $this->id), FORMAT_HTML, $options),
+ 'content_stripped' => strip_tags($this->content),
+ 'required' => false,
+ 'deleted' => $this->deleted,
+ 'response_table' => $this->responsetable,
+ 'fieldkey' => $this->mobile_fieldkey(),
+ 'precise' => $this->precise,
+ 'qnum' => '',
+ 'errormessage' => get_string('required') . ': ' . $this->name
+ ];
+
+ $mobiledata->issectiontext = true;
+ return $mobiledata;
+ }
+
/**
* True if question type supports feedback scores and weights. Same as supports_feedback() by default.
*/
@@ -61,7 +108,7 @@ public function supports_feedback_scores() {
* True if the question supports feedback and has valid settings for feedback. Override if the default logic is not enough.
*/
public function valid_feedback() {
- return true;
+ return false;
}
/**
@@ -72,23 +119,41 @@ public function question_template() {
return 'mod_questionnaire/question_sectionfb';
}
- protected function question_survey_display($data, $descendantsdata, $blankquestionnaire=false) {
+ /**
+ * Override and return false if a number should not be rendered for this question in any context.
+ * @return bool
+ */
+ public function is_numbered() {
+ return false;
+ }
+
+ /**
+ * Return the context tags for the check question template.
+ * @param \mod_questionnaire\responsetype\response\response $response
+ * @param array $descendantsdata Array of all questions/choices depending on this question.
+ * @param boolean $blankquestionnaire
+ * @return object The check question context tags.
+ *
+ */
+ protected function question_survey_display($response, $descendantsdata, $blankquestionnaire=false) {
global $DB, $CFG, $PAGE;
require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php');
// If !isset then normal behavior as sectiontext question.
- if (!isset($data->questionnaire_id)) {
+ if (!isset($response->questionnaireid)) {
return '';
}
- $fbsections = $DB->get_records('questionnaire_fb_sections', ['surveyid' => $this->surveyid]);
$filteredsections = [];
// In which section(s) is this question?
- foreach ($fbsections as $key => $fbsection) {
- $scorecalculation = unserialize($fbsection->scorecalculation);
- if (array_key_exists($this->id, $scorecalculation)) {
- array_push($filteredsections, $fbsection->section);
+ if ($fbsections = $DB->get_records('questionnaire_fb_sections', ['surveyid' => $this->surveyid])) {
+ foreach ($fbsections as $key => $fbsection) {
+ if ($scorecalculation = section::decode_scorecalculation($fbsection->scorecalculation)) {
+ if (array_key_exists($this->id, $scorecalculation)) {
+ array_push($filteredsections, $fbsection->section);
+ }
+ }
}
}
@@ -97,8 +162,8 @@ protected function question_survey_display($data, $descendantsdata, $blankquesti
return '';
}
- list($cm, $course, $questionnaire) = questionnaire_get_standard_page_items(null, $data->questionnaire_id);
- $questionnaire = new \questionnaire(0, $questionnaire, $course, $cm);
+ list($cm, $course, $questionnaire) = questionnaire_get_standard_page_items(null, $response->questionnaireid);
+ $questionnaire = new \questionnaire($course, $cm, 0, $questionnaire);
$questionnaire->add_renderer($PAGE->get_renderer('mod_questionnaire'));
$questionnaire->add_page(new \mod_questionnaire\output\reportpage());
@@ -106,8 +171,8 @@ protected function question_survey_display($data, $descendantsdata, $blankquesti
$allresponses = false;
$currentgroupid = 0;
$isgroupmember = false;
- $resps = [$data->rid => null];
- $rid = $data->rid;
+ $rid = (isset($response->id) && !empty($response->id)) ? $response->id : 0;
+ $resps = [$rid => null];
// For $filteredsections -> get the feedback messages only for this sections!
$feedbackmessages = $questionnaire->response_analysis($rid, $resps, $compare, $isgroupmember, $allresponses,
$currentgroupid, $filteredsections);
@@ -121,9 +186,13 @@ protected function question_survey_display($data, $descendantsdata, $blankquesti
$questiontags->qelements->choice = $choice;
return $questiontags;
-
}
+ /**
+ * Question specific response display method.
+ * @param \stdClass $data
+ *
+ */
protected function response_survey_display($data) {
return '';
}
@@ -138,22 +207,30 @@ public function response_complete($responsedata) {
return true;
}
- /*
- //name is required for feedbacksections and better organization of different sectiontext questions
- protected function form_name(\MoodleQuickForm $mform) {
- return $mform;
- }
- */
-
+ /**
+ * Add the form required field.
+ * @param \MoodleQuickForm $mform
+ * @return \MoodleQuickForm
+ */
protected function form_required(\MoodleQuickForm $mform) {
return $mform;
}
+ /**
+ * Return the length form element.
+ * @param \MoodleQuickForm $mform
+ * @param string $helpname
+ */
protected function form_length(\MoodleQuickForm $mform, $helpname = '') {
- return base::form_length_hidden($mform);
+ return question::form_length_hidden($mform);
}
+ /**
+ * Return the precision form element.
+ * @param \MoodleQuickForm $mform
+ * @param string $helpname
+ */
protected function form_precise(\MoodleQuickForm $mform, $helpname = '') {
- return base::form_precise_hidden($mform);
+ return question::form_precise_hidden($mform);
}
-}
\ No newline at end of file
+}
diff --git a/classes/question/slider.php b/classes/question/slider.php
new file mode 100644
index 00000000..005c6bab
--- /dev/null
+++ b/classes/question/slider.php
@@ -0,0 +1,337 @@
+.
+
+namespace mod_questionnaire\question;
+
+/**
+ * This file contains the parent class for slider question types.
+ *
+ * @author Hieu Vu Van
+ * @copyright 2022 The Open University.
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package mod_questionnaire
+ */
+class slider extends question {
+
+ /**
+ * Return the responseclass used.
+ * @return string
+ */
+ protected function responseclass() {
+ return '\\mod_questionnaire\\responsetype\\slider';
+ }
+
+ /**
+ * Return the help name.
+ * @return string
+ */
+ public function helpname() {
+ return 'slider';
+ }
+
+ /**
+ * Return true if the question has choices.
+ */
+ public function has_choices() {
+ return false;
+ }
+
+ /**
+ * Override and return a form template if provided. Output of question_survey_display is iterpreted based on this.
+ *
+ * @return boolean | string
+ */
+ public function question_template() {
+ return 'mod_questionnaire/question_slider';
+ }
+
+ /**
+ * Override and return a response template if provided. Output of response_survey_display is iterpreted based on this.
+ *
+ * @return boolean | string
+ */
+ public function response_template() {
+ return 'mod_questionnaire/response_slider';
+ }
+
+ /**
+ * True if question type supports feedback options. False by default.
+ * @return bool
+ */
+ public function supports_feedback() {
+ return true;
+ }
+
+ /**
+ * True if the question supports feedback and has valid settings for feedback. Override if the default logic is not enough.
+ * @return bool
+ */
+ public function valid_feedback() {
+ $extradata = json_decode($this->extradata);
+ $minrange = $extradata->minrange;
+ // Negative scores are not accepted in Feedback.
+ return $this->supports_feedback() && !empty($this->name) && $minrange >= 0;
+ }
+
+ /**
+ * Get the maximum score possible for feedback if appropriate. Override if default behaviour is not correct.
+ * @return int | boolean
+ */
+ public function get_feedback_maxscore() {
+ if ($this->valid_feedback()) {
+ $extradata = json_decode($this->extradata);
+ $maxscore = $extradata->maxrange;
+ } else {
+ $maxscore = false;
+ }
+ return $maxscore;
+ }
+
+ /**
+ * Return the context tags for the check question template.
+ *
+ * @param \mod_questionnaire\responsetype\response\response $response
+ * @param array $dependants Array of all questions/choices depending on this question.
+ * @param boolean $blankquestionnaire
+ * @return object The check question context tags.
+ *
+ */
+ protected function question_survey_display($response, $dependants = [], $blankquestionnaire = false) {
+ global $PAGE;
+ $PAGE->requires->js_init_call('M.mod_questionnaire.init_slider', null, false, questionnaire_get_js_module());
+ $extradata = json_decode($this->extradata);
+ $questiontags = new \stdClass();
+ if (isset($response->answers[$this->id][0])) {
+ $extradata->startingvalue = $response->answers[$this->id][0]->value;
+ }
+ $extradata->name = 'q' . $this->id;
+ $extradata->id = self::qtypename($this->type_id) . $this->id;
+ $questiontags->qelements = new \stdClass();
+ $questiontags->qelements->extradata = $extradata;
+ return $questiontags;
+ }
+
+ /**
+ * Return the context tags for the slider response template.
+ * @param \mod_questionnaire\responsetype\response\response $response
+ * @return \stdClass The check question response context tags.
+ */
+ protected function response_survey_display($response) {
+ global $PAGE;
+ $PAGE->requires->js_init_call('M.mod_questionnaire.init_slider', null, false, questionnaire_get_js_module());
+
+ $resptags = new \stdClass();
+ if (isset($response->answers[$this->id])) {
+ $answer = reset($response->answers[$this->id]);
+ $resptags->content = format_text($answer->value, FORMAT_HTML);
+ if (!empty($response->answers[$this->id]['extradata'])) {
+ $resptags->extradata = $response->answers[$this->id]['extradata'];
+ } else {
+ $extradata = json_decode($this->extradata);
+ $resptags->extradata = $extradata;
+ }
+ }
+ return $resptags;
+ }
+
+ /**
+ * Add the form required field.
+ * @param \MoodleQuickForm $mform
+ * @return \MoodleQuickForm
+ */
+ protected function form_required(\MoodleQuickForm $mform) {
+ return $mform;
+ }
+
+ /**
+ * Return the form precision.
+ * @param \MoodleQuickForm $mform
+ * @param string $helptext
+ * @return \MoodleQuickForm|void
+ */
+ protected function form_precise(\MoodleQuickForm $mform, $helptext = '') {
+ return question::form_precise_hidden($mform);
+ }
+
+ /**
+ * Return the form length.
+ * @param \MoodleQuickForm $mform
+ * @param string $helptext
+ * @return \MoodleQuickForm|void
+ */
+ protected function form_length(\MoodleQuickForm $mform, $helptext = '') {
+ return question::form_length_hidden($mform);
+ }
+
+ /**
+ * Override if the question uses the extradata field.
+ * @param \MoodleQuickForm $mform
+ * @param string $helpname
+ * @return \MoodleQuickForm
+ */
+ protected function form_extradata(\MoodleQuickForm $mform, $helpname = '') {
+ $minelementname = 'minrange';
+ $maxelementname = 'maxrange';
+ $startingvalue = 'startingvalue';
+ $stepvalue = 'stepvalue';
+
+ $ranges = [];
+ if (!empty($this->extradata)) {
+ $ranges = json_decode($this->extradata);
+ }
+ $mform->addElement('text', 'leftlabel', get_string('leftlabel', 'questionnaire'));
+ $mform->setType('leftlabel', PARAM_RAW);
+ if (isset($ranges->leftlabel)) {
+ $mform->setDefault('leftlabel', $ranges->leftlabel);
+ }
+ $mform->addElement('text', 'centerlabel', get_string('centerlabel', 'questionnaire'));
+ $mform->setType('centerlabel', PARAM_RAW);
+ if (isset($ranges->centerlabel)) {
+ $mform->setDefault('centerlabel', $ranges->centerlabel);
+ }
+ $mform->addElement('text', 'rightlabel', get_string('rightlabel', 'questionnaire'));
+ $mform->setType('rightlabel', PARAM_RAW);
+ if (isset($ranges->rightlabel)) {
+ $mform->setDefault('rightlabel', $ranges->rightlabel);
+ }
+
+ $patterint = '/^-?\d+$/';
+ $mform->addElement('text', $minelementname, get_string($minelementname, 'questionnaire'), ['size' => '3']);
+ $mform->setType($minelementname, PARAM_RAW);
+ $mform->addRule($minelementname, get_string('err_required', 'form'), 'required', null, 'client');
+ $mform->addRule($minelementname, get_string('err_numeric', 'form'), 'numeric', '', 'client');
+ $mform->addRule($minelementname, get_string('err_numeric', 'form'), 'regex', $patterint, 'client');
+ $mform->addHelpButton($minelementname, $minelementname, 'questionnaire');
+ if (isset($ranges->minrange)) {
+ $mform->setDefault($minelementname, $ranges->minrange);
+ } else {
+ $mform->setDefault($minelementname, 1);
+ }
+
+ $mform->addElement('text', $maxelementname, get_string($maxelementname, 'questionnaire'), ['size' => '3']);
+ $mform->setType($maxelementname, PARAM_RAW);
+ $mform->addHelpButton($maxelementname, $maxelementname, 'questionnaire');
+ $mform->addRule($maxelementname, get_string('err_required', 'form'), 'required', null, 'client');
+ $mform->addRule($maxelementname, get_string('err_numeric', 'form'), 'numeric', '', 'client');
+ $mform->addRule($maxelementname, get_string('err_numeric', 'form'), 'regex', $patterint, 'client');
+ if (isset($ranges->maxrange)) {
+ $mform->setDefault($maxelementname, $ranges->maxrange);
+ } else {
+ $mform->setDefault($maxelementname, 10);
+ }
+
+ $mform->addElement('text', $startingvalue, get_string($startingvalue, 'questionnaire'), ['size' => '3']);
+ $mform->setType($startingvalue, PARAM_RAW);
+ $mform->addHelpButton($startingvalue, $startingvalue, 'questionnaire');
+ $mform->addRule($startingvalue, get_string('err_required', 'form'), 'required', null, 'client');
+ $mform->addRule($startingvalue, get_string('err_numeric', 'form'), 'numeric', '', 'client');
+ $mform->addRule($startingvalue, get_string('err_numeric', 'form'), 'regex', $patterint, 'client');
+ if (isset($ranges->startingvalue)) {
+ $mform->setDefault($startingvalue, $ranges->startingvalue);
+ } else {
+ $mform->setDefault($startingvalue, 5);
+ }
+
+ $mform->addElement('text', $stepvalue, get_string($stepvalue, 'questionnaire'), ['size' => '3']);
+ $mform->setType($stepvalue, PARAM_RAW);
+ $mform->addHelpButton($stepvalue, $stepvalue, 'questionnaire');
+ $mform->addRule($stepvalue, get_string('err_required', 'form'), 'required', null, 'client');
+ $mform->addRule($stepvalue, get_string('err_numeric', 'form'), 'numeric', '', 'client');
+ $mform->addRule($stepvalue, get_string('err_numeric', 'form'), 'regex', '/^-?\d+$/', 'client');
+
+ if (isset($ranges->stepvalue)) {
+ $mform->setDefault($stepvalue, $ranges->stepvalue);
+ } else {
+ $mform->setDefault($stepvalue, 1);
+ }
+ return $mform;
+ }
+
+ /**
+ * Any preprocessing of general data.
+ * @param \stdClass $formdata
+ * @return bool
+ */
+ protected function form_preprocess_data($formdata) {
+ $ranges = [];
+ if (isset($formdata->minrange)) {
+ $ranges['minrange'] = $formdata->minrange;
+ }
+ if (isset($formdata->maxrange)) {
+ $ranges['maxrange'] = $formdata->maxrange;
+ }
+ if (isset($formdata->startingvalue)) {
+ $ranges['startingvalue'] = $formdata->startingvalue;
+ }
+ if (isset($formdata->stepvalue)) {
+ $ranges['stepvalue'] = $formdata->stepvalue;
+ }
+ if (isset($formdata->leftlabel)) {
+ $ranges['leftlabel'] = $formdata->leftlabel;
+ }
+ if (isset($formdata->rightlabel)) {
+ $ranges['rightlabel'] = $formdata->rightlabel;
+ }
+ if (isset($formdata->centerlabel)) {
+ $ranges['centerlabel'] = $formdata->centerlabel;
+ }
+
+ // Now store the new named degrees in extradata.
+ $formdata->extradata = json_encode($ranges);
+ return parent::form_preprocess_data($formdata);
+ }
+
+ /**
+ * True if question provides mobile support.
+ *
+ * @return bool
+ */
+ public function supports_mobile() {
+ return true;
+ }
+
+ /**
+ * True if question need extradata for mobile app.
+ *
+ * @return bool
+ */
+ public function mobile_question_extradata_display() {
+ return true;
+ }
+
+ /**
+ * Return the mobile question display.
+ *
+ * @param int $qnum
+ * @param bool $autonum
+ * @return \stdClass
+ */
+ public function mobile_question_display($qnum, $autonum = false) {
+ $mobiledata = parent::mobile_question_display($qnum, $autonum);
+ $mobiledata->isslider = true;
+ return $mobiledata;
+ }
+
+ /**
+ * Return the otherdata to be used by the mobile app.
+ *
+ * @return array
+ */
+ public function mobile_otherdata() {
+ $extradata = json_decode($this->extradata);
+ return [$this->mobile_fieldkey() => $extradata->startingvalue];
+ }
+}
diff --git a/classes/question/text.php b/classes/question/text.php
index f10de144..3bd12661 100644
--- a/classes/question/text.php
+++ b/classes/question/text.php
@@ -14,22 +14,24 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\question;
+
/**
* This file contains the parent class for text question types.
*
* @author Mike Churchward
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questiontypes
+ * @package mod_questionnaire
*/
-
-namespace mod_questionnaire\question;
-defined('MOODLE_INTERNAL') || die();
-
-class text extends base {
+class text extends question {
/**
- * Constructor. Use to set any default properties.
- *
+ * The class constructor
+ * @param int $id
+ * @param \stdClass $question
+ * @param \context $context
+ * @param array $params
*/
public function __construct($id = 0, $question = null, $context = null, $params = []) {
$this->length = 20;
@@ -37,10 +39,18 @@ public function __construct($id = 0, $question = null, $context = null, $params
return parent::__construct($id, $question, $context, $params);
}
+ /**
+ * Each question type must define its response class.
+ * @return object The response object based off of questionnaire_response_base.
+ */
protected function responseclass() {
- return '\\mod_questionnaire\\response\\text';
+ return '\\mod_questionnaire\\responsetype\\text';
}
+ /**
+ * Short name for this question type - no spaces, etc..
+ * @return string
+ */
public function helpname() {
return 'textbox';
}
@@ -62,14 +72,13 @@ public function response_template() {
}
/**
- * Return the context tags for the check question template.
- * @param object $data
- * @param string $descendantdata
- * @param boolean $blankquestionnaire
- * @return object The check question context tags.
+ * Question specific display method.
+ * @param \stdClass $response
+ * @param array $descendantsdata
+ * @param bool $blankquestionnaire
*
*/
- protected function question_survey_display($data, $descendantsdata, $blankquestionnaire=false) {
+ protected function question_survey_display($response, $descendantsdata, $blankquestionnaire=false) {
// Text Box.
$questiontags = new \stdClass();
$questiontags->qelements = new \stdClass();
@@ -80,31 +89,76 @@ protected function question_survey_display($data, $descendantsdata, $blankquesti
if ($this->precise > 0) {
$choice->maxlength = $this->precise;
}
- $choice->value = (isset($data->{'q'.$this->id}) ? stripslashes($data->{'q'.$this->id}) : '');
+ $choice->value = (isset($response->answers[$this->id][0]) ?
+ format_string(stripslashes($response->answers[$this->id][0]->value)) : '');
$choice->id = self::qtypename($this->type_id) . $this->id;
$questiontags->qelements->choice = $choice;
return $questiontags;
}
/**
- * Return the context tags for the text response template.
- * @param object $data
- * @return object The radio question response context tags.
- *
+ * Question specific response display method.
+ * @param \stdClass $response
*/
- protected function response_survey_display($data) {
+ protected function response_survey_display($response) {
$resptags = new \stdClass();
- if (isset($data->{'q'.$this->id})) {
- $resptags->content = format_text($data->{'q'.$this->id}, FORMAT_HTML);
+ if (isset($response->answers[$this->id])) {
+ $answer = reset($response->answers[$this->id]);
+ $resptags->content = format_text($answer->value, FORMAT_HTML);
}
return $resptags;
}
+ /**
+ * Return the length form element.
+ * @param \MoodleQuickForm $mform
+ * @param string $helptext
+ */
protected function form_length(\MoodleQuickForm $mform, $helptext = '') {
return parent::form_length($mform, 'fieldlength');
}
+ /**
+ * Return the precision form element.
+ * @param \MoodleQuickForm $mform
+ * @param string $helptext
+ */
protected function form_precise(\MoodleQuickForm $mform, $helptext = '') {
return parent::form_precise($mform, 'maxtextlength');
}
-}
\ No newline at end of file
+
+ /**
+ * True if question provides mobile support.
+ * @return bool
+ */
+ public function supports_mobile() {
+ return true;
+ }
+
+ /**
+ * Override and return false if not supporting mobile app.
+ * @param int $qnum
+ * @param bool $autonum
+ * @return \stdClass
+ */
+ public function mobile_question_display($qnum, $autonum = false) {
+ $mobiledata = parent::mobile_question_display($qnum, $autonum);
+ $mobiledata->istextessay = true;
+ return $mobiledata;
+ }
+
+ /**
+ * Override and return false if not supporting mobile app.
+ * @return array
+ */
+ public function mobile_question_choices_display() {
+ $choices = [];
+ $choices[0] = new \stdClass();
+ $choices[0]->id = 0;
+ $choices[0]->choice_id = 0;
+ $choices[0]->question_id = $this->id;
+ $choices[0]->content = '';
+ $choices[0]->value = null;
+ return $choices;
+ }
+}
diff --git a/classes/question/yesno.php b/classes/question/yesno.php
index 4a53d2af..8ecc5373 100644
--- a/classes/question/yesno.php
+++ b/classes/question/yesno.php
@@ -14,30 +14,37 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\question;
+
/**
* This file contains the parent class for yesno question types.
*
* @author Mike Churchward
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questiontypes
+ * @package mod_questionnaire
*/
+class yesno extends question {
-namespace mod_questionnaire\question;
-defined('MOODLE_INTERNAL') || die();
-
-class yesno extends base {
-
+ /**
+ * Each question type must define its response class.
+ * @return object The response object based off of questionnaire_response_base.
+ */
protected function responseclass() {
- return '\\mod_questionnaire\\response\\boolean';
+ return '\\mod_questionnaire\\responsetype\\boolean';
}
+ /**
+ * Short name for this question type - no spaces, etc..
+ * @return string
+ */
public function helpname() {
return 'yesno';
}
/**
* Override and return a form template if provided. Output of question_survey_display is iterpreted based on this.
- * @return boolean | string
+ * @return string
*/
public function question_template() {
return 'mod_questionnaire/question_yesno';
@@ -45,7 +52,7 @@ public function question_template() {
/**
* Override and return a response template if provided. Output of question_survey_display is iterpreted based on this.
- * @return boolean | string
+ * @return string
*/
public function response_template() {
return 'mod_questionnaire/response_yesno';
@@ -53,7 +60,7 @@ public function response_template() {
/**
* Override this and return true if the question type allows dependent questions.
- * @return boolean
+ * @return bool
*/
public function allows_dependents() {
return true;
@@ -61,6 +68,7 @@ public function allows_dependents() {
/**
* True if question type supports feedback options. False by default.
+ * @return bool
*/
public function supports_feedback() {
return true;
@@ -68,6 +76,7 @@ public function supports_feedback() {
/**
* True if the question supports feedback and has valid settings for feedback. Override if the default logic is not enough.
+ * @return bool
*/
public function valid_feedback() {
return $this->required();
@@ -102,13 +111,13 @@ protected function get_dependency_options() {
/**
* Return the context tags for the check question template.
- * @param object $data
+ * @param \mod_questionnaire\responsetype\response\response $response
* @param array $dependants Array of all questions/choices depending on this question.
* @param boolean $blankquestionnaire
* @return object The check question context tags.
- *
+ * @throws \coding_exception
*/
- protected function question_survey_display($data, $dependants=[], $blankquestionnaire=false) {
+ protected function question_survey_display($response, $dependants=[], $blankquestionnaire=false) {
global $idcounter; // To make sure all radio buttons have unique ids. // JR 20 NOV 2007.
$stryes = get_string('yes');
@@ -124,7 +133,7 @@ protected function question_survey_display($data, $dependants=[], $blankquestion
$options = [$val1 => $stryes, $val2 => $strno];
$name = 'q'.$this->id;
- $checked = (isset($data->{'q'.$this->id}) ? $data->{'q'.$this->id} : '');
+ $checked = (isset($response->answers[$this->id][0]) ? $response->answers[$this->id][0]->value : '');
$ischecked = false;
$choicetags = new \stdClass();
@@ -145,6 +154,9 @@ protected function question_survey_display($data, $dependants=[], $blankquestion
if ($blankquestionnaire) {
$option->disabled = true;
}
+ if (!empty($this->qlegend)) {
+ $option->alabel = strip_tags("{$this->qlegend} {$option->label}");
+ }
$choicetags->qelements->choice[] = $option;
}
// CONTRIB-846.
@@ -160,6 +172,9 @@ protected function question_survey_display($data, $dependants=[], $blankquestion
if (!$ischecked && !$blankquestionnaire) {
$option->checked = true;
}
+ if (!empty($this->qlegend)) {
+ $option->alabel = strip_tags("{$this->qlegend} {$option->label}");
+ }
$choicetags->qelements->choice[] = $option;
}
// End CONTRIB-846.
@@ -169,11 +184,11 @@ protected function question_survey_display($data, $dependants=[], $blankquestion
/**
* Return the context tags for the text response template.
- * @param object $data
+ * @param \mod_questionnaire\responsetype\response\response $response
* @return object The radio question response context tags.
- *
+ * @throws \coding_exception
*/
- protected function response_survey_display($data) {
+ protected function response_survey_display($response) {
static $uniquetag = 0; // To make sure all radios have unique names.
$resptags = new \stdClass();
@@ -182,21 +197,104 @@ protected function response_survey_display($data) {
$resptags->noname = 'q'.$this->id.$uniquetag++.'n';
$resptags->stryes = get_string('yes');
$resptags->strno = get_string('no');
- if (isset($data->{'q'.$this->id}) && ($data->{'q'.$this->id} == 'y')) {
+ if (!isset($response->answers[$this->id])) {
+ $response->answers[$this->id][] = new \mod_questionnaire\responsetype\answer\answer();
+ }
+ $answer = reset($response->answers[$this->id]);
+ if ($answer->value == 'y') {
$resptags->yesselected = 1;
}
- if (isset($data->{'q'.$this->id}) && ($data->{'q'.$this->id} == 'n')) {
+ if ($answer->value == 'n') {
$resptags->noselected = 1;
}
+ if (!empty($this->qlegend)) {
+ $resptags->alabelyes = strip_tags("{$this->qlegend} {$resptags->stryes}");
+ $resptags->alabelno = strip_tags("{$this->qlegend} {$resptags->strno}");
+ }
return $resptags;
}
+ /**
+ * Return the length form element.
+ * @param \MoodleQuickForm $mform
+ * @param string $helpname
+ */
protected function form_length(\MoodleQuickForm $mform, $helpname = '') {
- return base::form_length_hidden($mform);
+ return question::form_length_hidden($mform);
}
+ /**
+ * Return the precision form element.
+ * @param \MoodleQuickForm $mform
+ * @param string $helpname
+ */
protected function form_precise(\MoodleQuickForm $mform, $helpname = '') {
- return base::form_precise_hidden($mform);
+ return question::form_precise_hidden($mform);
+ }
+
+ /**
+ * True if question provides mobile support.
+ *
+ * @return bool
+ */
+ public function supports_mobile() {
+ return true;
+ }
+
+ /**
+ * Override and return false if not supporting mobile app.
+ * @param int $qnum
+ * @param bool $autonum
+ * @return \stdClass
+ */
+ public function mobile_question_display($qnum, $autonum = false) {
+ $mobiledata = parent::mobile_question_display($qnum, $autonum);
+ $mobiledata->isbool = true;
+ return $mobiledata;
+ }
+
+ /**
+ * Override and return false if not supporting mobile app.
+ * @return array
+ */
+ public function mobile_question_choices_display() {
+ $choices = [];
+ $choices[0] = new \stdClass();
+ $choices[0]->id = 0;
+ $choices[0]->choice_id = 'n';
+ $choices[0]->question_id = $this->id;
+ $choices[0]->value = null;
+ $choices[0]->content = get_string('no');
+ $choices[0]->isbool = true;
+ $choices[1] = new \stdClass();
+ $choices[1]->id = 1;
+ $choices[1]->choice_id = 'y';
+ $choices[1]->question_id = $this->id;
+ $choices[1]->value = null;
+ $choices[1]->content = get_string('yes');
+ $choices[1]->isbool = true;
+ if ($this->required()) {
+ $choices[1]->value = 'y';
+ $choices[1]->firstone = true;
+ }
+
+ return $choices;
+ }
+
+ /**
+ * Return the mobile response data.
+ * @param response $response
+ * @return array
+ */
+ public function get_mobile_response_data($response) {
+ $resultdata = [];
+ if (isset($response->answers[$this->id][0]) && ($response->answers[$this->id][0]->value == 'n')) {
+ $resultdata[$this->mobile_fieldkey()] = 0;
+ } else if (isset($response->answers[$this->id][0]) && ($response->answers[$this->id][0]->value == 'y')) {
+ $resultdata[$this->mobile_fieldkey()] = 1;
+ }
+
+ return $resultdata;
}
-}
\ No newline at end of file
+}
diff --git a/classes/questions_form.php b/classes/questions_form.php
index 766a0845..0bb2b469 100644
--- a/classes/questions_form.php
+++ b/classes/questions_form.php
@@ -14,26 +14,36 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * @package mod_questionnaire
- * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
- * @author Mike Churchward & Joseph Rézeau
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
namespace mod_questionnaire;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
+#[\AllowDynamicProperties]
+/**
+ * The form definition class for questions.
+ *
+ * @package mod_questionnaire
+ * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
+ * @author Mike Churchward & Joseph Rézeau
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
class questions_form extends \moodleform {
+ /**
+ * The constructor.
+ * @param mixed $action
+ * @param bool $moveq
+ */
public function __construct($action, $moveq=false) {
$this->moveq = $moveq;
return parent::__construct($action);
}
+ /**
+ * Form definition.
+ */
public function definition() {
global $CFG, $questionnaire, $SESSION;
global $DB;
@@ -137,7 +147,7 @@ public function definition() {
redirect($CFG->wwwroot.'/mod/questionnaire/questions.php?id='.$questionnaire->cm->id);
}
- if ($tid != QUESPAGEBREAK && $tid != QUESSECTIONTEXT) {
+ if ($question->is_numbered()) {
$qnum++;
}
@@ -158,7 +168,13 @@ public function definition() {
$spacer = $questionnaire->renderer->image_url('spacer');
if (!$this->moveq) {
- $mform->addElement('html', '
'); // Begin div qn-container.
+ if ($dependencies) {
+ // Begin div qn-container with indent if questionnaire has child.
+ $mform->addElement('html', '
');
+ } else {
+ $mform->addElement('html', '
'); // Begin div qn-container.
+ }
+
$mextra = array('value' => $question->id,
'alt' => $strmove,
'title' => $strmove);
@@ -241,7 +257,7 @@ public function definition() {
$manageqgroup[] =& $mform->createElement('image', 'editbutton['.$question->id.']', $esrc, $eextra);
$manageqgroup[] =& $mform->createElement('image', 'removebutton['.$question->id.']', $rsrc, $rextra);
- if ($tid != QUESPAGEBREAK && $tid != QUESSECTIONTEXT) {
+ if ($tid != QUESPAGEBREAK && $tid != QUESSECTIONTEXT && $tid != QUESSLIDER) {
if ($required == 'y') {
$reqsrc = $questionnaire->renderer->image_url('t/stop');
$strrequired = get_string('required', 'questionnaire');
@@ -315,12 +331,10 @@ public function definition() {
$mform->addElement('static', 'qdepend_' . $question->id, '', $dependencies);
}
- if ($tid != QUESPAGEBREAK) {
- if ($tid != QUESSECTIONTEXT) {
+ if ($question->is_numbered()) {
$qnumber = '
';
+ $pagetags->averages->choiceaverages[] = (object)['column1' => $choicecol1, 'column2' => $choicecol2,
+ 'column3' => $choicecol3];
+ // JR JUNE 2012 do not display meaningless average rank values for Osgood.
+ } else if ($avg || ($nbna != 0)) {
+ $stravgval = '';
+ if ($avg) {
+ if ($stravgvalue) {
+ $stravgval = '('.sprintf('%.1f', $avgvalue).')';
+ }
+ $stravgval = sprintf('%.1f', $avg).' '.$stravgval;
+ if ($isna) {
+ $choicecol4 = new \stdClass();
+ $choicecol4->width = $header4->width;
+ $choicecol4->pdfwidth = $header4->pdfwidth;
+ $choicecol4->align = $header4->align;
+ $choicecol4->text = $nbna;
+ }
+ }
+ $choicecol1 = new \stdClass();
+ $choicecol1->width = $header1->width;
+ $choicecol1->pdfwidth = $header1->pdfwidth;
+ $choicecol1->align = $header1->align;
+ $choicecol1->text = format_text($content, FORMAT_HTML, ['noclean' => true]);
+ $choicecol2 = new \stdClass();
+ $choicecol2->width = $header2->width;
+ $choicecol2->pdfwidth = $header2->pdfwidth;
+ $choicecol2->align = $header2->align;
+ $choicecol2->imageurl = $imageurl;
+ $choicecol2->spacerimage = $spacerimage;
+ $choicecol2->margin = $margin;
+ $choicecol2->marginpdf = $marginpdf;
+ $choicecol3 = new \stdClass();
+ $choicecol3->width = $header3->width;
+ $choicecol3->pdfwidth = $header3->pdfwidth;
+ $choicecol3->align = $header3->align;
+ $choicecol3->text = $stravgval;
+ if ($avg) {
+ if (isset($choicecol4)) {
+ $pagetags->averages->choiceaverages[] = (object)['column1' => $choicecol1,
+ 'column2' => $choicecol2, 'column3' => $choicecol3, 'column4' => $choicecol4];
+ } else {
+ $pagetags->averages->choiceaverages[] = (object)['column1' => $choicecol1,
+ 'column2' => $choicecol2, 'column3' => $choicecol3];
+ }
+ } else {
+ $choicecol4 = new \stdClass();
+ $choicecol4->width = $header4->width;
+ $choicecol4->pdfwidth = $header4->pdfwidth;
+ $choicecol4->align = $header4->align;
+ $choicecol4->text = $nbna;
+ $pagetags->averages->choiceaverages[] = (object)['column1' => $choicecol1, 'column2' => $choicecol2,
+ 'column3' => $choicecol3];
+ }
+ }
+ } // End if named degrees.
+ } // End foreach.
+ } else {
+ $nodata1 = new \stdClass();
+ $nodata1->width = $header1->width;
+ $nodata1->align = $header1->align;
+ $nodata1->text = '';
+ $nodata2 = new \stdClass();
+ $nodata2->width = $header2->width;
+ $nodata2->align = $header2->align;
+ $nodata2->text = get_string('noresponsedata', 'mod_questionnaire');
+ $nodata3 = new \stdClass();
+ $nodata3->width = $header3->width;
+ $nodata3->align = $header3->align;
+ $nodata3->text = '';
+ if (isset($header4)) {
+ $nodata4 = new \stdClass();
+ $nodata4->width = $header4->width;
+ $nodata4->align = $header4->align;
+ $nodata4->text = '';
+ $pagetags->averages->nodata = [$nodata1, $nodata2, $nodata3, $nodata4];
+ } else {
+ $pagetags->averages->nodata = [$nodata1, $nodata2, $nodata3];
+ }
+ }
+ return $pagetags;
+ }
+
+ /**
+ * Return a structure for counts.
+ * @param array $rids
+ * @param array $rows
+ * @param string $sort
+ * @return \stdClass
+ */
+ private function mkrescount($rids, $rows, $sort) {
+ // Display number of responses to Rate questions - see http://moodle.org/mod/forum/discuss.php?d=185106.
+ global $DB;
+
+ $nbresponses = count($rids);
+ // Prepare data to be displayed.
+ $isrestricted = ($this->question->length < count($this->question->choices)) && $this->question->precise == 2;
+
+ $rsql = '';
+ if (!empty($rids)) {
+ list($rsql, $params) = $DB->get_in_or_equal($rids);
+ $rsql = ' AND response_id ' . $rsql;
+ }
+
+ array_unshift($params, $this->question->id); // This is question_id.
+ $sql = 'SELECT r.id, c.content, r.rankvalue, c.id AS choiceid ' .
+ 'FROM {questionnaire_quest_choice} c , ' .
+ '{questionnaire_response_rank} r ' .
+ 'WHERE c.question_id = ?' .
+ ' AND r.question_id = c.question_id' .
+ ' AND r.choice_id = c.id ' .
+ $rsql .
+ ' ORDER BY choiceid, rankvalue ASC';
+ $choices = $DB->get_records_sql($sql, $params);
+
+ // Sort rows (results) by average value.
+ if ($sort != 'default') {
+ $sortarray = array();
+ foreach ($rows as $row) {
+ foreach ($row as $key => $value) {
+ if (!isset($sortarray[$key])) {
+ $sortarray[$key] = array();
+ }
+ $sortarray[$key][] = $value;
+ }
+ }
+ $orderby = "average";
+ switch ($sort) {
+ case 'ascending':
+ array_multisort($sortarray[$orderby], SORT_ASC, $rows);
+ break;
+ case 'descending':
+ array_multisort($sortarray[$orderby], SORT_DESC, $rows);
+ break;
+ }
+ }
+ $nbranks = $this->question->length;
+ $ranks = [];
+ $rankvalue = [];
+ if (!empty($this->question->nameddegrees)) {
+ $rankvalue = array_flip(array_keys($this->question->nameddegrees));
+ }
+ foreach ($rows as $row) {
+ $choiceid = $row->id;
+ foreach ($choices as $choice) {
+ if ($choice->choiceid == $choiceid) {
+ $n = 0;
+ for ($i = 1; $i <= $nbranks; $i++) {
+ if ((isset($rankvalue[$choice->rankvalue]) && ($rankvalue[$choice->rankvalue] == ($i - 1))) ||
+ (empty($rankvalue) && ($choice->rankvalue == $i))) {
+ $n++;
+ if (!isset($ranks[$choice->content][$i])) {
+ $ranks[$choice->content][$i] = 0;
+ }
+ $ranks[$choice->content][$i] += $n;
+ } else if (!isset($ranks[$choice->content][$i])) {
+ $ranks[$choice->content][$i] = 0;
+ }
+ }
+ }
+ }
+ }
+
+ // Psettings for display.
+ $strtotal = ''.get_string('total', 'questionnaire').'';
+ $isna = $this->question->precise == 1;
+ $osgood = false;
+ if ($this->question->precise == 3) { // Osgood's semantic differential.
+ $osgood = true;
+ }
+ if ($this->question->precise == 1) {
+ $na = get_string('notapplicable', 'questionnaire');
+ } else {
+ $na = '';
+ }
+ $nameddegrees = 0;
+ $n = array();
+ foreach ($this->question->nameddegrees as $degree) {
+ $content = $degree;
+ $n[$nameddegrees] = format_text($content, FORMAT_HTML, ['noclean' => true]);
+ $nameddegrees++;
+ }
+ foreach ($this->question->choices as $choice) {
+ $contents = questionnaire_choice_values($choice->content);
+ if ($contents->modname) {
+ $choice->content = $contents->text;
+ }
+ }
+
+ $pagetags = new \stdClass();
+ $pagetags->totals = new \stdClass();
+ $pagetags->totals->headers = [];
+ if ($osgood) {
+ $align = 'right';
+ } else {
+ $align = 'left';
+ }
+ $pagetags->totals->headers[] = (object)['align' => $align,
+ 'text' => ''.get_string('responses', 'questionnaire').''];
+
+ // Display the column titles.
+ for ($j = 0; $j < $this->question->length; $j++) {
+ if (isset($n[$j])) {
+ $str = $n[$j];
+ } else {
+ $str = $j + 1;
+ }
+ $pagetags->totals->headers[] = (object)['align' => 'center', 'text' => ''.$str.''];
+ }
+ if ($osgood) {
+ $pagetags->totals->headers[] = (object)['align' => 'left', 'text' => ''];
+ }
+ $pagetags->totals->headers[] = (object)['align' => 'center', 'text' => $strtotal];
+ if ($isrestricted) {
+ $pagetags->totals->headers[] = (object)['align' => 'center', 'text' => get_string('notapplicable', 'questionnaire')];
+ }
+ if ($na) {
+ $pagetags->totals->headers[] = (object)['align' => 'center', 'text' => $na];
+ }
+
+ // Now display the responses.
+ $pagetags->totals->choices = [];
+ foreach ($ranks as $content => $rank) {
+ $totalcols = [];
+ // Eliminate potential named degrees on Likert scale.
+ if (!preg_match("/^[0-9]{1,3}=/", $content)) {
+ // First display the list of degrees (named or un-named)
+ // number of NOT AVAILABLE responses for this possible answer.
+ $nbna = $this->counts[$content]->nbna;
+ // TOTAL number of responses for this possible answer.
+ $total = $this->counts[$content]->num;
+ $nbresp = ''.$total.'';
+ if ($osgood) {
+ // Ensure there are two bits of content.
+ list($content, $contentright) = array_merge(preg_split('/[|]/', $content), array(' '));
+ $header = reset($pagetags->totals->headers);
+ $totalcols[] = (object)['align' => $header->align,
+ 'text' => format_text($content, FORMAT_HTML, ['noclean' => true])];
+ } else {
+ // Eliminate potentially short-named choices.
+ $contents = questionnaire_choice_values($content);
+ if ($contents->modname) {
+ $content = $contents->text;
+ }
+ $header = reset($pagetags->totals->headers);
+ $totalcols[] = (object)['align' => $header->align,
+ 'text' => format_text($content, FORMAT_HTML, ['noclean' => true])];
+ }
+ // Display ranks/rates numbers.
+ $maxrank = max($rank);
+ for ($i = 1; $i <= $this->question->length; $i++) {
+ $percent = '';
+ if (isset($rank[$i])) {
+ $str = $rank[$i];
+ if ($total !== 0 && $str !== 0) {
+ $percent = ' ('.number_format(($str * 100) / $total).'%)';
+ }
+ // Emphasize responses with max rank value.
+ if ($str == $maxrank) {
+ $str = ''.$str.'';
+ }
+ } else {
+ $str = 0;
+ }
+ $header = next($pagetags->totals->headers);
+ $totalcols[] = (object)['align' => $header->align, 'text' => $str.$percent];
+ }
+ if ($osgood) {
+ $header = next($pagetags->totals->headers);
+ $totalcols[] = (object)['align' => $header->align,
+ 'text' => format_text($contentright, FORMAT_HTML, ['noclean' => true])];
+ }
+ $header = next($pagetags->totals->headers);
+ $totalcols[] = (object)['align' => $header->align, 'text' => $nbresp];
+ if ($isrestricted) {
+ $header = next($pagetags->totals->headers);
+ $totalcols[] = (object)['align' => $header->align, 'text' => $nbresponses - $total];
+ }
+ if (!$osgood) {
+ if ($na) {
+ $header = next($pagetags->totals->headers);
+ $totalcols[] = (object)['align' => $header->align, 'text' => $nbna];
+ }
+ }
+ } // End named degrees.
+ $pagetags->totals->choices[] = (object)['totalcols' => $totalcols];
+ }
+ return $pagetags;
+ }
+
+ /**
+ * Sorting function for ascending.
+ * @param \stdClass $a
+ * @param \stdClass $b
+ * @return int
+ */
+ private static function sortavgasc($a, $b) {
+ if (isset($a->avg) && isset($b->avg)) {
+ if ( $a->avg < $b->avg ) {
+ return -1;
+ } else if ($a->avg > $b->avg ) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * Sorting function for descending.
+ * @param \stdClass $a
+ * @param \stdClass $b
+ * @return int
+ */
+ private static function sortavgdesc($a, $b) {
+ if (isset($a->avg) && isset($b->avg)) {
+ if ( $a->avg > $b->avg ) {
+ return -1;
+ } else if ($a->avg < $b->avg) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+}
diff --git a/classes/responsetype/response/response.php b/classes/responsetype/response/response.php
new file mode 100644
index 00000000..070160d2
--- /dev/null
+++ b/classes/responsetype/response/response.php
@@ -0,0 +1,173 @@
+.
+
+namespace mod_questionnaire\responsetype\response;
+
+/**
+ * This defines a structured class to hold responses.
+ *
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package mod_questionnaire
+ * @copyright 2019, onwards Poet
+ */
+class response {
+
+ // Class properties.
+
+ /** @var int $id The id of the response this applies to. */
+ public $id;
+
+ /** @var int $questionnaireid The id of the questionnaire this response applies to. */
+ public $questionnaireid;
+
+ /** @var int $userid The id of the user for this response. */
+ public $userid;
+
+ /** @var int $submitted The most recent submission date of this response. */
+ public $submitted;
+
+ /** @var boolean $complete Flag for final submission of this response. */
+ public $complete;
+
+ /** @var int $grade Numeric grade for this response (if applicable). */
+ public $grade;
+
+ /** @var array $answers Array by question of array of answer objects. */
+ public $answers;
+
+ /**
+ * Choice constructor.
+ * @param null $id
+ * @param null $questionnaireid
+ * @param null $userid
+ * @param null $submitted
+ * @param null $complete
+ * @param null $grade
+ * @param bool $addanswers
+ */
+ public function __construct($id = null, $questionnaireid = null, $userid = null, $submitted = null, $complete = null,
+ $grade = null, $addanswers = true) {
+ $this->id = $id;
+ $this->questionnaireid = $questionnaireid;
+ $this->userid = $userid;
+ $this->submitted = $submitted;
+ $this->complete = $complete;
+ $this->grade = $grade;
+
+ // Add answers by questions that exist.
+ if ($addanswers) {
+ $this->add_questions_answers();
+ }
+ }
+
+ /**
+ * Create and return a response object from data.
+ *
+ * @param \stdClass|array $responsedata The data to load.
+ * @return response
+ */
+ public static function create_from_data($responsedata) {
+ if (!is_array($responsedata)) {
+ $responsedata = (array)$responsedata;
+ }
+
+ $properties = array_keys(get_class_vars(__CLASS__));
+ foreach ($properties as $property) {
+ if (!isset($responsedata[$property])) {
+ $responsedata[$property] = null;
+ }
+ }
+
+ return new response($responsedata['id'], $responsedata['questionnaireid'], $responsedata['userid'],
+ $responsedata['submitted'], $responsedata['complete'], $responsedata['grade']);
+ }
+
+ /**
+ * Provide a response object from web form data to the question.
+ *
+ * @param \stdClass $responsedata All of the responsedata as an object.
+ * @param array $questions
+ * @return bool|response A response object.
+ */
+ public static function response_from_webform($responsedata, $questions) {
+ global $USER;
+
+ $questionnaireid = isset($responsedata->questionnaire_id) ? $responsedata->questionnaire_id :
+ (isset($responsedata->a) ? $responsedata->a : 0);
+ $response = new response($responsedata->rid, $questionnaireid, $USER->id, null, null, null, false);
+ foreach ($questions as $question) {
+ if ($question->supports_responses()) {
+ $response->answers[$question->id] = $question->responsetype::answers_from_webform($responsedata, $question);
+ }
+ }
+ return $response;
+ }
+
+ /**
+ * Provide a response object from mobile app data to the question.
+ *
+ * @param id $questionnaireid
+ * @param id $responseid
+ * @param \stdClass $responsedata All of the responsedata as an object.
+ * @param array $questions Array of question objects.
+ * @return bool|response A response object.
+ */
+ public static function response_from_appdata($questionnaireid, $responseid, $responsedata, $questions) {
+ global $USER;
+
+ $response = new response($responseid, $questionnaireid, $USER->id, null, null, null, false);
+
+ // Process app data by question and choice and create a webform structure.
+ $processedresponses = new \stdClass();
+ $processedresponses->rid = $responseid;
+ foreach ($responsedata as $answerid => $value) {
+ $parts = explode('_', $answerid);
+ if ($parts[0] == 'response') {
+ $qid = 'q' . $parts[2];
+ if (!isset($processedresponses->{$qid})) {
+ $processedresponses->{$qid} = [];
+ }
+ if (isset($parts[3])) {
+ $cid = $parts[3];
+ } else {
+ $cid = 0;
+ }
+ $processedresponses->{$qid}[$cid] = $value;
+ }
+ }
+
+ foreach ($questions as $question) {
+ if ($question->supports_responses() && isset($processedresponses->{'q'.$question->id})) {
+ $response->answers[$question->id] = $question->responsetype::answers_from_appdata($processedresponses, $question);
+ }
+ }
+ return $response;
+ }
+
+ /**
+ * Add the answers to each question in a question array of answers structure.
+ */
+ public function add_questions_answers() {
+ $this->answers = [];
+ $this->answers += \mod_questionnaire\responsetype\multiple::response_answers_by_question($this->id);
+ $this->answers += \mod_questionnaire\responsetype\single::response_answers_by_question($this->id);
+ $this->answers += \mod_questionnaire\responsetype\rank::response_answers_by_question($this->id);
+ $this->answers += \mod_questionnaire\responsetype\boolean::response_answers_by_question($this->id);
+ $this->answers += \mod_questionnaire\responsetype\date::response_answers_by_question($this->id);
+ $this->answers += \mod_questionnaire\responsetype\text::response_answers_by_question($this->id);
+ }
+}
diff --git a/classes/response/base.php b/classes/responsetype/responsetype.php
similarity index 70%
rename from classes/response/base.php
rename to classes/responsetype/responsetype.php
index ce857ad2..59e3715d 100644
--- a/classes/response/base.php
+++ b/classes/responsetype/responsetype.php
@@ -14,51 +14,81 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * This file contains the parent class for questionnaire question types.
- *
- * @author Mike Churchward
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questiontypes
- */
+namespace mod_questionnaire\responsetype;
-namespace mod_questionnaire\response;
-defined('MOODLE_INTERNAL') || die();
use \html_writer;
use \html_table;
use mod_questionnaire\db\bulk_sql_config;
/**
- * Class for describing a response.
+ * This file contains the parent class for questionnaire response types.
*
* @author Mike Churchward
- * @package response
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package mod_questionnaire
*/
+abstract class responsetype {
+
+ // Class properties.
+ /** @var \mod_questionnaire\question\question $question The question for this response. */
+ public $question;
-abstract class base {
+ /** @var int $responseid The id of the response this is for. */
+ public $responseid;
- public function __construct($question) {
+ /** @var array $choices An array of \mod_questionnaire\responsetype\choice objects. */
+ public $choices;
+
+ /**
+ * responsetype constructor.
+ * @param \mod_questionnaire\question\question $question
+ * @param int|null $responseid
+ * @param array $choices
+ */
+ public function __construct(\mod_questionnaire\question\question $question, int $responseid = null, array $choices = []) {
$this->question = $question;
+ $this->responseid = $responseid;
+ $this->choices = $choices;
}
/**
- * Provide the necessary response data table name.
+ * Provide the necessary response data table name. Should probably always be used with late static binding 'static::' form
+ * rather than 'self::' form to allow for class extending.
*
* @return string response table name.
*/
- static public function response_table() {
+ public static function response_table() {
return 'Must be implemented!';
}
+ /**
+ * Return the known response tables. Should be replaced by a better management system eventually.
+ * @return array
+ */
+ public static function all_response_tables() {
+ return ['questionnaire_response_bool', 'questionnaire_response_date', 'questionnaire_response_other',
+ 'questionnaire_response_rank', 'questionnaire_response_text', 'questionnaire_resp_multiple',
+ 'questionnaire_resp_single'];
+ }
+
+ /**
+ * Provide an array of answer objects from web form data for the question.
+ *
+ * @param \stdClass $responsedata All of the responsedata as an object.
+ * @param \mod_questionnaire\question\question $question
+ * @return array \mod_questionnaire\responsetype\answer\answer An array of answer objects.
+ */
+ abstract public static function answers_from_webform($responsedata, $question);
+
/**
* Insert a provided response to the question.
*
- * @param integer $rid - The data id of the response table id.
- * @param mixed $val - The response data provided.
+ * @param object $responsedata All of the responsedata as an object.
* @return int|bool - on error the subtype should call set_error and return false.
*/
- abstract public function insert_response($rid, $val);
+ abstract public function insert_response($responsedata);
/**
* Provide the result information for the specified result records.
@@ -81,7 +111,7 @@ abstract public function display_results($rids=false, $sort='', $anonymous=false
/**
* If the choice id needs to be transformed into a different value, override this in the child class.
- * @param $choiceid
+ * @param mixed $choiceid
* @return mixed
*/
public function transform_choiceid($choiceid) {
@@ -90,22 +120,22 @@ public function transform_choiceid($choiceid) {
/**
* Provide a template for results screen if defined.
- * @return mixed The template string or false/
+ * @param bool $pdf
+ * @return mixed The template string or false.
*/
- public function results_template() {
+ public function results_template($pdf = false) {
return false;
}
/**
* Gets the results tags for templates for questions with defined choices (single, multiple, boolean).
*
- * @param $weights
- * @param $participants Number of questionnaire participants.
- * @param $respondents Number of question respondents.
- * @param $showtotals
+ * @param array $weights
+ * @param int $participants Number of questionnaire participants.
+ * @param int $respondents Number of question respondents.
+ * @param int $showtotals
* @param string $sort
* @return \stdClass
- * @throws \coding_exception
*/
public function get_results_tags($weights, $participants, $respondents, $showtotals = 1, $sort = '') {
global $CFG;
@@ -128,6 +158,7 @@ public function get_results_tags($weights, $participants, $respondents, $showtot
reset ($weights);
$pagetags->responses = [];
+ $evencolor = false;
foreach ($weights as $content => $num) {
$response = new \stdClass();
$response->text = format_text($content, FORMAT_HTML, ['noclean' => true]);
@@ -157,8 +188,11 @@ public function get_results_tags($weights, $participants, $respondents, $showtot
$response->percent = sprintf(' %.'.$precision.'f%%', $percent);
}
$response->total = $num;
+ // The 'evencolor' attribute is used by the PDF template.
+ $response->evencolor = $evencolor;
$pagetags->responses[] = (object)['response' => $response];
$pos++;
+ $evencolor = !$evencolor;
} // End while.
if ($showtotals) {
@@ -187,6 +221,7 @@ public function get_results_tags($weights, $participants, $respondents, $showtot
$pagetags->total->image2 = $imageurl . 'thbar.gif';
$pagetags->total->percent = sprintf(' %.'.$precision.'f%%', $percent);
$pagetags->total->total = "$respondents/$participants";
+ $pagetags->total->evencolor = $evencolor;
}
}
@@ -206,24 +241,51 @@ public function get_feedback_scores(array $rids) {
* Return an array of answers by question/choice for the given response. Must be implemented by the subclass.
*
* @param int $rid The response id.
- * @param null $col Other data columns to return.
- * @param bool $csvexport Using for CSV export.
- * @param int $choicecodes CSV choicecodes are required.
- * @param int $choicetext CSV choicetext is required.
* @return array
*/
- static public function response_select($rid, $col = null, $csvexport = false, $choicecodes = 0, $choicetext = 1) {
+ public static function response_select($rid) {
return [];
}
+ /**
+ * Return an array of answer objects by question for the given response id.
+ * THIS SHOULD REPLACE response_select.
+ *
+ * @param int $rid The response id.
+ * @return array array answer
+ */
+ public static function response_answers_by_question($rid) {
+ return [];
+ }
+
+ /**
+ * Provide an array of answer objects from mobile data for the question.
+ *
+ * @param \stdClass $responsedata All of the responsedata as an object.
+ * @param \mod_questionnaire\question\question $question
+ * @return array \mod_questionnaire\responsetype\answer\answer An array of answer objects.
+ */
+ public static function answers_from_appdata($responsedata, $question) {
+ // In most cases this can be a direct call to answers_from_webform with the one modification below. Override when this will
+ // not work.
+ if (isset($responsedata->{'q'.$question->id}) && !empty($responsedata->{'q'.$question->id})) {
+ $responsedata->{'q'.$question->id} = $responsedata->{'q'.$question->id}[0];
+ }
+ return static::answers_from_webform($responsedata, $question);
+ }
+
/**
* Return all the fields to be used for users in bulk questionnaire sql.
*
- * @author: Guy Thomas
* @return string
+ * author: Guy Thomas
*/
protected function user_fields_sql() {
- $userfieldsarr = get_all_user_name_fields();
+ if (class_exists('\core_user\fields')) {
+ $userfieldsarr = \core_user\fields::get_name_fields();
+ } else {
+ $userfieldsarr = get_all_user_name_fields();
+ }
$userfieldsarr = array_merge($userfieldsarr, ['username', 'department', 'institution']);
$userfields = '';
foreach ($userfieldsarr as $field) {
@@ -236,12 +298,13 @@ protected function user_fields_sql() {
/**
* Return sql and params for getting responses in bulk.
- * @author Guy Thomas
* @param int|array $questionnaireids One id, or an array of ids.
* @param bool|int $responseid
* @param bool|int $userid
* @param bool|int $groupid
+ * @param int $showincompletes
* @return array
+ * author Guy Thomas
*/
public function get_bulk_sql($questionnaireids, $responseid = false, $userid = false, $groupid = false, $showincompletes = 0) {
global $DB;
diff --git a/classes/responsetype/single.php b/classes/responsetype/single.php
new file mode 100644
index 00000000..d8dc2f7c
--- /dev/null
+++ b/classes/responsetype/single.php
@@ -0,0 +1,400 @@
+.
+
+namespace mod_questionnaire\responsetype;
+
+/**
+ * Class for single response types.
+ *
+ * @author Mike Churchward
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package mod_questionnaire
+ */
+class single extends responsetype {
+ /**
+ * Provide the necessary response data table name. Should probably always be used with late static binding 'static::' form
+ * rather than 'self::' form to allow for class extending.
+ *
+ * @return string response table name.
+ */
+ public static function response_table() {
+ return 'questionnaire_resp_single';
+ }
+
+ /**
+ * Provide an array of answer objects from web form data for the question.
+ *
+ * @param \stdClass $responsedata All of the responsedata as an object.
+ * @param \mod_questionnaire\question\question $question
+ * @return array \mod_questionnaire\responsetype\answer\answer An array of answer objects.
+ * @throws \coding_exception
+ */
+ public static function answers_from_webform($responsedata, $question) {
+ $answers = [];
+ if (isset($responsedata->{'q'.$question->id}) && isset($question->choices[$responsedata->{'q'.$question->id}])) {
+ $record = new \stdClass();
+ $record->responseid = $responsedata->rid;
+ $record->questionid = $question->id;
+ $record->choiceid = $responsedata->{'q'.$question->id};
+ // If this choice is an "other" choice, look for the added input.
+ if ($question->choices[$responsedata->{'q'.$question->id}]->is_other_choice()) {
+ $cname = 'q' . $question->id .
+ \mod_questionnaire\question\choice::id_other_choice_name($responsedata->{'q'.$question->id});
+ $record->value = isset($responsedata->{$cname}) ? $responsedata->{$cname} : '';
+ }
+ $answers[$responsedata->{'q'.$question->id}] = answer\answer::create_from_data($record);
+ }
+ return $answers;
+ }
+
+ /**
+ * Provide an array of answer objects from mobile data for the question.
+ *
+ * @param \stdClass $responsedata All of the responsedata as an object.
+ * @param \mod_questionnaire\question\question $question
+ * @return array \mod_questionnaire\responsetype\answer\answer An array of answer objects.
+ */
+ public static function answers_from_appdata($responsedata, $question) {
+ $answers = [];
+ $qname = 'q'.$question->id;
+ if (isset($responsedata->{$qname}[0]) && !empty($responsedata->{$qname}[0])) {
+ $record = new \stdClass();
+ $record->responseid = $responsedata->rid;
+ $record->questionid = $question->id;
+ $record->choiceid = $responsedata->{$qname}[0];
+ // If this choice is an "other" choice, look for the added input.
+ if ($question->choices[$record->choiceid]->is_other_choice()) {
+ $cname = \mod_questionnaire\question\choice::id_other_choice_name($record->choiceid);
+ $record->value =
+ isset($responsedata->{$qname}[$cname]) ? $responsedata->{$qname}[$cname] : '';
+ } else {
+ $record->value = '';
+ }
+ $answers[] = answer\answer::create_from_data($record);
+ }
+ return $answers;
+ }
+
+ /**
+ * Insert a provided response to the question.
+ *
+ * @param object $responsedata All of the responsedata as an object.
+ * @return int|bool - on error the subtype should call set_error and return false.
+ */
+ public function insert_response($responsedata) {
+ global $DB;
+
+ if (!$responsedata instanceof \mod_questionnaire\responsetype\response\response) {
+ $response = \mod_questionnaire\responsetype\response\response::response_from_webform($responsedata, [$this->question]);
+ } else {
+ $response = $responsedata;
+ }
+
+ $resid = false;
+ if (!empty($response) && isset($response->answers[$this->question->id])) {
+ foreach ($response->answers[$this->question->id] as $answer) {
+ if (isset($this->question->choices[$answer->choiceid])) {
+ if ($this->question->choices[$answer->choiceid]->is_other_choice()) {
+ // If no input specified, ignore this choice.
+ if (empty($answer->value) || preg_match("/^[\s]*$/", $answer->value)) {
+ continue;
+ }
+ $record = new \stdClass();
+ $record->response_id = $response->id;
+ $record->question_id = $this->question->id;
+ $record->choice_id = $answer->choiceid;
+ $record->response = clean_text($answer->value);
+ $DB->insert_record('questionnaire_response_other', $record);
+ }
+ // Record the choice selection.
+ $record = new \stdClass();
+ $record->response_id = $response->id;
+ $record->question_id = $this->question->id;
+ $record->choice_id = $answer->choiceid;
+ $resid = $DB->insert_record(static::response_table(), $record);
+ }
+ }
+ }
+ return $resid;
+ }
+
+ /**
+ * Provide the result information for the specified result records.
+ *
+ * @param int|array $rids - A single response id, or array.
+ * @param boolean $anonymous - Whether or not responses are anonymous.
+ * @return array - Array of data records.
+ */
+ public function get_results($rids=false, $anonymous=false) {
+ global $DB;
+
+ $rsql = '';
+ $params = array($this->question->id);
+ if (!empty($rids)) {
+ list($rsql, $rparams) = $DB->get_in_or_equal($rids);
+ $params = array_merge($params, $rparams);
+ $rsql = ' AND response_id ' . $rsql;
+ }
+
+ // Added qc.id to preserve original choices ordering.
+ $sql = 'SELECT rt.id, qc.id as cid, qc.content ' .
+ 'FROM {questionnaire_quest_choice} qc, ' .
+ '{'.static::response_table().'} rt ' .
+ 'WHERE qc.question_id= ? AND qc.content NOT LIKE \'!other%\' AND ' .
+ 'rt.question_id=qc.question_id AND rt.choice_id=qc.id' . $rsql . ' ' .
+ 'ORDER BY qc.id';
+
+ $rows = $DB->get_records_sql($sql, $params);
+
+ // Handle 'other...'.
+ $sql = 'SELECT rt.id, rt.response, qc.content ' .
+ 'FROM {questionnaire_response_other} rt, ' .
+ '{questionnaire_quest_choice} qc ' .
+ 'WHERE rt.question_id= ? AND rt.choice_id=qc.id' . $rsql . ' ' .
+ 'ORDER BY qc.id';
+
+ if ($recs = $DB->get_records_sql($sql, $params)) {
+ $i = 1;
+ foreach ($recs as $rec) {
+ $rows['other'.$i] = new \stdClass();
+ $rows['other'.$i]->content = $rec->content;
+ $rows['other'.$i]->response = $rec->response;
+ $i++;
+ }
+ }
+
+ return $rows;
+ }
+
+ /**
+ * Provide the feedback scores for all requested response id's. This should be provided only by questions that provide feedback.
+ * @param array $rids
+ * @return array | boolean
+ */
+ public function get_feedback_scores(array $rids) {
+ global $DB;
+
+ $rsql = '';
+ $params = [$this->question->id];
+ if (!empty($rids)) {
+ list($rsql, $rparams) = $DB->get_in_or_equal($rids);
+ $params = array_merge($params, $rparams);
+ $rsql = ' AND response_id ' . $rsql;
+ }
+ $params[] = 'y';
+
+ $sql = 'SELECT response_id as rid, c.value AS score ' .
+ 'FROM {'.$this->response_table().'} r ' .
+ 'INNER JOIN {questionnaire_quest_choice} c ON r.choice_id = c.id ' .
+ 'WHERE r.question_id= ? ' . $rsql . ' ' .
+ 'ORDER BY response_id ASC';
+ return $DB->get_records_sql($sql, $params);
+ }
+
+ /**
+ * Provide a template for results screen if defined.
+ * @param bool $pdf
+ * @return mixed The template string or false/
+ */
+ public function results_template($pdf = false) {
+ if ($pdf) {
+ return 'mod_questionnaire/resultspdf_choice';
+ } else {
+ return 'mod_questionnaire/results_choice';
+ }
+ }
+
+ /**
+ * Return the JSON structure required for the template.
+ *
+ * @param bool $rids
+ * @param string $sort
+ * @param bool $anonymous
+ * @return string
+ */
+ public function display_results($rids=false, $sort='', $anonymous=false) {
+ global $DB;
+
+ $rows = $this->get_results($rids, $anonymous);
+ if (is_array($rids)) {
+ $prtotal = 1;
+ } else if (is_int($rids)) {
+ $prtotal = 0;
+ }
+ $numresps = count($rids);
+
+ $responsecountsql = 'SELECT COUNT(DISTINCT r.response_id) ' .
+ 'FROM {' . $this->response_table() . '} r ' .
+ 'WHERE r.question_id = ? ';
+ $numrespondents = $DB->count_records_sql($responsecountsql, [$this->question->id]);
+
+ if ($rows) {
+ $counts = [];
+ foreach ($rows as $idx => $row) {
+ if (strpos($idx, 'other') === 0) {
+ $answer = $row->response;
+ $ccontent = $row->content;
+ $content = \mod_questionnaire\question\choice::content_other_choice_display($ccontent);
+ $content .= ' ' . clean_text($answer);
+ $textidx = $content;
+ $counts[$textidx] = !empty($counts[$textidx]) ? ($counts[$textidx] + 1) : 1;
+ } else {
+ $contents = questionnaire_choice_values($row->content);
+ $textidx = $contents->text.$contents->image;
+ $counts[$textidx] = !empty($counts[$textidx]) ? ($counts[$textidx] + 1) : 1;
+ }
+ }
+ $pagetags = $this->get_results_tags($counts, $numresps, $numrespondents, $prtotal, $sort);
+ } else {
+ $pagetags = new \stdClass();
+ }
+ return $pagetags;
+ }
+
+ /**
+ * Return an array of answers by question/choice for the given response. Must be implemented by the subclass.
+ * Array is indexed by question, and contains an array by choice code of selected choices.
+ *
+ * @param int $rid The response id.
+ * @return array
+ */
+ public static function response_select($rid) {
+ global $DB;
+
+ $values = [];
+ $sql = 'SELECT a.id, q.id as qid, q.content, c.content as ccontent, c.id as cid, o.response ' .
+ 'FROM {'.static::response_table().'} a ' .
+ 'INNER JOIN {questionnaire_question} q ON a.question_id = q.id ' .
+ 'INNER JOIN {questionnaire_quest_choice} c ON a.choice_id = c.id ' .
+ 'LEFT JOIN {questionnaire_response_other} o ON a.response_id = o.response_id AND c.id = o.choice_id ' .
+ 'WHERE a.response_id = ? ';
+ $records = $DB->get_records_sql($sql, [$rid]);
+ foreach ($records as $row) {
+ $newrow['content'] = $row->content;
+ $newrow['ccontent'] = $row->ccontent;
+ $newrow['responses'] = [];
+ $newrow['responses'][$row->cid] = $row->cid;
+ if (\mod_questionnaire\question\choice::content_is_other_choice($row->ccontent)) {
+ $newrow['responses'][\mod_questionnaire\question\choice::id_other_choice_name($row->cid)] = $row->response;
+ }
+ $values[$row->qid] = $newrow;
+ }
+
+ return $values;
+ }
+
+ /**
+ * Return an array of answer objects by question for the given response id.
+ * THIS SHOULD REPLACE response_select.
+ *
+ * @param int $rid The response id.
+ * @return array array answer
+ * @throws \dml_exception
+ */
+ public static function response_answers_by_question($rid) {
+ global $DB;
+
+ $answers = [];
+ $sql = 'SELECT r.id as id, r.response_id as responseid, r.question_id as questionid, r.choice_id as choiceid, ' .
+ 'o.response as value ' .
+ 'FROM {' . static::response_table() .'} r ' .
+ 'LEFT JOIN {questionnaire_response_other} o ON r.response_id = o.response_id AND r.question_id = o.question_id AND ' .
+ 'r.choice_id = o.choice_id ' .
+ 'WHERE r.response_id = ? ';
+ $records = $DB->get_records_sql($sql, [$rid]);
+ foreach ($records as $record) {
+ $answers[$record->questionid][$record->choiceid] = answer\answer::create_from_data($record);
+ }
+
+ return $answers;
+ }
+
+ /**
+ * Return sql and params for getting responses in bulk.
+ * @param int|array $questionnaireids One id, or an array of ids.
+ * @param bool|int $responseid
+ * @param bool|int $userid
+ * @param bool|int $groupid
+ * @param int $showincompletes
+ * @return array
+ * author Guy Thomas
+ */
+ public function get_bulk_sql($questionnaireids, $responseid = false, $userid = false, $groupid = false, $showincompletes = 0) {
+ global $DB;
+
+ $sql = $this->bulk_sql();
+ if (($groupid !== false) && ($groupid > 0)) {
+ $groupsql = ' INNER JOIN {groups_members} gm ON gm.groupid = ? AND gm.userid = qr.userid ';
+ $gparams = [$groupid];
+ } else {
+ $groupsql = '';
+ $gparams = [];
+ }
+
+ if (is_array($questionnaireids)) {
+ list($qsql, $params) = $DB->get_in_or_equal($questionnaireids);
+ } else {
+ $qsql = ' = ? ';
+ $params = [$questionnaireids];
+ }
+ if ($showincompletes == 1) {
+ $showcompleteonly = '';
+ } else {
+ $showcompleteonly = 'AND qr.complete = ? ';
+ $params[] = 'y';
+ }
+
+ $sql .= "
+ AND qr.questionnaireid $qsql $showcompleteonly
+ LEFT JOIN {questionnaire_response_other} qro ON qro.response_id = qr.id AND qro.choice_id = qrs.choice_id
+ LEFT JOIN {user} u ON u.id = qr.userid
+ $groupsql
+ ";
+ $params = array_merge($params, $gparams);
+
+ if ($responseid) {
+ $sql .= " WHERE qr.id = ?";
+ $params[] = $responseid;
+ } else if ($userid) {
+ $sql .= " WHERE qr.userid = ?";
+ $params[] = $userid;
+ }
+
+ return [$sql, $params];
+ }
+
+ /**
+ * Return sql for getting responses in bulk.
+ * @author Guy Thomas
+ * @return string
+ */
+ protected function bulk_sql() {
+ global $DB;
+
+ $userfields = $this->user_fields_sql();
+ $alias = 'qrs';
+ $extraselect = 'qrs.choice_id, ' . $DB->sql_order_by_text('qro.response', 1000) . ' AS response, 0 AS rankvalue';
+
+ return "
+ SELECT " . $DB->sql_concat_join("'_'", ['qr.id', "'".$this->question->helpname()."'", $alias.'.id']) . " AS id,
+ qr.submitted, qr.complete, qr.grade, qr.userid, $userfields, qr.id AS rid, $alias.question_id,
+ $extraselect
+ FROM {questionnaire_response} qr
+ JOIN {".static::response_table()."} $alias ON $alias.response_id = qr.id
+ ";
+ }
+}
diff --git a/classes/responsetype/slider.php b/classes/responsetype/slider.php
new file mode 100644
index 00000000..d0b160e8
--- /dev/null
+++ b/classes/responsetype/slider.php
@@ -0,0 +1,76 @@
+.
+
+namespace mod_questionnaire\responsetype;
+
+/**
+ * Class for slider text response types.
+ *
+ * @author Hieu Vu Van
+ * @copyright 2022 The Open University.
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package mod_questionnaire
+ */
+class slider extends numericaltext {
+ /**
+ * Return an array of answer objects by question for the given response id.
+ * THIS SHOULD REPLACE response_select.
+ *
+ * @param int $rid The response id.
+ * @return array array answer
+ * @throws \dml_exception
+ */
+ public static function response_answers_by_question($rid) {
+ global $DB;
+
+ $answers = [];
+ $sql = 'SELECT qs.id, qs.response_id as responseid, qs.question_id as questionid,
+ 0 as choiceid, qs.response as value, qq.extradata ' .
+ 'FROM {' . static::response_table() . '} qs ' .
+ 'INNER JOIN {questionnaire_question} qq ON qq.id = qs.question_id ' .
+ 'WHERE response_id = ? ';
+ $records = $DB->get_records_sql($sql, [$rid]);
+ foreach ($records as $record) {
+ $answers[$record->questionid][] = answer\answer::create_from_data($record);
+ if (!empty($record->extradata)) {
+ $answers[$record->questionid]['extradata'] = json_decode($record->extradata);
+ }
+ }
+ return $answers;
+ }
+
+ /**
+ * Provide the feedback scores for all requested response id's. This should be provided only by questions that provide feedback.
+ * @param array $rids
+ * @return array | boolean
+ */
+ public function get_feedback_scores(array $rids) {
+ global $DB;
+ $rsql = '';
+ $params = [$this->question->id];
+ if (!empty($rids)) {
+ list($rsql, $rparams) = $DB->get_in_or_equal($rids);
+ $params = array_merge($params, $rparams);
+ $rsql = ' AND response_id ' . $rsql;
+ }
+ $sql = 'SELECT response_id as rid, response AS score ' .
+ 'FROM {'.$this->response_table().'} r ' .
+ 'WHERE r.question_id= ? ' . $rsql . ' ' .
+ 'ORDER BY response_id ASC';
+ return $DB->get_records_sql($sql, $params);
+ }
+
+}
diff --git a/classes/response/text.php b/classes/responsetype/text.php
similarity index 58%
rename from classes/response/text.php
rename to classes/responsetype/text.php
index 8a04ec13..b03b7d47 100644
--- a/classes/response/text.php
+++ b/classes/responsetype/text.php
@@ -14,50 +14,81 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * This file contains the parent class for questionnaire question types.
- *
- * @author Mike Churchward
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questiontypes
- */
-
-namespace mod_questionnaire\response;
-defined('MOODLE_INTERNAL') || die();
+namespace mod_questionnaire\responsetype;
use mod_questionnaire\db\bulk_sql_config;
/**
* Class for text response types.
- *
* @author Mike Churchward
- * @package responsetypes
+ * @copyright 2016 onward Mike Churchward (mike.churchward@poetopensource.org)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package mod_questionnaire
*/
-
-class text extends base {
- static public function response_table() {
+class text extends responsetype {
+ /**
+ * Provide the necessary response data table name. Should probably always be used with late static binding 'static::' form
+ * rather than 'self::' form to allow for class extending.
+ *
+ * @return string response table name.
+ */
+ public static function response_table() {
return 'questionnaire_response_text';
}
- public function insert_response($rid, $val) {
+ /**
+ * Provide an array of answer objects from web form data for the question.
+ *
+ * @param \stdClass $responsedata All of the responsedata as an object.
+ * @param \mod_questionnaire\question\question $question
+ * @return array \mod_questionnaire\responsetype\answer\answer An array of answer objects.
+ */
+ public static function answers_from_webform($responsedata, $question) {
+ $answers = [];
+ if (isset($responsedata->{'q'.$question->id}) && (strlen($responsedata->{'q'.$question->id}) > 0)) {
+ $val = $responsedata->{'q' . $question->id};
+ $record = new \stdClass();
+ $record->responseid = $responsedata->rid;
+ $record->questionid = $question->id;
+ $record->value = $val;
+ $answers[] = answer\answer::create_from_data($record);
+ }
+ return $answers;
+ }
+
+ /**
+ * Insert a provided response to the question.
+ *
+ * @param object $responsedata All of the responsedata as an object.
+ * @return int|bool - on error the subtype should call set_error and return false.
+ */
+ public function insert_response($responsedata) {
global $DB;
- // Only insert if non-empty content.
- if ($this->question->type_id == QUESNUMERIC) {
- $val = str_replace(",", ".", $val); // Allow commas as well as points in decimal numbers.
- $val = preg_replace("/[^0-9.\-]*(-?[0-9]*\.?[0-9]*).*/", '\1', $val);
+
+ if (!$responsedata instanceof \mod_questionnaire\responsetype\response\response) {
+ $response = \mod_questionnaire\responsetype\response\response::response_from_webform($responsedata, [$this->question]);
+ } else {
+ $response = $responsedata;
}
- if (preg_match("/[^ \t\n]/", $val)) {
+ if (!empty($response) && isset($response->answers[$this->question->id][0])) {
$record = new \stdClass();
- $record->response_id = $rid;
+ $record->response_id = $response->id;
$record->question_id = $this->question->id;
- $record->response = $val;
- return $DB->insert_record(self::response_table(), $record);
+ $record->response = clean_text($response->answers[$this->question->id][0]->value);
+ return $DB->insert_record(static::response_table(), $record);
} else {
return false;
}
}
+ /**
+ * Provide the result information for the specified result records.
+ *
+ * @param int|array $rids - A single response id, or array.
+ * @param boolean $anonymous - Whether or not responses are anonymous.
+ * @return array - Array of data records.
+ */
public function get_results($rids=false, $anonymous=false) {
global $DB;
@@ -70,7 +101,7 @@ public function get_results($rids=false, $anonymous=false) {
if ($anonymous) {
$sql = 'SELECT t.id, t.response, r.submitted AS submitted, ' .
'r.questionnaireid, r.id AS rid ' .
- 'FROM {'.self::response_table().'} t, ' .
+ 'FROM {'.static::response_table().'} t, ' .
'{questionnaire_response} r ' .
'WHERE question_id=' . $this->question->id . $rsql .
' AND t.response_id = r.id ' .
@@ -79,7 +110,7 @@ public function get_results($rids=false, $anonymous=false) {
$sql = 'SELECT t.id, t.response, r.submitted AS submitted, r.userid, u.username AS username, ' .
'u.id as usrid, ' .
'r.questionnaireid, r.id AS rid ' .
- 'FROM {'.self::response_table().'} t, ' .
+ 'FROM {'.static::response_table().'} t, ' .
'{questionnaire_response} r, ' .
'{user} u ' .
'WHERE question_id=' . $this->question->id . $rsql .
@@ -92,18 +123,24 @@ public function get_results($rids=false, $anonymous=false) {
/**
* Provide a template for results screen if defined.
+ * @param bool $pdf
* @return mixed The template string or false/
*/
- public function results_template() {
- return 'mod_questionnaire/results_text';
+ public function results_template($pdf = false) {
+ if ($pdf) {
+ return 'mod_questionnaire/resultspdf_text';
+ } else {
+ return 'mod_questionnaire/results_text';
+ }
}
/**
- * @param bool $rids
- * @param string $sort
- * @param bool $anonymous
- * @return string
- * @throws \coding_exception
+ * Provide the result information for the specified result records.
+ *
+ * @param int|array $rids - A single response id, or array.
+ * @param string $sort - Optional display sort.
+ * @param boolean $anonymous - Whether or not responses are anonymous.
+ * @return string - Display output.
*/
public function display_results($rids=false, $sort='', $anonymous=false) {
if (is_array($rids)) {
@@ -114,21 +151,7 @@ public function display_results($rids=false, $sort='', $anonymous=false) {
if ($rows = $this->get_results($rids, $anonymous)) {
$numrespondents = count($rids);
$numresponses = count($rows);
- $isnumeric = $this->question->type_id == QUESNUMERIC;
- // Count identical answers (numeric questions only).
- if ($isnumeric) {
- foreach ($rows as $row) {
- if (!empty($row->response) || $row->response === "0") {
- $this->text = $row->response;
- $textidx = clean_text($this->text);
- $this->counts[$textidx] = !empty($this->counts[$textidx]) ? ($this->counts[$textidx] + 1) : 1;
- $this->userid[$textidx] = !empty($this->counts[$textidx]) ? ($this->counts[$textidx] + 1) : 1;
- }
- }
- $pagetags = $this->get_results_tags($this->counts, $numrespondents, $numresponses, $prtotal);
- } else {
- $pagetags = $this->get_results_tags($rows, $numrespondents, $numresponses, $prtotal);
- }
+ $pagetags = $this->get_results_tags($rows, $numrespondents, $numresponses, $prtotal);
} else {
$pagetags = new \stdClass();
}
@@ -136,15 +159,14 @@ public function display_results($rids=false, $sort='', $anonymous=false) {
}
/**
- * Override the results tags function for templates for questions with dates.
+ * Gets the results tags for templates for questions with defined choices (single, multiple, boolean).
*
- * @param $weights
- * @param $participants Number of questionnaire participants.
- * @param $respondents Number of question respondents.
- * @param $showtotals
+ * @param array $weights
+ * @param int $participants Number of questionnaire participants.
+ * @param int $respondents Number of question respondents.
+ * @param int $showtotals
* @param string $sort
* @return \stdClass
- * @throws \coding_exception
*/
public function get_results_tags($weights, $participants, $respondents, $showtotals = 1, $sort = '') {
$pagetags = new \stdClass();
@@ -166,9 +188,10 @@ public function get_results_tags($weights, $participants, $respondents, $showtot
'¤tgroupid='.$currentgroupid;
}
$users = [];
+ $evencolor = false;
foreach ($weights as $row) {
$response = new \stdClass();
- $response->text = format_text($row->response, FORMAT_HTML, ['noclean' => true]);
+ $response->text = format_text($row->response, FORMAT_HTML);
if ($viewsingleresponse && $nonanonymous) {
$rurl = $url.'&rid='.$row->rid.'&individualresponse=1';
$title = userdate($row->submitted);
@@ -179,7 +202,10 @@ public function get_results_tags($weights, $participants, $respondents, $showtot
} else {
$response->respondent = '';
}
+ // The 'evencolor' attribute is used by the PDF template.
+ $response->evencolor = $evencolor;
$pagetags->responses[] = (object)['response' => $response];
+ $evencolor = !$evencolor;
}
if ($showtotals == 1) {
@@ -194,29 +220,38 @@ public function get_results_tags($weights, $participants, $respondents, $showtot
if (!empty($weights) && is_array($weights)) {
ksort($weights);
+ $evencolor = false;
foreach ($weights as $text => $num) {
$response = new \stdClass();
$response->text = $text;
$response->respondent = $num;
+ // The 'evencolor' attribute is used by the PDF template.
+ $response->evencolor = $evencolor;
$nbresponses += $num;
$sum += $text * $num;
+ $evencolor = !$evencolor;
$pagetags->responses[] = (object)['response' => $response];
}
$response = new \stdClass();
$response->text = $sum;
$response->respondent = $strtotal;
+ $response->evencolor = $evencolor;
$pagetags->responses[] = (object)['response' => $response];
+ $evencolor = !$evencolor;
$response = new \stdClass();
$response->respondent = $straverage;
$avg = $sum / $nbresponses;
$response->text = sprintf('%.' . $this->question->precise . 'f', $avg);
+ $response->evencolor = $evencolor;
$pagetags->responses[] = (object)['response' => $response];
+ $evencolor = !$evencolor;
if ($showtotals == 1) {
$pagetags->total = new \stdClass();
$pagetags->total->total = "$respondents/$participants";
+ $pagetags->total->evencolor = $evencolor;
}
}
}
@@ -228,18 +263,14 @@ public function get_results_tags($weights, $participants, $respondents, $showtot
* Return an array of answers by question/choice for the given response. Must be implemented by the subclass.
*
* @param int $rid The response id.
- * @param null $col Other data columns to return.
- * @param bool $csvexport Using for CSV export.
- * @param int $choicecodes CSV choicecodes are required.
- * @param int $choicetext CSV choicetext is required.
* @return array
*/
- static public function response_select($rid, $col = null, $csvexport = false, $choicecodes = 0, $choicetext = 1) {
+ public static function response_select($rid) {
global $DB;
$values = [];
- $sql = 'SELECT q.id '.$col.', a.response as aresponse '.
- 'FROM {'.self::response_table().'} a, {questionnaire_question} q '.
+ $sql = 'SELECT q.id, q.content, a.response as aresponse '.
+ 'FROM {'.static::response_table().'} a, {questionnaire_question} q '.
'WHERE a.response_id=? AND a.question_id=q.id ';
$records = $DB->get_records_sql($sql, [$rid]);
foreach ($records as $qid => $row) {
@@ -259,12 +290,35 @@ static public function response_select($rid, $col = null, $csvexport = false, $c
return $values;
}
+ /**
+ * Return an array of answer objects by question for the given response id.
+ * THIS SHOULD REPLACE response_select.
+ *
+ * @param int $rid The response id.
+ * @return array array answer
+ * @throws \dml_exception
+ */
+ public static function response_answers_by_question($rid) {
+ global $DB;
+
+ $answers = [];
+ $sql = 'SELECT id, response_id as responseid, question_id as questionid, 0 as choiceid, response as value ' .
+ 'FROM {' . static::response_table() .'} ' .
+ 'WHERE response_id = ? ';
+ $records = $DB->get_records_sql($sql, [$rid]);
+ foreach ($records as $record) {
+ $answers[$record->questionid][] = answer\answer::create_from_data($record);
+ }
+
+ return $answers;
+ }
+
/**
* Configure bulk sql
* @return bulk_sql_config
*/
protected function bulk_sql_config() {
- return new bulk_sql_config(self::response_table(), 'qrt', false, true, false);
+ return new bulk_sql_config(static::response_table(), 'qrt', false, true, false);
}
}
diff --git a/classes/search/activity.php b/classes/search/activity.php
index 631c020f..3c775a1e 100644
--- a/classes/search/activity.php
+++ b/classes/search/activity.php
@@ -25,8 +25,6 @@
namespace mod_questionnaire\search;
-defined('MOODLE_INTERNAL') || die();
-
/**
* Search area for mod_questionnaire activities.
*
@@ -77,4 +75,4 @@ public function get_document($record, $options = []) {
return $doc;
}
-}
\ No newline at end of file
+}
diff --git a/classes/search/question.php b/classes/search/question.php
index 2ac1af8f..0d5d4b60 100644
--- a/classes/search/question.php
+++ b/classes/search/question.php
@@ -13,21 +13,17 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * Contains class mod_questionnaire\search\question
- *
- * @package mod_questionnaire
- * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
- * @author Mike Churchward
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
+
namespace mod_questionnaire\search;
-defined('MOODLE_INTERNAL') || die();
+
/**
+ * Contains the question class definition for search.
+ *
* Search area for mod_questionnaire questions. Separated from the activity search so that admins can choose whether or not they
* want this part enabled.
*
* @package mod_questionnaire
+ * @author Mike Churchward
* @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -157,4 +153,4 @@ public function get_context_url(\core_search\document $doc) {
$context = \context::instance_by_id($doc->get('contextid'));
return new \moodle_url('/mod/questionnaire/view.php', ['id' => $context->instanceid]);
}
-}
\ No newline at end of file
+}
diff --git a/classes/settings_form.php b/classes/settings_form.php
index aab1f412..c810f0d6 100644
--- a/classes/settings_form.php
+++ b/classes/settings_form.php
@@ -14,21 +14,24 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * @package mod_questionnaire
- * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
- * @author Mike Churchward
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
namespace mod_questionnaire;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
+/**
+ * The questionnaire settings form.
+ * @package mod_questionnaire
+ * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
class settings_form extends \moodleform {
+ /**
+ * Defines the form.
+ */
public function definition() {
global $questionnaire, $questionnairerealms;
@@ -88,7 +91,13 @@ public function definition() {
$mform->setType('thank_body', PARAM_RAW);
$mform->setDefault('thank_body', $questionnaire->survey->thank_body);
- $mform->addElement('text', 'email', get_string('email', 'questionnaire'), array('size' => '75'));
+ $allowemailreporting = get_config('questionnaire', 'allowemailreporting');
+ if (!$allowemailreporting) {
+ $attributes = ['size' => '75', 'disabled' => 'disabled'];
+ } else {
+ $attributes = ['size' => '75'];
+ }
+ $mform->addElement('text', 'email', get_string('email', 'questionnaire'), $attributes);
$mform->setType('email', PARAM_TEXT);
$mform->setDefault('email', $questionnaire->survey->email);
$mform->addHelpButton('email', 'sendemail', 'questionnaire');
@@ -121,8 +130,15 @@ public function definition() {
}
+ /**
+ * Validation rules for form.
+ * @param array $data array of ("fieldname"=>value) of submitted data
+ * @param array $files array of uploaded files "element_name"=>tmp_file_path
+ * @return array of "element_name"=>"error_description" if there are errors,
+ * or an empty array if everything is OK (true allowed for backwards compatibility too).
+ */
public function validation($data, $files) {
$errors = parent::validation($data, $files);
return $errors;
}
-}
\ No newline at end of file
+}
diff --git a/classes/task/cleanup.php b/classes/task/cleanup.php
index 1dc8396e..7cd40678 100644
--- a/classes/task/cleanup.php
+++ b/classes/task/cleanup.php
@@ -14,17 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace mod_questionnaire\task;
+
/**
* A scheduled task for Questionnaire.
*
* @package mod_questionnaire
* @copyright 2015 The Open University
+ * @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-namespace mod_questionnaire\task;
-
-defined('MOODLE_INTERNAL') || die();
-
class cleanup extends \core\task\scheduled_task {
/**
@@ -36,6 +35,9 @@ public function get_name() {
return get_string('crontask', 'mod_questionnaire');
}
+ /**
+ * Execute method.
+ */
public function execute() {
global $CFG;
require_once($CFG->dirroot . '/mod/questionnaire/locallib.php');
diff --git a/complete.php b/complete.php
index 074ff29c..9950770f 100644
--- a/complete.php
+++ b/complete.php
@@ -14,8 +14,15 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-// This page prints a particular instance of questionnaire.
-
+/**
+ * This page prints a particular instance of questionnaire.
+ *
+ * @package mod_questionnaire
+ * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
+ * @author Mike Churchward
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ *
+ */
require_once("../../config.php");
require_once($CFG->libdir . '/completionlib.php');
require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php');
@@ -26,7 +33,7 @@
$SESSION->questionnaire->current_tab = 'view';
$id = optional_param('id', null, PARAM_INT); // Course Module ID.
-$a = optional_param('a', null, PARAM_INT); // questionnaire ID.
+$a = optional_param('a', null, PARAM_INT); // Questionnaire ID.
$sid = optional_param('sid', null, PARAM_INT); // Survey id.
$resume = optional_param('resume', null, PARAM_INT); // Is this attempt a resume of a saved attempt?
@@ -47,13 +54,13 @@
$PAGE->set_url($url);
$PAGE->set_context($context);
-$questionnaire = new questionnaire(0, $questionnaire, $course, $cm);
+$questionnaire = new questionnaire( $course, $cm, 0, $questionnaire);
// Add renderer and page objects to the questionnaire object for display use.
$questionnaire->add_renderer($PAGE->get_renderer('mod_questionnaire'));
$questionnaire->add_page(new \mod_questionnaire\output\completepage());
$questionnaire->strquestionnaires = get_string("modulenameplural", "questionnaire");
-$questionnaire->strquestionnaire = get_string("modulename", "questionnaire");
+$questionnaire->strquestionnaire = get_string("modulename", "questionnaire");
// Mark as viewed.
$completion = new completion_info($course);
@@ -77,4 +84,4 @@
// Output the page.
echo $questionnaire->renderer->header();
echo $questionnaire->renderer->render($questionnaire->page);
-echo $questionnaire->renderer->footer($course);
\ No newline at end of file
+echo $questionnaire->renderer->footer($course);
diff --git a/composer.json b/composer.json
index 014357e7..728c346a 100644
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,5 @@
{
- "name": "remotelearner/moodle-mod_questionnaire",
+ "name": "poetos/moodle-mod_questionnaire",
"type": "moodle-mod",
"require": {
"composer/installers": "~1.0"
diff --git a/db/access.php b/db/access.php
index 1fc8a9b0..bc95e281 100644
--- a/db/access.php
+++ b/db/access.php
@@ -238,4 +238,4 @@
)
)
-);
\ No newline at end of file
+);
diff --git a/db/install.php b/db/install.php
index 0a459a11..55b0eda4 100644
--- a/db/install.php
+++ b/db/install.php
@@ -14,18 +14,18 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/*
- *
- * @package mod
- * @subpackage questionnaire
+/**
+ * The file containing the install functions.
+ * @package mod_questionnaire
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* This file is executed right after the install.xml
- * @copyright 2010 Remote Learner (http://www.remote-learner.net)
+ * @copyright 2016 Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-defined('MOODLE_INTERNAL') || die();
-
+/**
+ * The install function.
+ */
function xmldb_questionnaire_install() {
global $DB;
@@ -93,6 +93,13 @@ function xmldb_questionnaire_install() {
$questiontype->response_table = 'response_text';
$id = $DB->insert_record('questionnaire_question_type', $questiontype);
+ $questiontype = new stdClass();
+ $questiontype->typeid = 11;
+ $questiontype->type = 'Slider';
+ $questiontype->has_choices = 'n';
+ $questiontype->response_table = 'response_text';
+ $id = $DB->insert_record('questionnaire_question_type', $questiontype);
+
$questiontype = new stdClass();
$questiontype->typeid = 99;
$questiontype->type = 'Page Break';
@@ -107,4 +114,4 @@ function xmldb_questionnaire_install() {
$questiontype->response_table = '';
$id = $DB->insert_record('questionnaire_question_type', $questiontype);
-}
\ No newline at end of file
+}
diff --git a/db/install.xml b/db/install.xml
index bc2fcb74..a1b7af0b 100644
--- a/db/install.xml
+++ b/db/install.xml
@@ -7,7 +7,7 @@
-
+
@@ -21,15 +21,20 @@
-
+
+
+
+
+
+
@@ -53,10 +58,10 @@
+
-
@@ -66,16 +71,20 @@
-
-
+
+
+
+
+
+
@@ -86,10 +95,8 @@
+
-
-
-
@@ -115,14 +122,12 @@
+
-
-
-
-
+
@@ -136,7 +141,7 @@
-
+
@@ -164,7 +169,7 @@
-
+
@@ -179,7 +184,7 @@
-
+
@@ -194,7 +199,7 @@
-
+
@@ -208,7 +213,7 @@
-
+
@@ -222,8 +227,8 @@
-
-
+
+
@@ -232,12 +237,13 @@
+
-
-
+
+
@@ -246,6 +252,7 @@
+
@@ -260,10 +267,9 @@
+
+
-
-
-
\ No newline at end of file
diff --git a/db/log.php b/db/log.php
index af423063..968e5dc0 100644
--- a/db/log.php
+++ b/db/log.php
@@ -17,9 +17,8 @@
/**
* Capability definitions for the quiz module.
*
- * @package mod
- * @subpackage questionnaire
- * @copyright 2010 Remote-Learner.net (http://www.remote-learner.net)
+ * @package mod_questionnaire
+ * @copyright 2016 Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -29,4 +28,4 @@
array('module' => 'questionnaire', 'action' => 'view all', 'mtable' => 'questionnaire', 'field' => 'name'),
array('module' => 'questionnaire', 'action' => 'submit', 'mtable' => 'questionnaire_response', 'field' => 'id'),
array('module' => 'questionnaire', 'action' => 'view', 'mtable' => 'questionnaire', 'field' => 'name'),
-);
\ No newline at end of file
+);
diff --git a/db/mobile.php b/db/mobile.php
index 4f6d5f0d..01bec8fb 100644
--- a/db/mobile.php
+++ b/db/mobile.php
@@ -29,17 +29,23 @@
'handlers' => [
'questionsview' => [
'displaydata' => [
- 'icon' => $CFG->wwwroot . '/mod/questionnaire/pix/icon.gif',
- 'class' => '',
+ 'icon' => $CFG->wwwroot . '/mod/questionnaire/pix/icon.svg',
+ 'class' => 'core-course-module-questionnaire-handler',
],
'delegate' => 'CoreCourseModuleDelegate',
- 'method' => 'mobile_view_activity'
+ 'method' => 'mobile_view_activity',
+ 'styles' => [
+ 'url' => $CFG->wwwroot . '/mod/questionnaire/styles_app.css',
+ 'version' => '1.5'
+ ]
]
],
'lang' => [
['yourresponse', 'questionnaire'],
['submitted', 'questionnaire'],
+ ['answerquestions', 'questionnaire'],
['areyousure', 'moodle'],
+ ['resumesurvey', 'questionnaire'],
['success', 'moodle'],
['savechanges', 'moodle'],
['nextpage', 'questionnaire'],
@@ -48,4 +54,4 @@
['required', 'moodle']
],
]
-];
\ No newline at end of file
+];
diff --git a/db/services.php b/db/services.php
index e7c529be..ba55f345 100644
--- a/db/services.php
+++ b/db/services.php
@@ -26,14 +26,22 @@
defined('MOODLE_INTERNAL') || die;
+$services = [
+ 'mod_questionnaire_ws' => [
+ 'functions' => ['mod_questionnaire_submit_questionnaire_response'],
+ 'requiredcapability' => '',
+ 'enabled' => 1
+ ]
+];
+
$functions = [
- 'mod_questionnaire_submit_questionnaire_branching' => [
- 'classname' => 'mod_questionnaire_external',
- 'methodname' => 'submit_questionnaire_branching',
+ 'mod_questionnaire_submit_questionnaire_response' => [
+ 'classname' => 'mod_questionnaire\external',
+ 'methodname' => 'submit_questionnaire_response',
'classpath' => 'mod/questionnaire/externallib.php',
- 'description' => 'Questionnaire Branching submit',
+ 'description' => 'Questionnaire submit',
'type' => 'write',
'capabilities' => 'mod/questionnaire:submit',
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE]
]
-];
\ No newline at end of file
+];
diff --git a/db/tasks.php b/db/tasks.php
index c36110b1..40d241ab 100644
--- a/db/tasks.php
+++ b/db/tasks.php
@@ -16,6 +16,7 @@
/**
* Definition of Questionnaire scheduled tasks.
+ *
* Default is to run once every 12 hours.
*
* @package mod_questionnaire
@@ -23,9 +24,9 @@
* @copyright 2015 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
defined('MOODLE_INTERNAL') || die();
+/** @var array $tasks */
$tasks = array(
array(
'classname' => 'mod_questionnaire\task\cleanup',
diff --git a/db/upgrade.php b/db/upgrade.php
index 270b1758..dd5fd989 100644
--- a/db/upgrade.php
+++ b/db/upgrade.php
@@ -15,14 +15,18 @@
// along with Moodle. If not, see .
/**
+ * The file containing the upgrade functions.
* @package mod_questionnaire
- * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
+ * @copyright 2016 Mike Churchward (mike.churchward@poetopensource.org)
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-defined('MOODLE_INTERNAL') || die();
-
+/**
+ * The module upgrade function.
+ * @param int $oldversion
+ * @return bool
+ */
function xmldb_questionnaire_upgrade($oldversion=0) {
global $CFG, $DB;
@@ -724,11 +728,10 @@ function xmldb_questionnaire_upgrade($oldversion=0) {
}
// Get all of the attempts records, and add the questionnaire id to the corresponding response record.
- $rs = $DB->get_recordset('questionnaire_attempts');
- foreach ($rs as $attempt) {
- $DB->set_field('questionnaire_response', 'questionnaireid', $attempt->qid, ['id' => $attempt->rid]);
- }
- $rs->close();
+ $sql = 'UPDATE {questionnaire_response} qr ' .
+ 'INNER JOIN {questionnaire_attempts} qa ON qr.id = qa.rid ' .
+ 'SET qr.questionnaireid = qa.qid';
+ $DB->execute($sql, []);
// Get all of the response records with a '0' questionnaireid, and extract the questionnaireid from the survey_id field.
$rs = $DB->get_recordset('questionnaire_response', ['questionnaireid' => 0]);
@@ -808,10 +811,204 @@ function xmldb_questionnaire_upgrade($oldversion=0) {
upgrade_mod_savepoint(true, 2018050106, 'questionnaire');
}
- return $result;
+ if ($oldversion < 2018110103) {
+
+ // Define field id to be added to questionnaire_question.
+ $table = new xmldb_table('questionnaire_question');
+ $field = new xmldb_field('extradata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'deleted');
+
+ // Conditionally launch add field id.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Need to move rank named degree choices to the new field.
+ \mod_questionnaire\question\rate::move_all_nameddegree_choices();
+
+ // Questionnaire savepoint reached.
+ upgrade_mod_savepoint(true, 2018110103, 'questionnaire');
+ }
+
+ if ($oldversion < 2020011507) {
+ // This operation might take a while. Cancel PHP timeouts for this.
+ \core_php_time_limit::raise();
+
+ // Making the database tables standard across the board.
+ $table = new xmldb_table('questionnaire');
+ $field1 = new xmldb_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+ $field2 = new xmldb_field('sid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+
+ // Changing fields that are used in indexes and keys generates errors (sometimes). Drop all foreign keys and indexes first;
+ // recreate them after. And, the might be a key or an index, so drop both and fix after.
+ $key1 = new xmldb_key('course', XMLDB_KEY_FOREIGN, ['course'], 'course', ['id']);
+ $dbman->drop_key($table, $key1);
+ $index1 = new xmldb_index('course', XMLDB_INDEX_NOTUNIQUE, ['course']);
+ if ($dbman->index_exists($table, $index1)) {
+ $dbman->drop_index($table, $index1);
+ }
+ $key2 = new xmldb_key('sid', XMLDB_KEY_FOREIGN, ['sid'], 'questionnaire_survey', ['id']);
+ $dbman->drop_key($table, $key2);
+ $index2 = new xmldb_index('sid', XMLDB_INDEX_NOTUNIQUE, ['sid']);
+ if ($dbman->index_exists($table, $index2)) {
+ $dbman->drop_index($table, $index2);
+ }
+ $index3 = new xmldb_index('respview', XMLDB_INDEX_NOTUNIQUE, ['resp_view']);
+ if ($dbman->index_exists($table, $index3)) {
+ $dbman->drop_index($table, $index3);
+ }
+ $dbman->change_field_type($table, $field1);
+ $dbman->change_field_type($table, $field2);
+ $dbman->add_key($table, $key1);
+ $dbman->add_key($table, $key2);
+ $dbman->add_index($table, $index3);
+
+ $table = new xmldb_table('questionnaire_survey');
+ $index = new xmldb_index('courseid', XMLDB_INDEX_NOTUNIQUE, ['courseid']);
+ if ($dbman->index_exists($table, $index)) {
+ $dbman->drop_index($table, $index);
+ }
+ $key = new xmldb_key('courseid', XMLDB_KEY_FOREIGN, ['courseid'], 'course', ['id']);
+ $dbman->drop_key($table, $key);
+ $dbman->add_key($table, $key);
+
+ $table = new xmldb_table('questionnaire_question');
+ $field = new xmldb_field('surveyid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+ $index = new xmldb_index('quest_question_sididx', XMLDB_INDEX_NOTUNIQUE, ['surveyid', 'deleted']);
+ if ($dbman->index_exists($table, $index)) {
+ $dbman->drop_index($table, $index);
+ }
+ $dbman->change_field_type($table, $field);
+ $dbman->add_index($table, $index);
+ $field = new xmldb_field('length', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+ $dbman->change_field_type($table, $field);
+ $field = new xmldb_field('precise', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+ $dbman->change_field_type($table, $field);
+
+ $table = new xmldb_table('questionnaire_quest_choice');
+ $index = new xmldb_index('questionid', XMLDB_INDEX_NOTUNIQUE, ['question_id']);
+ if ($dbman->index_exists($table, $index)) {
+ $dbman->drop_index($table, $index);
+ }
+ $key = new xmldb_key('questionid', XMLDB_KEY_FOREIGN, ['question_id'], 'questionnaire_question', ['id']);
+ $dbman->drop_key($table, $key);
+ $dbman->add_key($table, $key);
+
+ $table = new xmldb_table('questionnaire_response');
+ $index = new xmldb_index('questionnaireid', XMLDB_INDEX_NOTUNIQUE, ['questionnaireid']);
+ if ($dbman->index_exists($table, $index)) {
+ $dbman->drop_index($table, $index);
+ }
+ $key = new xmldb_key('questionnaireid', XMLDB_KEY_FOREIGN, ['questionnaireid'], 'questionnaire', ['id']);
+ $dbman->drop_key($table, $key);
+ $dbman->add_key($table, $key);
+
+ // Postgres and MSSQL have a bug that impacts changing fields with a sequence defined (see bug MDL-68799), so don't change
+ // this for Postgres or MSSQL.
+ if (($DB->get_dbfamily() !== 'postgres') && ($DB->get_dbfamily() !== 'mssql')) {
+ $idfield = new xmldb_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE);
+
+ $table = new xmldb_table('questionnaire_response_bool');
+ $dbman->change_field_type($table, $idfield);
+
+ $table = new xmldb_table('questionnaire_response_date');
+ $dbman->change_field_type($table, $idfield);
+
+ $table = new xmldb_table('questionnaire_response_other');
+ $dbman->change_field_type($table, $idfield);
+
+ $table = new xmldb_table('questionnaire_response_rank');
+ $dbman->change_field_type($table, $idfield);
+
+ $table = new xmldb_table('questionnaire_resp_single');
+ $dbman->change_field_type($table, $idfield);
+
+ $table = new xmldb_table('questionnaire_response_text');
+ $dbman->change_field_type($table, $idfield);
+
+ $table = new xmldb_table('questionnaire_fb_sections');
+ $dbman->change_field_type($table, $idfield);
+
+ $table = new xmldb_table('questionnaire_feedback');
+ $dbman->change_field_type($table, $idfield);
+ }
+
+ $table = new xmldb_table('questionnaire_response_rank');
+ $field = new xmldb_field('rankvalue', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+ $dbman->change_field_type($table, $field);
+
+ $table = new xmldb_table('questionnaire_fb_sections');
+ $field = new xmldb_field('surveyid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+ $key = new xmldb_key('surveyid', XMLDB_KEY_FOREIGN, ['surveyid'], 'questionnaire_survey', ['id']);
+ $dbman->drop_key($table, $key);
+ $dbman->change_field_type($table, $field);
+ $dbman->add_key($table, $key);
+
+ $table = new xmldb_table('questionnaire_feedback');
+ $field = new xmldb_field('sectionid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+ $key = new xmldb_key('sectionid', XMLDB_KEY_FOREIGN, ['sectionid'], 'questionnaire_fb_sections', ['id']);
+ $dbman->drop_key($table, $key);
+ $dbman->change_field_type($table, $field);
+ $dbman->add_key($table, $key);
+
+ $table = new xmldb_table('questionnaire_survey');
+ $field = new xmldb_field('feedbacksections', XMLDB_TYPE_INTEGER, '2', null, null, null, '0');
+ $dbman->change_field_type($table, $field);
+
+ $table = new xmldb_table('questionnaire_dependency');
+ $index = new xmldb_index('questionid', XMLDB_INDEX_NOTUNIQUE, ['questionid']);
+ if ($dbman->index_exists($table, $index)) {
+ $dbman->drop_index($table, $index);
+ }
+ $key = new xmldb_key('questionid', XMLDB_KEY_FOREIGN, ['questionid'], 'questionnaire_question', ['id']);
+ $dbman->drop_key($table, $key);
+ $dbman->add_key($table, $key);
+ $key = new xmldb_key('surveyid', XMLDB_KEY_FOREIGN, ['surveyid'], 'questionnaire_survey', ['id']);
+ $dbman->add_key($table, $key);
+
+ // Questionnaire savepoint reached.
+ upgrade_mod_savepoint(true, 2020011507, 'questionnaire');
+ }
+
+ if ($oldversion < 2020062301) {
+ // Add show progress bar setting.
+ $table = new xmldb_table('questionnaire');
+ $field = new xmldb_field('progressbar', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, 0, 'autonum');
+
+ // Conditionally launch add field.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Questionnaire savepoint reached.
+ upgrade_mod_savepoint(true, 2020062301, 'questionnaire');
+ }
+
+ if ($oldversion < 2022092200) {
+ // Add new slider question type.
+ $exist = $DB->record_exists('questionnaire_question_type', ['typeid' => 11]);
+ if (!$exist) {
+ $questiontype = new stdClass();
+ $questiontype->typeid = 11;
+ $questiontype->type = 'Slider';
+ $questiontype->has_choices = 'n';
+ $questiontype->response_table = 'response_text';
+ $DB->insert_record('questionnaire_question_type', $questiontype);
+ }
+ upgrade_mod_savepoint(true, 2022092200, 'questionnaire');
+ }
+
+ if ($oldversion < 2022121600.02) {
+ // Upgrade for downloadoptions - useridentityfields setting.
+ upgrade_mod_savepoint(true, 2022121600.02, 'questionnaire');
+ }
+
+ return true;
}
-// Supporting functions used once.
+/**
+ * Supporting functions used once.
+ * @return bool
+ */
function questionnaire_upgrade_2007120101() {
global $DB;
diff --git a/drawchart.php b/drawchart.php
index de9a7f8d..ab50fe83 100644
--- a/drawchart.php
+++ b/drawchart.php
@@ -15,16 +15,27 @@
// along with Moodle. If not, see .
/**
+ * Library draw chart function.
* @package mod_questionnaire
* @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-defined('MOODLE_INTERNAL') || die();
-
-function draw_chart($feedbacktype, $charttype=null, $labels,
- $score=null, $allscore=null, $globallabel=null, $groupname, $allresponses) {
+/**
+ * This is the function.
+ * @param string $feedbacktype
+ * @param array $labels
+ * @param string $groupname
+ * @param bool $allresponses
+ * @param null|string $charttype
+ * @param null|array $score
+ * @param null|array $allscore
+ * @param null|string $globallabel
+ * @return string
+ */
+function draw_chart($feedbacktype, $labels, $groupname,
+ $allresponses, $charttype=null, $score=null, $allscore=null, $globallabel=null) {
global $PAGE;
$pageoutput = '';
@@ -564,4 +575,4 @@ function draw_chart($feedbacktype, $charttype=null, $labels,
}
return $pageoutput;
-}
\ No newline at end of file
+}
diff --git a/externallib.php b/externallib.php
index 9470baa0..7715fc1e 100644
--- a/externallib.php
+++ b/externallib.php
@@ -24,9 +24,21 @@
* @since Moodle 3.0
*/
-defined('MOODLE_INTERNAL') || die;
+namespace mod_questionnaire;
+
+defined('MOODLE_INTERNAL') || die();
+
require_once($CFG->libdir . '/externallib.php');
+
+use external_api;
+use external_function_parameters;
+use external_single_structure;
+use external_multiple_structure;
+use external_value;
+use external_warnings;
+
require_once($CFG->dirroot . '/mod/questionnaire/lib.php');
+require_once($CFG->dirroot . '/mod/questionnaire/questionnaire.class.php');
/**
* Questionnaire module external functions
@@ -37,29 +49,31 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
-class mod_questionnaire_external extends \external_api {
+class external extends external_api {
/**
- * Describes the parameters for submit_questionnaire_branching_parameters.
+ * Describes the parameters for submit_questionnaire_response.
*
* @return external_function_parameters
* @since Moodle 3.0
*/
- public static function submit_questionnaire_branching_parameters() {
- return new \external_function_parameters(
+ public static function submit_questionnaire_response_parameters() {
+ return new external_function_parameters(
[
- 'questionnaireid' => new \external_value(PARAM_INT, 'Questionnaire instance id'),
- 'surveyid' => new \external_value(PARAM_INT, 'Survey id'),
- 'userid' => new \external_value(PARAM_INT, 'User id'),
- 'cmid' => new \external_value(PARAM_INT, 'Course module id'),
- 'sec' => new \external_value(PARAM_INT, 'Section number'),
- 'completed' => new \external_value(PARAM_INT, 'Completed survey or not'),
- 'submit' => new \external_value(PARAM_INT, 'Submit survey or not'),
- 'responses' => new \external_multiple_structure(
- new \external_single_structure(
+ 'questionnaireid' => new external_value(PARAM_INT, 'Questionnaire instance id'),
+ 'surveyid' => new external_value(PARAM_INT, 'Survey id'),
+ 'userid' => new external_value(PARAM_INT, 'User id'),
+ 'cmid' => new external_value(PARAM_INT, 'Course module id'),
+ 'sec' => new external_value(PARAM_INT, 'Section number'),
+ 'completed' => new external_value(PARAM_INT, 'Completed survey or not'),
+ 'rid' => new external_value(PARAM_INT, 'Existing response id'),
+ 'submit' => new external_value(PARAM_INT, 'Submit survey or not'),
+ 'action' => new external_value(PARAM_ALPHA, 'Page action'),
+ 'responses' => new external_multiple_structure(
+ new external_single_structure(
[
- 'name' => new \external_value(PARAM_RAW, 'data key'),
- 'value' => new \external_value(PARAM_RAW, 'data value')
+ 'name' => new external_value(PARAM_RAW, 'data key'),
+ 'value' => new external_value(PARAM_RAW, 'data value')
]
),
'The data to be saved', VALUE_DEFAULT, []
@@ -68,7 +82,7 @@ public static function submit_questionnaire_branching_parameters() {
);
}
- /**
+ /**
* Submit questionnaire responses
*
* @param int $questionnaireid the questionnaire instance id
@@ -77,15 +91,15 @@ public static function submit_questionnaire_branching_parameters() {
* @param int $cmid Course module id
* @param int $sec Section number
* @param int $completed Completed survey 1/0
+ * @param int $rid Already in progress response id.
* @param int $submit Submit survey?
+ * @param string $action
* @param array $responses the response ids
* @return array answers information and warnings
- * @since Moodle 3.0
*/
- public static function submit_questionnaire_branching($questionnaireid, $surveyid, $userid,
- $cmid, $sec, $completed, $submit, $responses) {
-
- $params = self::validate_parameters(self::submit_questionnaire_branching_parameters(),
+ public static function submit_questionnaire_response($questionnaireid, $surveyid, $userid, $cmid, $sec, $completed, $rid,
+ $submit, $action, $responses) {
+ self::validate_parameters(self::submit_questionnaire_response_parameters(),
[
'questionnaireid' => $questionnaireid,
'surveyid' => $surveyid,
@@ -93,24 +107,22 @@ public static function submit_questionnaire_branching($questionnaireid, $surveyi
'cmid' => $cmid,
'sec' => $sec,
'completed' => $completed,
+ 'rid' => $rid,
'submit' => $submit,
+ 'action' => $action,
'responses' => $responses
]
);
- if (!$questionnaire = get_questionnaire($params['questionnaireid'])) {
- throw new \moodle_exception("invalidcoursemodule", "error");
- }
- list($course, $cm) = get_course_and_cm_from_instance($questionnaire, 'questionnaire');
+ list($cm, $course, $questionnaire) = questionnaire_get_standard_page_items($cmid);
+ $questionnaire = new \questionnaire($course, $cm, 0, $questionnaire);
$context = \context_module::instance($cm->id);
self::validate_context($context);
require_capability('mod/questionnaire:submit', $context);
- $result = save_questionnaire_data_branching($questionnaireid, $surveyid, $userid, $cmid,
- $sec, $completed, $submit, $responses);
-
+ $result = $questionnaire->save_mobile_data($userid, $sec, $completed, $rid, $submit, $action, $responses);
$result['submitted'] = true;
if (isset($result['warnings']) && !empty($result['warnings'])) {
unset($result['responses']);
@@ -121,18 +133,17 @@ public static function submit_questionnaire_branching($questionnaireid, $surveyi
}
/**
- * Describes the submit_questionnaire_branching return value.
+ * Describes the submit_questionnaire_response return value.
*
- * @return external_multiple_structure
+ * @return external_single_structure
* @since Moodle 3.0
*/
- public static function submit_questionnaire_branching_returns() {
- return new \external_single_structure(
+ public static function submit_questionnaire_response_returns() {
+ return new external_single_structure(
[
- 'submitted' => new \external_value(PARAM_BOOL, 'submitted', true, false, false),
- 'warnings' => new \external_warnings(),
- 'params' => new \external_warnings(),
+ 'submitted' => new external_value(PARAM_BOOL, 'submitted', VALUE_REQUIRED, false, false),
+ 'warnings' => new external_warnings()
]
);
}
-}
\ No newline at end of file
+}
diff --git a/fbsections.php b/fbsections.php
index e35273f4..13f3a8e7 100644
--- a/fbsections.php
+++ b/fbsections.php
@@ -36,15 +36,15 @@
$sectionid = optional_param('sectionid', 0, PARAM_INT);
if (! $cm = get_coursemodule_from_id('questionnaire', $id)) {
- print_error('invalidcoursemodule');
+ throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
}
if (! $course = $DB->get_record("course", ["id" => $cm->course])) {
- print_error('coursemisconf');
+ throw new \moodle_exception('coursemisconf', 'mod_questionnaire');
}
if (! $questionnaire = $DB->get_record("questionnaire", ["id" => $cm->instance])) {
- print_error('invalidcoursemodule');
+ throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
}
// Needed here for forced language courses.
@@ -58,11 +58,11 @@
$SESSION->questionnaire = new stdClass();
}
-$questionnaire = new questionnaire(0, $questionnaire, $course, $cm);
+$questionnaire = new questionnaire($course, $cm, 0, $questionnaire);
if ($sectionid) {
// Get the specified section by its id.
- $feedbacksection = new mod_questionnaire\feedback\section(['id' => $sectionid], $questionnaire->questions);
+ $feedbacksection = new mod_questionnaire\feedback\section($questionnaire->questions, ['id' => $sectionid]);
} else if (!$DB->count_records('questionnaire_fb_sections', ['surveyid' => $questionnaire->sid])) {
// There are no sections currently, so create one.
@@ -75,8 +75,8 @@
} else {
// Get the specified section by section number.
- $feedbacksection = new mod_questionnaire\feedback\section(['surveyid' => $questionnaire->survey->id, 'sectionnum' => $section],
- $questionnaire->questions);
+ $feedbacksection = new mod_questionnaire\feedback\section($questionnaire->questions,
+ ['surveyid' => $questionnaire->survey->id, 'sectionnum' => $section]);
}
// Get all questions that are valid feedback questions.
@@ -94,7 +94,7 @@
$SESSION->questionnaire->current_tab = 'feedback';
if (!$questionnaire->capabilities->editquestions) {
- print_error('nopermissions', 'error', 'mod:questionnaire:editquestions');
+ throw new \moodle_exception('nopermissions', 'mod_questionnaire');
}
// Handle confirmed actions that impact display immediately.
diff --git a/feedback.php b/feedback.php
index 575fe455..cf6c2f50 100644
--- a/feedback.php
+++ b/feedback.php
@@ -33,15 +33,15 @@
$action = optional_param('action', '', PARAM_ALPHA);
if (! $cm = get_coursemodule_from_id('questionnaire', $id)) {
- print_error('invalidcoursemodule');
+ throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
}
if (! $course = $DB->get_record("course", ["id" => $cm->course])) {
- print_error('coursemisconf');
+ throw new \moodle_exception('coursemisconf', 'mod_questionnaire');
}
if (! $questionnaire = $DB->get_record("questionnaire", ["id" => $cm->instance])) {
- print_error('invalidcoursemodule');
+ throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
}
// Needed here for forced language courses.
@@ -53,7 +53,7 @@
if (!isset($SESSION->questionnaire)) {
$SESSION->questionnaire = new stdClass();
}
-$questionnaire = new questionnaire(0, $questionnaire, $course, $cm);
+$questionnaire = new questionnaire($course, $cm, 0, $questionnaire);
// Add renderer and page objects to the questionnaire object for display use.
$questionnaire->add_renderer($PAGE->get_renderer('mod_questionnaire'));
@@ -62,7 +62,7 @@
$SESSION->questionnaire->current_tab = 'feedback';
if (!$questionnaire->capabilities->editquestions) {
- print_error('nopermissions', 'error', 'mod:questionnaire:editquestions');
+ throw new \moodle_exception('nopermissions', 'mod_questionnaire');
}
$feedbackform = new \mod_questionnaire\feedback_form('feedback.php');
@@ -91,7 +91,7 @@
}
if ($settings = $feedbackform->get_data()) {
- if (isset($settings->feedbacksettingsbutton1) || isset($settings->buttongroup)) {
+ if (isset($settings->feedbacksettingsbutton1) || isset($settings->feedbacksettingsbutton2) || isset($settings->buttongroup)) {
if (isset ($settings->feedbackscores)) {
$sdata->feedbackscores = $settings->feedbackscores;
} else {
@@ -125,7 +125,7 @@
}
$sdata->courseid = $settings->courseid;
if (!($sid = $questionnaire->survey_update($sdata))) {
- print_error('couldnotcreatenewsurvey', 'questionnaire');
+ throw new \moodle_exception('couldnotcreatenewsurvey', 'mod_questionnaire');
}
}
diff --git a/grade.php b/grade.php
index 4ea4a2b7..43780fde 100644
--- a/grade.php
+++ b/grade.php
@@ -31,7 +31,7 @@
$id = required_param('id', PARAM_INT);
$cm = get_coursemodule_from_id('questionnaire', $id, 0, false, MUST_EXIST);
if (! $questionnaire = $DB->get_record("questionnaire", array("id" => $cm->instance))) {
- print_error('invalidcoursemodule');
+ throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
}
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
require_login($course, false, $cm);
diff --git a/images/hbartransp.gif b/images/hbartransp.gif
new file mode 100644
index 00000000..31838a05
Binary files /dev/null and b/images/hbartransp.gif differ
diff --git a/index.php b/index.php
index ce65d281..05bce900 100644
--- a/index.php
+++ b/index.php
@@ -17,20 +17,18 @@
/**
* This script lists all the instances of questionnaire in a particular course
*
- * @package mod
- * @subpackage questionnaire
- * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
+ * @package mod_questionnaire
+ * @copyright 2016 Mike Churchward (mike.churchward@poetopensource.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
require_once("../../config.php");
require_once($CFG->dirroot.'/mod/questionnaire/locallib.php');
$id = required_param('id', PARAM_INT);
$PAGE->set_url('/mod/questionnaire/index.php', array('id' => $id));
if (! $course = $DB->get_record('course', array('id' => $id))) {
- print_error('incorrectcourseid', 'questionnaire');
+ throw new \moodle_exception('Filter has not been set.', 'mod_questionnaire');
}
$coursecontext = context_course::instance($id);
require_login($course->id);
@@ -212,4 +210,4 @@
echo html_writer::table($table);
// Finish the page.
-echo $OUTPUT->footer();
\ No newline at end of file
+echo $OUTPUT->footer();
diff --git a/javascript/mobile.js b/javascript/mobile.js
index 1291f57a..e46928da 100644
--- a/javascript/mobile.js
+++ b/javascript/mobile.js
@@ -23,8 +23,11 @@ this.questionsFormErrors = {};
for (const fieldkey in this.CONTENT_OTHERDATA.fields) {
questionsFormFields[fieldkey] = [];
questionsFormFields[fieldkey][0] = '';
- for (const itemid in this.CONTENT_OTHERDATA.questions[this.CONTENT_OTHERDATA.pagenum][this.CONTENT_OTHERDATA.fields[fieldkey].id]) {
- questionsFormFields[fieldkey][0] = this.CONTENT_OTHERDATA.questions[this.CONTENT_OTHERDATA.pagenum][this.CONTENT_OTHERDATA.fields[fieldkey].id][itemid].value;
+ for (const itemid in
+ this.CONTENT_OTHERDATA.questions[this.CONTENT_OTHERDATA.pagenum][this.CONTENT_OTHERDATA.fields[fieldkey].id]) {
+ questionsFormFields[fieldkey][0] =
+ this.CONTENT_OTHERDATA.questions[this.CONTENT_OTHERDATA.pagenum][this.CONTENT_OTHERDATA.fields[fieldkey].id]
+ [itemid].value;
}
if (this.CONTENT_OTHERDATA.fields[fieldkey].required === 'y') {
questionsFormFields[fieldkey][1] = this.Validators.required;
diff --git a/lang/en/questionnaire.php b/lang/en/questionnaire.php
index cb920781..7de8d9f7 100644
--- a/lang/en/questionnaire.php
+++ b/lang/en/questionnaire.php
@@ -23,6 +23,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['accessibility:rate:choice'] = 'Row {$a->rowposition}, {$a->choicetitle}: Column {$a->colposition}, {$a->choiceanswer}.';
$string['action'] = 'Action';
$string['activityoverview'] = 'You have questionnaires that are due';
$string['additionalinfo'] = 'Additional Info';
@@ -37,6 +38,8 @@
$string['alignment_help'] = 'Select buttons alignment: vertical (default) or horizontal.';
$string['alignment_link'] = 'mod/questionnaire/questions#Radio_Buttons';
$string['all'] = 'All';
+$string['allnameddegrees'] = 'Named degrees';
+$string['allnameddegrees_help'] = 'Specify text to display for rate values instead of the number. Leave a value blank to not use.';
$string['alreadyfilled'] = 'You have already filled out this questionnaire for us{$a}. Thank you.';
$string['andaveragevalues'] = 'and average values';
$string['anonymous'] = 'Anonymous';
@@ -55,8 +58,9 @@
$string['autonumberpages'] = 'Auto number pages';
$string['autonumberpagesandquestions'] = 'Auto number pages and questions';
$string['average'] = 'Average';
-$string['averagerank'] = 'Average rank';
$string['averageposition'] = 'Average position';
+$string['averagerank'] = 'Average rank';
+$string['averagesrow'] = 'Averages (where applicable):';
$string['bodytext'] = 'Body text';
$string['boxesnbexact'] = 'exactly {$a} box(es).';
$string['boxesnbmax'] = 'a maximum of {$a} box(es).';
@@ -84,14 +88,15 @@
$string['checknotstarted'] = 'Select not started';
$string['checkstarted'] = 'Select started';
$string['clicktoswitch'] = '(click to switch)';
+$string['closebeforeopen'] = 'You have specified a close date before the open date.';
$string['closed'] = 'The questionnaire was closed on {$a}. Thanks.';
-$string['closedate'] = 'Use Close Date';
+$string['closedate'] = 'Allow responses until';
$string['closeson'] = 'Questionnaire closes on {$a}';
-$string['closedate_help'] = 'You can specify a date to close the questionnaire here. Check the check box, and select the date and time you want.
- Users will not be able to fill out the questionnaire after that date. If this is not selected, it will never be closed.';
$string['completionsubmit'] = 'Student must submit this questionnaire to complete it';
$string['condition'] = 'Condition';
$string['confalts'] = '- OR - Confirmation page';
+$string['configemailreporting'] = 'Allow reports by email';
+$string['configemailreportinglong'] = 'Enables options for some reports to be emailed directly to specified email addresses.';
$string['configusergraph'] = 'Display charts for "Personality Test" feedback';
$string['configusergraphlong'] = 'Use the Rgraph library to display "Personality Test" feedback charts.';
$string['configmaxsections'] = 'Maximum feedback sections';
@@ -113,10 +118,14 @@
$string['createcontent_help'] = 'Select one of the radio button options. \'Create new\' is the default.';
$string['createcontent_link'] = 'mod/questionnaire/mod#Content_Options';
$string['createnew'] = 'Create new';
+$string['centerlabel'] = 'Centre label';
$string['date'] = 'Date';
$string['date_help'] = 'Use this question type if you expect the response to be a correctly formatted date.';
$string['date_link'] = 'mod/questionnaire/questions#Date';
-$string['dateformatting'] = 'Use the day/month/year format, e.g. for March 14th, 1945: 14/3/1945';
+$string['dateformatting'] = 'Use the year-month-day format, e.g. for March 4th, 1945: 1945-03-04';
+// Prior to release 3.6.0, you could specify an input date format in the above string. Now, the format must be as below. This
+// string is used now in case sites modified the above string.
+$string['strictdateformatting'] = 'Enter the date using the date picker below.';
$string['deleteallresponses'] = 'Delete ALL Responses';
$string['deletecurrentquestion'] = 'Delete question {$a}';
$string['deletedallgroupresp'] = 'Deleted ALL Responses in group {$a}';
@@ -135,10 +144,12 @@
$string['directwarnings'] = 'Direct dependencies to this question will be removed. This will affect:';
$string['displaymethod'] = 'Display method not defined for question.';
$string['download'] = 'Download';
-$string['downloadtextformat'] = 'Download as CSV';
-$string['downloadtextformat_help'] = 'This feature enables you to save all the responses of a questionnaire to a text file (CSV).
- This file can then be imported into a spreadsheet (e.g. MS Excel or Open Office Calc) or a statistical package for further processing the data.';
+$string['downloadpdf'] = 'Download PDF';
+$string['downloadtextformat'] = 'Download';
+$string['downloadtextformat_help'] = 'This feature enables you to download questionnaire responses in a file format of your choice.
+ The file can then be opened in a spreadsheet program (e.g. MS Excel or Open Office Calc) or a statistical package for further processing.';
$string['downloadtextformat_link'] = 'mod/questionnaire/report#Download_in_text_format';
+$string['downloadtypes'] = 'Report type';
$string['dropdown'] = 'Dropdown Box';
$string['dropdown_help'] = 'There is no real advantage to using the Dropdown Box over using the Radio Buttons
except perhaps for longish lists of options, to save screen space.';
@@ -148,6 +159,15 @@
$string['editingquestionnaire'] = 'Editing Questionnaire Settings';
$string['editquestion'] = 'Editing {$a} question';
$string['email'] = 'Email';
+$string['emailextra'] = 'Send download to emails';
+$string['emailextra_help'] = 'Will send the download file to the listed email addresses, separated by commas. Note that NO security or privacy checking is done.
+ \'allowemailreporting\' must be enabled in module settings to access this.';
+$string['emailsnotspecified'] = 'No email(s) were specified.';
+$string['emailroles'] = 'Send download to roles';
+$string['emailroles_help'] = 'Will send the download file to all roles with "mod/questionnaire:submissionnotification" capability via email.
+ \'allowemailreporting\' must be enabled in module settings to access this.';
+$string['emailsend'] = 'Send reports';
+$string['emailssent'] = 'Downloads sent to specified email(s).';
$string['errnewname'] = 'Sorry, name already in use. Pick a new name.';
$string['erroropening'] = 'Error opening questionnaire.';
$string['errortable'] = 'Error system table corrupt.';
@@ -196,7 +216,8 @@
$string['feedbacknotes_help'] = 'Text entered here will be displayed to the respondents at the end of their Feedback Report';
$string['feedbackoptions'] = 'Feedback options';
$string['feedbackoptions_help'] = 'Feedback options are available if your questionnaire contains the following question types and question settings:
-Radio buttons; Dropdown box; Yes/No; or Rate (normal or Osgood scale). Those questions must be set as Required, their Question Name field must NOT be empty and the Possible answers choices must contain a value.';
+Radio buttons; Dropdown box; Yes/No; Rate (normal or Osgood scale) or Slider. Those questions must be set as Required, their Question Name field must NOT be empty and the Possible answers choices must contain a value.
+Slider questions must NOT use a negative value for the Minimum slider range.';
$string['feedbackoptions_link'] = 'mod/questionnaire/personality_test';
$string['feedbackremovequestionfromsection'] = 'This question is part of feedback section [{$a}]';
$string['feedbackremovesection'] = 'Removing this question will completely remove feedback section [{$a}]';
@@ -234,6 +255,7 @@
$string['headingtext'] = 'Heading text';
$string['horizontal'] = 'Horizontal';
$string['id'] = 'ID';
+$string['includerankaverages'] = 'Include rank question averages';
$string['includechoicecodes'] = 'Include choice codes';
$string['includechoicetext'] = 'Include choice text';
$string['includeincomplete'] = "Include incomplete responses";
@@ -244,12 +266,19 @@
$string['invalidresponserecord'] = 'Invalid response record specified.';
$string['invalidsurveyid'] = 'Invalid questionnaire ID.';
$string['invalidsectionid'] = 'Invalid feedback section specified.';
+$string['invalidrange'] = 'The maximum slider value must be greater than the minimum slider value.';
+$string['invalidstartingvalue'] = 'The starting value must be equal to or between the minimum and maximum values. For example, if using a scale of 1-10, the starting value could be 5.';
+$string['invalidminmaxrange'] = 'This question type supports an absolute maximum range of -100 to +100. We expect the vast majority of questionnaire designs to use a range of 1-10 or -10 to +10.';
+$string['invalidincrement'] = 'Note that the value increments must be lower than the maximum value. For example, if a scale of 1-10, the increment value would probably be 1.';
$string['indirectwarnings'] = 'This list shows the indirect dependent questions and the remaining dependencies for direct dependent questions:';
$string['kindofratescale'] = 'Type of rate scale';
$string['kindofratescale_help'] = 'Right-click on the More Help link below.';
$string['kindofratescale_link'] = 'mod/questionnaire/questions#Type_of_rate_scale';
$string['lastrespondent'] = 'Last Respondent';
$string['length'] = 'Length';
+$string['leftlabel'] = 'Left label';
+$string['leftpart'] = '{$a->min} is {$a->leftlabel}';
+$string['leftpartdefault'] = '{$a->min} is minimum slider range';
$string['managequestions'] = 'Manage questions';
$string['managequestions_help'] = 'In the Manage questions section of the Edit Questions page, you can conduct a number of operations on a Questionnaire\'s questions.';
$string['managequestions_link'] = 'mod/questionnaire/questions#Manage_questions';
@@ -269,6 +298,10 @@
Default values are 20 characters for the Input Box width and 25 characters for the maximum length of text entered.';
$string['messageprovider:message'] = 'Questionnaire reminder';
$string['messageprovider:notification'] = 'Questionnaire submission';
+$string['middlepart'] = ', {$a->centreval} is {$a->middlelabel}';
+$string['middlepartdefault'] = ', {$a->centreval} is average';
+$string['middlepartwithtwovalues'] = ', {$a->centreval1} and {$a->centreval2} are {$a->middlelabel}';
+$string['middlepartwithtwovaluesdefault'] = ', {$a->centreval1} and {$a->centreval2} are average';
$string['minforcedresponses'] = 'Min. forced responses';
$string['minforcedresponses_help'] = 'Use these parameters to force respondent to tick a minimum of **Min.** boxes and a maximum of **Max.** check boxes. To
force an exact number of check boxes to be ticked, set **Min.** and **Max.** to the same value. If only a min or a max value is desired, just leave the other
@@ -276,15 +309,20 @@
respondent does not comply with your requirements. Obviously you should make any requirements clear to the respondent either in the general instructions of
your Questionnaire or in the text of relevant questions.';
$string['misconfigured'] = 'Course is misconfigured';
-$string['missingquestion'] = 'Please answer Required question ';
-$string['missingquestions'] = 'Please answer Required questions: ';
+$string['missingquestion'] = 'Please answer required question ';
+$string['missingquestions'] = 'Please answer required questions: ';
$string['modulename'] = 'Questionnaire';
$string['modulename_help'] = 'The questionnaire module allows you to construct surveys using a variety of question types, for the purpose of gathering data from users.';
+$string['modulename_link'] = 'mod/questionnaire/view';
$string['modulenameplural'] = 'Questionnaires';
$string['movedisabled'] = 'This item cannot be moved';
$string['myresponses'] = 'All your responses';
$string['myresponsetitle'] = 'Your {$a} response(s)';
$string['myresults'] = 'Your Results';
+$string['minrange'] = 'Minimum slider range (left)';
+$string['minrange_help'] = 'Set the minimum value of the range on the left-hand side. It defaults to 1, but can set as low as -100. If you use a negative number (-100 to -1), the right-hand maximum will be expressed with a positive (+) sign.';
+$string['maxrange'] = 'Maximum slider range (right)';
+$string['maxrange_help'] = 'Set the maximum value of the range on the right-hand side. It defaults to 100, but it could be any number between 1-100. If the minimum value for the left-hand is a negative value, the maximum range will be expressed with a positive (+) sign.';
$string['name'] = 'Name';
$string['navigate'] = 'Allow branching questions';
$string['navigate_help'] = 'Enable Yes/No and Radio Buttons questions to have Child questions dependent on their choices in your questionnaire.';
@@ -332,9 +370,8 @@
$string['numeric'] = 'Numeric';
$string['numeric_help'] = 'Use this question type if you expect the response to be a correctly formatted number.';
$string['of'] = 'of';
-$string['opendate'] = 'Use Open Date';
-$string['opendate_help'] = 'You can specify a date to open the questionnaire here. Check the check box, and select the date and time you want.
- Users will not be able to fill out the questionnaire before that date. If this is not selected, it will be open immediately.';
+$string['openafterclose'] = 'You have specified an open date after the close date';
+$string['opendate'] = 'Allow responses from';
$string['option'] = 'option {$a}';
$string['optional'] = 'Optional - At least one of this dependencies has to be fulfilled.';
$string['optionalname'] = 'Question Name';
@@ -431,6 +468,10 @@
$string['privacy:metadata:questionnaire_resp_single:choice_id'] = 'The ID of the choice record for this response.';
$string['private'] = 'Private';
+$string['progressbar'] = 'Show progress bar';
+$string['progresshelp'] = 'Progress Bar';
+$string['progresshelp_help'] = 'Move on to the next page to fill up the progress bar';
+$string['progressbar_info'] = 'Questionnaire {$a} complete.';
$string['public'] = 'Public';
$string['publiccopy'] = 'Copy:';
$string['publicoriginal'] = 'Original:';
@@ -525,7 +566,11 @@
$string['resume_link'] = 'mod/questionnaire/mod#Save/Resume_answers';
$string['resumesurvey'] = 'Resume questionnaire';
$string['return'] = 'Return';
+$string['rightlabel'] = 'Right label';
+$string['rightpart'] = ' and {$a->max} is {$a->rightlabel}';
+$string['rightpartdefault'] = ' and {$a->max} is maximum slider range';
$string['save'] = 'Save';
+$string['save_and_exit'] = 'Save and exit';
$string['saveasnew'] = 'Save as New Question';
$string['savedbutnotsubmitted'] = 'This questionnaire has been saved but not yet submitted.';
$string['savedprogress'] = 'Your progress has been saved. You may return at any time to complete this questionnaire.';
@@ -546,7 +591,7 @@
$string['send_message_to'] = 'Send message to:';
$string['sendemail_help'] = 'Sends a copy of each submission to the specified address or addresses.
You can provide more than one address by separating them with commas.
-Leave blank for no email backup.';
+Leave blank for no email backup. \'allowemailreporting\' must be enabled in module settings to access this.';
$string['set'] = 'set';
$string['settings'] = 'Settings';
$string['settingssaved'] = 'Settings saved';
@@ -568,9 +613,16 @@
$string['subtitle_help'] = 'Subtitle of this questionnaire. Appears below the title on the first page only.';
$string['subject'] = 'Subject';
$string['summary'] = 'Summary';
+$string['summaryreportattached'] = 'Questionnaire summary report attached';
$string['surveynotexists'] = 'questionnaire does not exist.';
$string['surveyowner'] = 'You must be a questionnaire owner to perform this operation.';
$string['surveyresponse'] = 'Response from questionnaire';
+$string['slider'] = 'Slider';
+$string['slider_help'] = 'The slider question allows respondents to select a value from a continuous range by dragging a slider between two extremes. A centre value can also be set.';
+$string['startingvalue'] = 'Slider starting value';
+$string['startingvalue_help'] = 'The slider starting value specifies where the slider should first appear for respondents. It defaults to 1 because the range is unknown. You may wish to start it in the centre of the range by giving a central value (a range of 1-100 has a centre value of 50).';
+$string['stepvalue'] = 'Slider increment value';
+$string['stepvalue_help'] = 'The slider increment value specifies how finely you wish respondents to indicate their response in the range. The question defaults to a range of 1-100 with an increment of one, allowing respondents to give values of 70, 71, 72, 73, 74 etc. But you could instead set increments of five, allowing respondents to give values of 60, 65, 70, 75, 80 etc., or even just a range of 1-10 with increments of 1.';
$string['template'] = 'Template';
$string['templatenotviewable'] = 'Template questionnaires are not viewable.';
$string['text'] = 'Question Text';
@@ -607,7 +659,7 @@
$string['usetemplate'] = 'Use template';
$string['vertical'] = 'Vertical';
$string['view'] = 'View';
-$string['viewallresponses'] = 'View All Responses';
+$string['viewallresponses'] = 'View all responses';
$string['viewallresponses_help'] = 'If the questionnaire is set to **Group Mode**: *Visible groups*, or is set to *Separate groups* and the current user
has the *moodle/site:accessallgroups* capability (in the current context), and groups have been defined in the current course,
then the user has access to a dropdown list of groups. This dropdown list enables the user to "filter" the questionnaire responses by groups.
@@ -618,8 +670,9 @@
$string['viewindividualresponse'] = 'Individual responses';
$string['viewindividualresponse_help'] = 'Click on the respondents\' names in the list below to view their individual responses.';
$string['viewresponses'] = 'All responses ({$a})';
-$string['viewyourresponses'] = 'Your responses- view {$a}';
+$string['viewyourresponses'] = 'View your response(s)';
$string['warning'] = 'Warning, error encountered.';
+$string['where'] = 'where ';
$string['wronganswers'] = 'There is something wrong with your answers (see below)';
$string['wrongdateformat'] = 'The date entered: {$a} does not correspond to the format shown in the example.';
$string['wrongdaterange'] = 'ERROR! The year must be set in the 1902 to 2037 range.';
@@ -627,7 +680,8 @@
$string['wrongformats'] = 'There is something wrong with your answer to questions: ';
$string['yesno'] = 'Yes/No';
$string['yesno_help'] = 'Simple Yes/No question.';
-$string['yourresponse'] = 'Your response';
-$string['yourresponses'] = 'Your responses';
+$string['yourresponse'] = 'View your response(s)';
+$string['yourresponses'] = 'View your response(s)';
$string['crontask'] = 'Questionnaire cleanup job';
-$string['selectdropdowntext'] = 'Select One';
\ No newline at end of file
+$string['nopermissions'] = 'Sorry, but you do not currently have permissions to view this page or perform this action.';
+$string['unanswered'] = 'Unanswered';
diff --git a/lib.php b/lib.php
index efdb8af6..dd7f2ea0 100644
--- a/lib.php
+++ b/lib.php
@@ -14,20 +14,25 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-// Library of functions and constants for module questionnaire.
-
/**
+ * Library of functions and constants for module questionnaire.
* @package mod_questionnaire
- * @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
+ * @copyright 2016 Mike Churchward (mike.churchward@poetopensource.org)
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-defined('MOODLE_INTERNAL') || die();
-
+/** This may no longer be needed. */
define('QUESTIONNAIRE_RESETFORM_RESET', 'questionnaire_reset_data_');
+
+/** This may no longer be needed. */
define('QUESTIONNAIRE_RESETFORM_DROP', 'questionnaire_drop_questionnaire_');
+/**
+ * Library supports implementation.
+ * @param string $feature
+ * @return bool|null
+ */
function questionnaire_supports($feature) {
switch($feature) {
case FEATURE_BACKUP_MOODLE2:
@@ -48,24 +53,36 @@ function questionnaire_supports($feature) {
return true;
case FEATURE_SHOW_DESCRIPTION:
return true;
-
+ case FEATURE_MOD_PURPOSE:
+ return MOD_PURPOSE_COMMUNICATION;
default:
return null;
}
}
/**
+ * Return any extra capabilities.
* @return array all other caps used in module
*/
function questionnaire_get_extra_capabilities() {
return array('moodle/site:accessallgroups');
}
-function get_questionnaire($questionnaireid) {
+/**
+ * Implementation of get_instance.
+ * @param int $questionnaireid
+ * @return false|mixed|stdClass
+ */
+function questionnaire_get_instance($questionnaireid) {
global $DB;
return $DB->get_record('questionnaire', array('id' => $questionnaireid));
}
+/**
+ * Implementation of add_instance.
+ * @param stdClass $questionnaire
+ * @return bool|int
+ */
function questionnaire_add_instance($questionnaire) {
// Given an object containing all the necessary data,
// (defined by the form in mod.html) this function
@@ -75,13 +92,14 @@ function questionnaire_add_instance($questionnaire) {
require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php');
require_once($CFG->dirroot.'/mod/questionnaire/locallib.php');
- // Check the realm and set it to the survey if it's set.
+ $copyfiles = false;
+ // Check the realm and set it to the survey if it's set.
if (empty($questionnaire->sid)) {
// Create a new survey.
$course = get_course($questionnaire->course);
$cm = new stdClass();
- $qobject = new questionnaire(0, $questionnaire, $course, $cm);
+ $qobject = new questionnaire($course, $cm, 0, $questionnaire);
if ($questionnaire->create == 'new-0') {
$sdata = new stdClass();
@@ -98,7 +116,7 @@ function questionnaire_add_instance($questionnaire) {
$sdata->feedbacknotes = '';
$sdata->courseid = $course->id;
if (!($sid = $qobject->survey_update($sdata))) {
- print_error('couldnotcreatenewsurvey', 'questionnaire');
+ throw new \moodle_exception('couldnotcreatenewsurvey', 'mod_questionnaire');
}
} else {
$copyid = explode('-', $questionnaire->create);
@@ -116,6 +134,9 @@ function questionnaire_add_instance($questionnaire) {
// All new questionnaires should be created as "private".
// Even if they are *copies* of public or template questionnaires.
$DB->set_field('questionnaire_survey', 'realm', 'private', array('id' => $sid));
+
+ // Need to copy any files from the old questionnaire instance to the new one.
+ $questionnaire->copyid = $copyid;
}
// If the survey has dependency data, need to set the questionnaire to allow dependencies.
if ($DB->count_records('questionnaire_dependency', ['surveyid' => $sid]) > 0) {
@@ -127,14 +148,6 @@ function questionnaire_add_instance($questionnaire) {
$questionnaire->timemodified = time();
- // May have to add extra stuff in here.
- if (empty($questionnaire->useopendate)) {
- $questionnaire->opendate = 0;
- }
- if (empty($questionnaire->useclosedate)) {
- $questionnaire->closedate = 0;
- }
-
if ($questionnaire->resume == '1') {
$questionnaire->resume = 1;
} else {
@@ -154,9 +167,12 @@ function questionnaire_add_instance($questionnaire) {
return $questionnaire->id;
}
-// Given an object containing all the necessary data,
-// (defined by the form in mod.html) this function
-// will update an existing instance with new data.
+/**
+ * Given an object containing all the necessary data, (defined by the form in mod.html) this function will update an existing
+ * instance with new data.
+ * @param stdClass $questionnaire
+ * @return bool
+ */
function questionnaire_update_instance($questionnaire) {
global $DB, $CFG;
require_once($CFG->dirroot.'/mod/questionnaire/locallib.php');
@@ -169,14 +185,6 @@ function questionnaire_update_instance($questionnaire) {
$questionnaire->timemodified = time();
$questionnaire->id = $questionnaire->instance;
- // May have to add extra stuff in here.
- if (empty($questionnaire->useopendate)) {
- $questionnaire->opendate = 0;
- }
- if (empty($questionnaire->useclosedate)) {
- $questionnaire->closedate = 0;
- }
-
if ($questionnaire->resume == '1') {
$questionnaire->resume = 1;
} else {
@@ -195,9 +203,11 @@ function questionnaire_update_instance($questionnaire) {
return $DB->update_record("questionnaire", $questionnaire);
}
-// Given an ID of an instance of this module,
-// this function will permanently delete the instance
-// and any data that depends on it.
+/**
+ * Given an ID of an instance of this module, this function will permanently delete the instance and any data that depends on it.
+ * @param int $id
+ * @return bool
+ */
function questionnaire_delete_instance($id) {
global $DB, $CFG;
require_once($CFG->dirroot.'/mod/questionnaire/locallib.php');
@@ -229,15 +239,54 @@ function questionnaire_delete_instance($id) {
return $result;
}
-// Return a small object with summary information about what a
-// user has done with a given particular instance of this module
-// Used for user activity reports.
-// $return->time = the time they did it
-// $return->info = a short text description.
/**
- * $course and $mod are unused, but API requires them. Suppress PHPMD warning.
+ * Add a get_coursemodule_info function in case any questionnaire type wants to add 'extra' information
+ * for the course (see resource).
+ *
+ * Given a course_module object, this function returns any "extra" information that may be needed
+ * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
*
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @param stdClass $coursemodule The coursemodule object (record).
+ * @return cached_cm_info An object on information that the courses
+ * will know about (most noticeably, an icon).
+ */
+function questionnaire_get_coursemodule_info($coursemodule) {
+ global $DB;
+
+ $questionnaire = $DB->get_record('questionnaire',
+ array('id' => $coursemodule->instance), 'id, name, intro, introformat,
+ completionsubmit');
+ if (!$questionnaire) {
+ return null;
+ }
+
+ $info = new cached_cm_info();
+ $info->customdata = (object)[];
+
+ if ($coursemodule->showdescription) {
+ // Convert intro to html. Do not filter cached version, filters run at display time.
+ // Based on the function quiz_get_coursemodule_info() in the quiz module.
+ $info->content = format_module_intro('questionnaire', $questionnaire, $coursemodule->id, false);
+ }
+
+ // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
+ if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
+ $info->customdata->customcompletionrules['completionsubmit'] = $questionnaire->completionsubmit;
+ }
+ return $info;
+}
+
+/**
+ * Return a small object with summary information about what a user has done with a given particular instance of this module.
+ * Used for user activity reports.
+ * $return->time = the time they did it
+ * $return->info = a short text description.
+ * $course and $mod are unused, but API requires them. Suppress PHPMD warning.
+ * @param stdClass $course
+ * @param stdClass $user
+ * @param stdClass $mod
+ * @param stdClass $questionnaire
+ * @return stdClass
*/
function questionnaire_user_outline($course, $user, $mod, $questionnaire) {
global $CFG;
@@ -259,12 +308,15 @@ function questionnaire_user_outline($course, $user, $mod, $questionnaire) {
return $result;
}
-// Print a detailed representation of what a user has done with
-// a given particular instance of this module, for user activity reports.
/**
+ * Print a detailed representation of what a user has done with a given particular instance of this module, for user
+ * activity reports.
* $course and $mod are unused, but API requires them. Suppress PHPMD warning.
- *
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @param stdClass $course
+ * @param stdClass $user
+ * @param stdClass $mod
+ * @param stdClass $questionnaire
+ * @return bool
*/
function questionnaire_user_complete($course, $user, $mod, $questionnaire) {
global $CFG;
@@ -285,24 +337,25 @@ function questionnaire_user_complete($course, $user, $mod, $questionnaire) {
return true;
}
-// Given a course and a time, this module should find recent activity
-// that has occurred in questionnaire activities and print it out.
-// Return true if there was output, or false is there was none.
/**
+ * Given a course and a time, this module should find recent activity that has occurred in questionnaire activities and print it
+ * out.
+ * Return true if there was output, or false is there was none.
* $course, $isteacher and $timestart are unused, but API requires them. Suppress PHPMD warning.
- *
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @param stdClass $course
+ * @param bool $isteacher
+ * @param int $timestart
+ * @return false
*/
function questionnaire_print_recent_activity($course, $isteacher, $timestart) {
return false; // True if anything was printed, otherwise false.
}
-// Must return an array of grades for a given instance of this module,
-// indexed by user. It also returns a maximum allowed grade.
/**
+ * Must return an array of grades for a given instance of this module, indexed by user. It also returns a maximum allowed grade.
* $questionnaireid is unused, but API requires it. Suppress PHPMD warning.
- *
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @param int $questionnaireid
+ * @return null
*/
function questionnaire_grades($questionnaireid) {
return null;
@@ -311,7 +364,7 @@ function questionnaire_grades($questionnaireid) {
/**
* Return grade for given user or all users.
*
- * @param int $questionnaireid id of assignment
+ * @param stdClass $questionnaire
* @param int $userid optional user id, 0 means all users
* @return array array of grades, false if none
*/
@@ -327,18 +380,15 @@ function questionnaire_get_user_grades($questionnaire, $userid=0) {
$sql = "SELECT r.id, u.id AS userid, r.grade AS rawgrade, r.submitted AS dategraded, r.submitted AS datesubmitted
FROM {user} u, {questionnaire_response} r
WHERE u.id = r.userid AND r.questionnaireid = $questionnaire->id AND r.complete = 'y' $usersql";
- return $DB->get_records_sql($sql, $params);
+ return $DB->get_records_sql($sql, $params) ?? [];
}
/**
- * Update grades by firing grade_updated event
- *
- * @param object $assignment null means all assignments
- * @param int $userid specific user only, 0 mean all
- *
+ * Update grades by firing grade_updated event.
* $nullifnone is unused, but API requires it. Suppress PHPMD warning.
- *
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @param stdClass $questionnaire
+ * @param int $userid
+ * @param bool $nullifnone
*/
function questionnaire_update_grades($questionnaire=null, $userid=0, $nullifnone=true) {
global $CFG, $DB;
@@ -388,8 +438,8 @@ function questionnaire_update_grades($questionnaire=null, $userid=0, $nullifnone
/**
* Create grade item for given questionnaire
*
- * @param object $questionnaire object with extra cmidnumber
- * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
+ * @param stdClass $questionnaire object with extra cmidnumber
+ * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
* @return int 0 if ok, error code otherwise
*/
function questionnaire_grade_item_update($questionnaire, $grades = null) {
@@ -410,12 +460,12 @@ function questionnaire_grade_item_update($questionnaire, $grades = null) {
if ($questionnaire->grade > 0) {
$params['gradetype'] = GRADE_TYPE_VALUE;
- $params['grademax'] = $questionnaire->grade;
- $params['grademin'] = 0;
+ $params['grademax'] = $questionnaire->grade;
+ $params['grademin'] = 0;
} else if ($questionnaire->grade < 0) {
$params['gradetype'] = GRADE_TYPE_SCALE;
- $params['scaleid'] = -$questionnaire->grade;
+ $params['scaleid'] = -$questionnaire->grade;
} else if ($questionnaire->grade == 0) { // No Grade..be sure to delete the grade item if it exists.
$grades = null;
@@ -439,13 +489,11 @@ function questionnaire_grade_item_update($questionnaire, $grades = null) {
* it it has support for grading and scales. Commented code should be
* modified if necessary. See forum, glossary or journal modules
* as reference.
- * @param $questionnaireid int
- * @param $scaleid int
+ * @param int $questionnaireid
+ * @param int $scaleid
* @return boolean True if the scale is used by any questionnaire
*
* Function parameters are unused, but API requires them. Suppress PHPMD warning.
- *
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
function questionnaire_scale_used ($questionnaireid, $scaleid) {
return false;
@@ -455,672 +503,27 @@ function questionnaire_scale_used ($questionnaireid, $scaleid) {
* Checks if scale is being used by any instance of questionnaire
*
* This is used to find out if scale used anywhere
- * @param $scaleid int
+ * @param int $scaleid
* @return boolean True if the scale is used by any questionnaire
*
* Function parameters are unused, but API requires them. Suppress PHPMD warning.
- *
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
function questionnaire_scale_used_anywhere($scaleid) {
return false;
}
-/**
- * Get questionnaire data
- * for mobile only kurvin hendricks
- *
- * @global object $DB
- * @param int $cmid
- * @param int|bool $userid
- * @return array
- * @throws moodle_exception
- */
-function get_questionnaire_data($cmid, $userid = false) {
- global $DB, $USER;
- if ($q = get_coursemodule_from_id('questionnaire', $cmid)) {
- if (!$questionnaire = get_questionnaire($q->instance)) {
- throw new \moodle_exception("invalidcoursemodule", "error");
- }
- }
-
- $resumedsql = 'SELECT id FROM '
- . '{questionnaire_response} '
- . ' WHERE questionnaireid = ? AND userid = ? AND complete = ? AND submitted <= ?';
- $params = ['userid' => $userid, 'questionnaireid' => $q->instance, 'complete' => 'y'];
- $time = time();
- $ret = [
- 'questionnaire' => [
- 'id' => $questionnaire->id,
- 'name' => format_string($questionnaire->name),
- 'intro' => $questionnaire->intro,
- 'userid' => intval($userid ? $userid : $USER->id),
- 'questionnaireid' => intval($questionnaire->sid),
- 'autonumpages' => in_array($questionnaire->autonum, [1, 2]),
- 'autonumquestions' => in_array($questionnaire->autonum, [1, 3])
- ],
- 'response' => [
- 'id' => 0,
- 'questionnaireid' => 0,
- 'submitted' => 0,
- 'complete' => 'n',
- 'grade' => 0,
- 'userid' => 0,
- 'fullname' => '',
- 'userdate' => '',
- ],
- 'answered' => [],
- 'fields' => [],
- 'responses' => [],
- 'questionscount' => 0,
- 'pagescount' => 1,
- 'resumed' => $DB->get_records_sql($resumedsql,
- [$q->instance, $USER->id, 'n', ($time - (60 * 10))]),
- 'completed' => $DB->record_exists('questionnaire_response', $params),
- ];
- $sql = 'SELECT qq.*,qqt.response_table FROM '
- . '{questionnaire_question} qq LEFT JOIN {questionnaire_question_type} qqt '
- . 'ON qq.type_id = qqt.typeid WHERE qq.surveyid = ? AND qq.deleted = ? '
- . 'ORDER BY qq.position';
-
- // Building dataset here, will build uncomplete questions here.
- if ($questions = $DB->get_records_sql($sql, [$questionnaire->sid, 'n'])) {
- require_once('classes/question/base.php');
- $pagenum = 1;
- $context = \context_module::instance($cmid);
- $qnum = 0;
- foreach ($questions as $question) {
- $ret['questionscount']++;
- $qnum++;
- $fieldkey = 'response_'.$question->type_id.'_'.$question->id;
- $options = ['noclean' => true, 'para' => false, 'filter' => true,
- 'context' => $context, 'overflowdiv' => true];
- if ($question->type_id != QUESPAGEBREAK) {
- $ret['questionsinfo'][$pagenum][$question->id] =
- $ret['fields'][$fieldkey] = [
- 'id' => $question->id,
- 'surveyid' => $question->surveyid,
- 'name' => $question->name,
- 'type_id' => $question->type_id,
- 'length' => $question->length,
- 'content' => ($ret['questionnaire']['autonumquestions'] ? '' : '') . format_text(file_rewrite_pluginfile_urls(
- $question->content, 'pluginfile.php', $context->id,
- 'mod_questionnaire', 'question', $question->id),
- FORMAT_HTML, $options),
- 'content_stripped' => strip_tags($question->content),
- 'required' => $question->required,
- 'deleted' => $question->deleted,
- 'response_table' => $question->response_table,
- 'fieldkey' => $fieldkey,
- 'precise' => $question->precise,
- 'qnum' => $qnum,
- 'errormessage' => get_string('required') . ': ' . $question->name,
- ];
- }
- $std = new \stdClass();
- $std->id = $std->choice_id = 0;
- $std->question_id = $question->id;
- $std->content = '';
- $std->value = null;
- switch ($question->type_id) {
- case QUESYESNO: // Yes/No bool.
- $stdyes = new \stdClass();
- $stdyes->id = 1;
- $stdyes->choice_id = 'y';
- $stdyes->question_id = $question->id;
- $stdyes->value = null;
- $stdyes->content = get_string('yes');
- $stdyes->isbool = true;
- if ($ret['questionsinfo'][$pagenum][$question->id]['required']) {
- $stdyes->value = 'y';
- $stdyes->firstone = true;
- }
- $ret['questions'][$pagenum][$question->id][1] = $stdyes;
- $stdno = new \stdClass();
- $stdno->id = 0;
- $stdno->choice_id = 'n';
- $stdno->question_id = $question->id;
- $stdno->value = null;
- $stdno->content = get_string('no');
- $stdno->isbool = true;
- $ret['questions'][$pagenum][$question->id][0] = $stdno;
- $ret['questionsinfo'][$pagenum][$question->id]['isbool'] = true;
- break;
- case QUESTEXT: // Text.
- case QUESESSAY: // Essay.
- $ret['questions'][$pagenum][$question->id][0] = $std;
- $ret['questionsinfo'][$pagenum][$question->id]['istextessay'] = true;
- break;
- case QUESRADIO: // Radiobutton.
- $ret['questionsinfo'][$pagenum][$question->id]['isradiobutton'] = true;
- $excludes = [];
- if ($items = $DB->get_records('questionnaire_quest_choice',
- ['question_id' => $question->id])) {
-
- foreach ($items as $item) {
- if (!in_array($item->id, $excludes)) {
- $item->choice_id = $item->id;
- if ($item->value == null) {
- $item->value = '';
- }
- $ret['questions'][$pagenum][$question->id][$item->id] = $item;
- if ($question->type_id != 8) {
- if ($ret['questionsinfo'][$pagenum][$question->id]['required']) {
- if (!isset($ret['questionsinfo'][$pagenum][$question->id]['firstone'])) {
- $ret['questionsinfo'][$pagenum][$question->id]['firstone'] = true;
- $ret['questions'][$pagenum][$question->id][$item->id]->value = intval($item->choice_id);
- $ret['questions'][$pagenum][$question->id][$item->id]->firstone = true;
- }
- }
- }
- }
- }
- }
- break;
- case QUESCHECK: // Checkbox.
- $ret['questionsinfo'][$pagenum][$question->id]['ischeckbox'] = true;
- $excludes = [];
- if ($items = $DB->get_records('questionnaire_quest_choice',
- ['question_id' => $question->id])) {
-
- foreach ($items as $item) {
- if (!in_array($item->id, $excludes)) {
- $item->choice_id = $item->id;
- if ($item->value == null) {
- $item->value = '';
- }
- $ret['questions'][$pagenum][$question->id][$item->id] = $item;
- if ($question->type_id != QUESRATE) {
- if ($ret['questionsinfo'][$pagenum][$question->id]['required']) {
- if (!isset($ret['questionsinfo'][$pagenum][$question->id]['firstone'])) {
- $ret['questionsinfo'][$pagenum][$question->id]['firstone'] = true;
- $ret['questions'][$pagenum][$question->id][$item->id]->firstone = true;
- }
- }
- }
- }
- }
- }
- break;
- case QUESDROP: // Select.
- $ret['questionsinfo'][$pagenum][$question->id]['isselect'] = true;
- $excludes = [];
- if ($items = $DB->get_records('questionnaire_quest_choice',
- ['question_id' => $question->id])) {
-
- foreach ($items as $item) {
- if (!in_array($item->id, $excludes)) {
- $item->choice_id = $item->id;
- if ($item->value == null) {
- $item->value = '';
- }
- $ret['questions'][$pagenum][$question->id][$item->id] = $item;
- if ($question->type_id != 9) {
- if ($ret['questionsinfo'][$pagenum][$question->id]['required']) {
- if (!isset($ret['questionsinfo'][$pagenum][$question->id]['firstone'])) {
- $ret['questionsinfo'][$pagenum][$question->id]['firstone'] = true;
- $ret['questions'][$pagenum][$question->id][$item->id]->value = intval($item->choice_id);
- $ret['questions'][$pagenum][$question->id][$item->id]->firstone = true;
- }
- }
- }
- }
- }
- }
- break;
- case QUESRATE: // Rate 1-NN.
- $excludes = [];
- if ($items = $DB->get_records('questionnaire_quest_choice',
- ['question_id' => $question->id])) {
- $ret['questionsinfo'][$pagenum][$question->id]['israte'] = true;
- $vals = $extracontents = [];
- foreach ($items as $item) {
- $item->na = false;
- if ($question->precise == 0) {
- $ret['questions'][$pagenum][$question->id][$item->id] = $item;
- if ($ret['questionsinfo'][$pagenum][$question->id]['required'] == 'y') {
- $ret['questions'][$pagenum][$question->id][$item->id]->min
- = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 1;
- } else {
- $ret['questions'][$pagenum][$question->id][$item->id]->min
- = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 0;
- }
- $ret['questions'][$pagenum][$question->id][$item->id]->max
- = $ret['questions'][$pagenum][$question->id][$item->id]->maxstr
- = intval($question->length);
- } else if ($question->precise == 1) {
- $ret['questions'][$pagenum][$question->id][$item->id] = $item;
- if ($ret['questionsinfo'][$pagenum][$question->id]['required'] == 'y') {
- $ret['questions'][$pagenum][$question->id][$item->id]->min
- = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 1;
- } else {
- $ret['questions'][$pagenum][$question->id][$item->id]->min
- = $ret['questions'][$pagenum][$question->id][$item->id]->minstr = 0;
- }
- $ret['questions'][$pagenum][$question->id][$item->id]->max = intval($question->length) + 1;
- $ret['questions'][$pagenum][$question->id][$item->id]->na = true;
- } else if ($question->precise > 1) {
- $excludes[$item->id] = $item->id;
- if ($item->value == null) {
- if ($arr = explode('|', $item->content)) {
- if (count($arr) == 2) {
- $ret['questions'][$pagenum][$question->id][$item->id] = $item;
- $ret['questions'][$pagenum][$question->id][$item->id]->content = '';
- $ret['questions'][$pagenum][$question->id][$item->id]->minstr = $arr[0];
- $ret['questions'][$pagenum][$question->id][$item->id]->maxstr = $arr[1];
- }
- }
- } else {
- $val = intval($item->value);
- $vals[$val] = $val;
- $extracontents[] = $item->content;
- }
- }
- }
- if ($vals) {
- if ($q = $ret['questions'][$pagenum][$question->id]) {
- foreach (array_keys($q) as $itemid) {
- $ret['questions'][$pagenum][$question->id][$itemid]->min = min($vals);
- $ret['questions'][$pagenum][$question->id][$itemid]->max = max($vals);
- }
- }
- }
- if ($extracontents) {
- $extracontents = array_unique($extracontents);
- $extrahtml = '
diff --git a/templates/dataformat_selector.mustache b/templates/dataformat_selector.mustache
new file mode 100755
index 00000000..109bddd3
--- /dev/null
+++ b/templates/dataformat_selector.mustache
@@ -0,0 +1,79 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/dataformat_selector
+
+ Template for dataformat selection and download form.
+
+ Context variables required for this template:
+ * label
+ * base
+ * name
+ * params
+ * options
+ * sesskey
+ * submit
+ * extrafields
+
+ Example context (json):
+ {
+ "base": "http://example.org/",
+ "name": "test",
+ "label": "Download table data as",
+ "params": [
+ {
+ "name": "fieldname",
+ "value": "defaultvalue"
+ }
+ ],
+ "extrafields": "Input HTML",
+ "options": [
+ {
+ "label": "CSV",
+ "name": "csv"
+ },
+ {
+ "label": "Excel",
+ "name": "excel"
+ }
+ ],
+ "submit": "Download"
+ }
+}}
+
diff --git a/templates/extrafields.mustache b/templates/extrafields.mustache
new file mode 100755
index 00000000..fae0995c
--- /dev/null
+++ b/templates/extrafields.mustache
@@ -0,0 +1,29 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/extrafields
+
+ Templates which inserts a couple of extra inputs in the dataformat_selector.
+
+ Example context (json):
+ {
+ }
+ }}
+
+
+
+
diff --git a/templates/local/mobile/ionic5/boolean_question.mustache b/templates/local/mobile/ionic5/boolean_question.mustache
new file mode 100644
index 00000000..1d5958dd
--- /dev/null
+++ b/templates/local/mobile/ionic5/boolean_question.mustache
@@ -0,0 +1,53 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_boolean_question
+
+ Template which defines a boolean question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * choices - array of objects: choice_id, content, completed and value of each question choice.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "choices": [
+ {
+ "id": 5432,
+ "content": "Yes",
+ "completed": 1,
+ "value": 1
+ },
+ {
+ "id": 5432,
+ "content": "No",
+ "completed": 1,
+ "value": 0
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+">
+ <%#choices%>
+
+
+ disabled="true"<%#value%> checked="true"<%/value%><%/completed%> [value]="<%id%>">
+
+ <%/choices%>
+
\ No newline at end of file
diff --git a/templates/local/mobile/ionic5/checkbox_question.mustache b/templates/local/mobile/ionic5/checkbox_question.mustache
new file mode 100644
index 00000000..0a769fbd
--- /dev/null
+++ b/templates/local/mobile/ionic5/checkbox_question.mustache
@@ -0,0 +1,62 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_checkbox_question
+
+ Template which defines a checkbox question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * choices - array of objects: choice_id, content, completed and value of each question choice.
+
+ Example context (json):
+ {
+ "choices": [
+ {
+ "fieldkey": 985,
+ "id": 5432,
+ "content": "Red",
+ "completed": 0,
+ "value": 1
+ },
+ {
+ "fieldkey": 986 ,
+ "id": 5433,
+ "content": "Blue",
+ "completed": 0,
+ "value": 0
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+
+ <%#choices%>
+
+
+ checked="true"<%/value%> value="<%id%>"
+ <%#completed%> disabled="true"<%/completed%>
+ [(ngModel)]="CONTENT_OTHERDATA.<%choicefieldkey%>">
+
+ <%#otherchoicekey%>
+
+ "
+ <%#completed%> disabled="true"<%/completed%>>
+
+ <%/otherchoicekey%>
+ <%/choices%>
+
\ No newline at end of file
diff --git a/templates/local/mobile/ionic5/date_question.mustache b/templates/local/mobile/ionic5/date_question.mustache
new file mode 100644
index 00000000..460c17f5
--- /dev/null
+++ b/templates/local/mobile/ionic5/date_question.mustache
@@ -0,0 +1,42 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_date_question
+
+ Template which defines a date question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * completed - boolean: True if question already completed.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "completed": 0
+ }
+}}
+{{=<% %>=}}
+
+
+ <%#completed%>
+
+ <%/completed%>
+
+ <%^completed%>
+ ">
+ <%/completed%>
+
diff --git a/templates/local/mobile/ionic5/main_index_page.mustache b/templates/local/mobile/ionic5/main_index_page.mustache
new file mode 100644
index 00000000..249b951e
--- /dev/null
+++ b/templates/local/mobile/ionic5/main_index_page.mustache
@@ -0,0 +1,125 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_main_index_page
+
+ Template which defines the main page that displays notices, submissions and the start answering link.
+
+ Context variables required for this template:
+ * questionnaire - object: "intro" and "autonumquestions" strings for first respondent link.
+ * previous - object: "url" and "title" strings for previous link.
+ * respnumber - object: Current positio ("currpos") and "total" number of responses.
+ * next - object: "url" and "title" strings for next link.
+ * lastrespondent - object: "url" and "title" strings for last respondent link.
+ * listlink - string: Url of the link back to the response list.
+ * printaction - string: HTML to launch the print function.
+
+ Example context (json):
+ {
+ "cmid": 985,
+ "userid": 267,
+ "intro": "Welcome to the questionnaire",
+ "autonumquestions": "1",
+ "id": "342",
+ "rid": 0,
+ "surveyid": "23",
+ "action": "index",
+ "pagenum": 0,
+ "nextpage": 1,
+ "prevpage": 0,
+ "completed": "1",
+ "complete_userdate": "Monday, 17 December 2018, 3:34pm",
+ "emptypage": "0",
+ "emptypage_content": "This is an empty page.",
+ "pagequestions": [
+ {
+ "id": 5432,
+ "type_id": 4,
+ "qnum": "Q1",
+ "content": "Answer this question",
+ "required": "1",
+ "fieldkey": "response_1_23",
+ "isselect": "0",
+ "isbool": "0",
+ "isradiobutton": "1",
+ "ischeckbox": "0",
+ "istextessay": "0",
+ "israte": "0",
+ "choices": [
+ {
+ "id": 745,
+ "content": "Red",
+ "value": " ",
+ "choice_id": 745,
+ "min": 0,
+ "max": 5,
+ "minstr": "Low",
+ "maxstr": "High",
+ "na": 0
+ }
+ ]
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+
\ No newline at end of file
diff --git a/templates/local/mobile/ionic5/numeric_question.mustache b/templates/local/mobile/ionic5/numeric_question.mustache
new file mode 100644
index 00000000..f2988460
--- /dev/null
+++ b/templates/local/mobile/ionic5/numeric_question.mustache
@@ -0,0 +1,42 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_numeric_question
+
+ Template which defines a numeric question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * completed - boolean: True if question already completed.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "completed": 0
+ }
+}}
+{{=<% %>=}}
+
+
+ <%#completed%>
+
+ <%/completed%>
+
+ <%^completed%>
+ ">
+ <%/completed%>
+
\ No newline at end of file
diff --git a/templates/local/mobile/ionic5/radio_question.mustache b/templates/local/mobile/ionic5/radio_question.mustache
new file mode 100644
index 00000000..3d57c2f7
--- /dev/null
+++ b/templates/local/mobile/ionic5/radio_question.mustache
@@ -0,0 +1,61 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_radio_question
+
+ Template which defines a radio question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * choices - array of objects: choice_id, content, completed and value of each question choice.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "choices": [
+ {
+ "id": 5432,
+ "content": "Red",
+ "completed": 0,
+ "value": 1
+ },
+ {
+ "id": 5433,
+ "content": "Blue",
+ "completed": 0,
+ "value": 0
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+">
+ <%#choices%>
+
+
+ checked="true"<%/value%>
+ value="<%id%>"
+ <%#completed%> disabled="true"<%/completed%>>
+
+ <%#otherchoicekey%>
+
+ "
+ <%#completed%> disabled="true"<%/completed%>>
+
+ <%/otherchoicekey%>
+ <%/choices%>
+
\ No newline at end of file
diff --git a/templates/local/mobile/ionic5/rate_question.mustache b/templates/local/mobile/ionic5/rate_question.mustache
new file mode 100644
index 00000000..1d4eca2d
--- /dev/null
+++ b/templates/local/mobile/ionic5/rate_question.mustache
@@ -0,0 +1,120 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_rate_question
+
+ Template which defines a rate question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * choices - array of objects: choice_id, content, completed and value of each question choice.
+
+ Example context (json):
+ {
+ "choices": [
+ {
+ "fieldkey": 985,
+ "content": "Red",
+ "min": 0,
+ "max": 5,
+ "minstr": "zero",
+ "maxstr": "five",
+ "na": 1
+ },
+ {
+ "fieldkey": 986,
+ "content": "Blue",
+ "min": 0,
+ "max": 5,
+ "minstr": "zero",
+ "maxstr": "five",
+ "na": 1
+ }
+ ],
+ "rates": [
+ {
+ "value": 0,
+ "label": "Good"
+ },
+ {
+ "value": 1,
+ "label": 1
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+
+
+
+ <%#choices.0%>
+ <%#leftlabel%>
+
+ <%/leftlabel%>
+ <%/choices.0%>
+ <%#rates%>
+
+ <%label%>
+
+ <%/rates%>
+ <%#hasnacolumn%>
+
+ <%label%>
+
+ <%/hasnacolumn%>
+ <%#choices.0%>
+ <%#rightlabel%>
+
+ <%/rightlabel%>
+ <%/choices.0%>
+
+ <%#choices%>
+
+
+
+
+
+ {{ CONTENT_OTHERDATA.<%fieldkey%> }}
+
+
+ ">
+
+ <%#leftlabel%>
+
+ <%leftlabel%>
+
+ <%/leftlabel%>
+ <%#rates%>
+
+ disabled="true"<%/completed%>>
+
+ <%/rates%>
+ <%#hasnacolumn%>
+
+ disabled="true"<%/completed%>>
+
+ <%/hasnacolumn%>
+ <%#rightlabel%>
+
+ <%rightlabel%>
+
+ <%/rightlabel%>
+
+
+ <%/choices%>
+
+
\ No newline at end of file
diff --git a/templates/local/mobile/ionic5/select_question.mustache b/templates/local/mobile/ionic5/select_question.mustache
new file mode 100644
index 00000000..e609e22c
--- /dev/null
+++ b/templates/local/mobile/ionic5/select_question.mustache
@@ -0,0 +1,51 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_select_question
+
+ Template which defines a select question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * choices - array of objects: choice_id, content, completed and value of each question choice.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "choices": [
+ {
+ "content": "Red",
+ "completed": 0,
+ "value": 1
+ },
+ {
+ "content": "Blue",
+ "completed": 0,
+ "value": 0
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+
+
+ ">
+ <%#choices%>
+ disabled="true"<%/completed%><%#value%> selected="true"<%/value%> value="<%id%>"><%content%>
+ <%/choices%>
+
+
\ No newline at end of file
diff --git a/templates/local/mobile/ionic5/slider_question.mustache b/templates/local/mobile/ionic5/slider_question.mustache
new file mode 100644
index 00000000..80623356
--- /dev/null
+++ b/templates/local/mobile/ionic5/slider_question.mustache
@@ -0,0 +1,57 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_slider_question
+
+ Template which defines a slider question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * completed - boolean: True if question already completed.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "extradata": {
+ "minrange" : 1
+ "maxrange" : 10
+ "startingvalue" : 5
+ "stepvalue" : 1
+ "leftlabel" : "left label"
+ "rightlabel" : "right label"
+ "centerlabel": "center label"
+ },
+ "completed": 0
+ }
+
+}}
+{{=<% %>=}}
+<%#extradata%>
+
+ disabled="true"<%/completed%>
+ min="<%extradata.minrange%>" max="<%extradata.maxrange%>"
+ pin="true" step="<%extradata.stepvalue%>"
+ [(ngModel)]="CONTENT_OTHERDATA.<%fieldkey%>">
+ <%extradata.leftlabel%>
+ <%extradata.rightlabel%>
+
+
+ class="disabled"<%/completed%>>
+
+
+<%/extradata%>
diff --git a/templates/local/mobile/ionic5/text_question.mustache b/templates/local/mobile/ionic5/text_question.mustache
new file mode 100644
index 00000000..8113aea9
--- /dev/null
+++ b/templates/local/mobile/ionic5/text_question.mustache
@@ -0,0 +1,43 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_text_question
+
+ Template which defines a text question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * completed - boolean: True if question already completed.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "completed": 0
+ }
+}}
+{{=<% %>=}}
+
+
+ <%#completed%>
+
+ <%/completed%>
+
+ <%^completed%>
+ "
+ core-auto-rows>
+ <%/completed%>
+
\ No newline at end of file
diff --git a/templates/local/mobile/ionic5/view_activity_page.mustache b/templates/local/mobile/ionic5/view_activity_page.mustache
new file mode 100644
index 00000000..ee5f508b
--- /dev/null
+++ b/templates/local/mobile/ionic5/view_activity_page.mustache
@@ -0,0 +1,215 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_view_activity_page
+
+ Template which defines a questionnaire display in the mobile app.
+
+ Context variables required for this template:
+ * questionnaire - object: "intro" and "autonumquestions" strings for first respondent link.
+ * previous - object: "url" and "title" strings for previous link.
+ * respnumber - object: Current positio ("currpos") and "total" number of responses.
+ * next - object: "url" and "title" strings for next link.
+ * lastrespondent - object: "url" and "title" strings for last respondent link.
+ * listlink - string: Url of the link back to the response list.
+ * printaction - string: HTML to launch the print function.
+
+ Example context (json):
+ {
+ "cmid": 985,
+ "userid": 267,
+ "intro": "Welcome to the questionnaire",
+ "autonumquestions": "1",
+ "id": "342",
+ "rid": 0,
+ "surveyid": "23",
+ "action": "nextpage",
+ "pagenum": 0,
+ "nextpage": 1,
+ "prevpage": 0,
+ "hasmorepages": 1,
+ "completed": "1",
+ "complete_userdate": "Monday, 17 December 2018, 3:34pm",
+ "emptypage": "0",
+ "emptypage_content": "This is an empty page.",
+ "pagequestions": [
+ {
+ "id": 5432,
+ "type_id": 4,
+ "qnum": "Q1",
+ "content": "Answer this question",
+ "required": "1",
+ "fieldkey": "response_1_23",
+ "isselect": "0",
+ "isbool": "0",
+ "isradiobutton": "1",
+ "ischeckbox": "0",
+ "istextessay": "0",
+ "israte": "0",
+ "choices": [
+ {
+ "id": 745,
+ "content": "Red",
+ "value": " ",
+ "choice_id": 745,
+ "min": 0,
+ "max": 5,
+ "minstr": "Low",
+ "maxstr": "High",
+ "na": 0
+ }
+ ]
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+
diff --git a/templates/local/mobile/latest/boolean_question.mustache b/templates/local/mobile/latest/boolean_question.mustache
new file mode 100644
index 00000000..bde7cd49
--- /dev/null
+++ b/templates/local/mobile/latest/boolean_question.mustache
@@ -0,0 +1,54 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_boolean_question
+
+ Template which defines a boolean question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * choices - array of objects: choice_id, content, completed and value of each question choice.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "choices": [
+ {
+ "id": 5432,
+ "content": "Yes",
+ "completed": 1,
+ "value": 1
+ },
+ {
+ "id": 5432,
+ "content": "No",
+ "completed": 1,
+ "value": 0
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+">
+ <%#choices%>
+
+ disabled="true"<%#value%> checked="true"<%/value%><%/completed%> [value]="<%id%>">
+
+
+
+ <%/choices%>
+
diff --git a/templates/local/mobile/latest/checkbox_question.mustache b/templates/local/mobile/latest/checkbox_question.mustache
new file mode 100644
index 00000000..86640f76
--- /dev/null
+++ b/templates/local/mobile/latest/checkbox_question.mustache
@@ -0,0 +1,63 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_checkbox_question
+
+ Template which defines a checkbox question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * choices - array of objects: choice_id, content, completed and value of each question choice.
+
+ Example context (json):
+ {
+ "choices": [
+ {
+ "fieldkey": 985,
+ "id": 5432,
+ "content": "Red",
+ "completed": 0,
+ "value": 1
+ },
+ {
+ "fieldkey": 986 ,
+ "id": 5433,
+ "content": "Blue",
+ "completed": 0,
+ "value": 0
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+
+ <%#choices%>
+
+ checked="true"<%/value%> value="<%id%>"
+ <%#completed%> disabled="true"<%/completed%>
+ [(ngModel)]="CONTENT_OTHERDATA.<%choicefieldkey%>">
+
+
+
+ <%#otherchoicekey%>
+
+ "
+ <%#completed%> disabled="true"<%/completed%>>
+
+ <%/otherchoicekey%>
+ <%/choices%>
+
diff --git a/templates/local/mobile/latest/date_question.mustache b/templates/local/mobile/latest/date_question.mustache
new file mode 100644
index 00000000..1554a6ed
--- /dev/null
+++ b/templates/local/mobile/latest/date_question.mustache
@@ -0,0 +1,50 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_date_question
+
+ Template which defines a date question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * completed - boolean: True if question already completed.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "completed": 0
+ }
+}}
+{{=<% %>=}}
+
+
+ <%#completed%>
+
+ <%/completed%>
+
+ <%^completed%>
+
+
+
+ " presentation="date-time"
+ max="2100-12-31" [showDefaultButtons]="true">
+ {{'core.date' | translate}}
+
+
+
+ <%/completed%>
+
diff --git a/templates/local/mobile/latest/main_index_page.mustache b/templates/local/mobile/latest/main_index_page.mustache
new file mode 100644
index 00000000..b561bc1a
--- /dev/null
+++ b/templates/local/mobile/latest/main_index_page.mustache
@@ -0,0 +1,124 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_main_index_page
+
+ Template which defines the main page that displays notices, submissions and the start answering link.
+
+ Context variables required for this template:
+ * questionnaire - object: "intro" and "autonumquestions" strings for first respondent link.
+ * previous - object: "url" and "title" strings for previous link.
+ * respnumber - object: Current positio ("currpos") and "total" number of responses.
+ * next - object: "url" and "title" strings for next link.
+ * lastrespondent - object: "url" and "title" strings for last respondent link.
+ * listlink - string: Url of the link back to the response list.
+ * printaction - string: HTML to launch the print function.
+
+ Example context (json):
+ {
+ "cmid": 985,
+ "userid": 267,
+ "intro": "Welcome to the questionnaire",
+ "autonumquestions": "1",
+ "id": "342",
+ "rid": 0,
+ "surveyid": "23",
+ "action": "index",
+ "pagenum": 0,
+ "nextpage": 1,
+ "prevpage": 0,
+ "completed": "1",
+ "complete_userdate": "Monday, 17 December 2018, 3:34pm",
+ "emptypage": "0",
+ "emptypage_content": "This is an empty page.",
+ "pagequestions": [
+ {
+ "id": 5432,
+ "type_id": 4,
+ "qnum": "Q1",
+ "content": "Answer this question",
+ "required": "1",
+ "fieldkey": "response_1_23",
+ "isselect": "0",
+ "isbool": "0",
+ "isradiobutton": "1",
+ "ischeckbox": "0",
+ "istextessay": "0",
+ "israte": "0",
+ "choices": [
+ {
+ "id": 745,
+ "content": "Red",
+ "value": " ",
+ "choice_id": 745,
+ "min": 0,
+ "max": 5,
+ "minstr": "Low",
+ "maxstr": "High",
+ "na": 0
+ }
+ ]
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+
diff --git a/templates/local/mobile/latest/numeric_question.mustache b/templates/local/mobile/latest/numeric_question.mustache
new file mode 100644
index 00000000..f2988460
--- /dev/null
+++ b/templates/local/mobile/latest/numeric_question.mustache
@@ -0,0 +1,42 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_numeric_question
+
+ Template which defines a numeric question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * completed - boolean: True if question already completed.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "completed": 0
+ }
+}}
+{{=<% %>=}}
+
+
+ <%#completed%>
+
+ <%/completed%>
+
+ <%^completed%>
+ ">
+ <%/completed%>
+
\ No newline at end of file
diff --git a/templates/local/mobile/latest/radio_question.mustache b/templates/local/mobile/latest/radio_question.mustache
new file mode 100644
index 00000000..ccffab0b
--- /dev/null
+++ b/templates/local/mobile/latest/radio_question.mustache
@@ -0,0 +1,62 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_radio_question
+
+ Template which defines a radio question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * choices - array of objects: choice_id, content, completed and value of each question choice.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "choices": [
+ {
+ "id": 5432,
+ "content": "Red",
+ "completed": 0,
+ "value": 1
+ },
+ {
+ "id": 5433,
+ "content": "Blue",
+ "completed": 0,
+ "value": 0
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+">
+ <%#choices%>
+
+ checked="true"<%/value%>
+ value="<%id%>"
+ <%#completed%> disabled="true"<%/completed%>>
+
+
+
+ <%#otherchoicekey%>
+
+ "
+ <%#completed%> disabled="true"<%/completed%>>
+
+ <%/otherchoicekey%>
+ <%/choices%>
+
diff --git a/templates/local/mobile/latest/rate_question.mustache b/templates/local/mobile/latest/rate_question.mustache
new file mode 100644
index 00000000..1d4eca2d
--- /dev/null
+++ b/templates/local/mobile/latest/rate_question.mustache
@@ -0,0 +1,120 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_rate_question
+
+ Template which defines a rate question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * choices - array of objects: choice_id, content, completed and value of each question choice.
+
+ Example context (json):
+ {
+ "choices": [
+ {
+ "fieldkey": 985,
+ "content": "Red",
+ "min": 0,
+ "max": 5,
+ "minstr": "zero",
+ "maxstr": "five",
+ "na": 1
+ },
+ {
+ "fieldkey": 986,
+ "content": "Blue",
+ "min": 0,
+ "max": 5,
+ "minstr": "zero",
+ "maxstr": "five",
+ "na": 1
+ }
+ ],
+ "rates": [
+ {
+ "value": 0,
+ "label": "Good"
+ },
+ {
+ "value": 1,
+ "label": 1
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+
+
+
+ <%#choices.0%>
+ <%#leftlabel%>
+
+ <%/leftlabel%>
+ <%/choices.0%>
+ <%#rates%>
+
+ <%label%>
+
+ <%/rates%>
+ <%#hasnacolumn%>
+
+ <%label%>
+
+ <%/hasnacolumn%>
+ <%#choices.0%>
+ <%#rightlabel%>
+
+ <%/rightlabel%>
+ <%/choices.0%>
+
+ <%#choices%>
+
+
+
+
+
+ {{ CONTENT_OTHERDATA.<%fieldkey%> }}
+
+
+ ">
+
+ <%#leftlabel%>
+
+ <%leftlabel%>
+
+ <%/leftlabel%>
+ <%#rates%>
+
+ disabled="true"<%/completed%>>
+
+ <%/rates%>
+ <%#hasnacolumn%>
+
+ disabled="true"<%/completed%>>
+
+ <%/hasnacolumn%>
+ <%#rightlabel%>
+
+ <%rightlabel%>
+
+ <%/rightlabel%>
+
+
+ <%/choices%>
+
+
\ No newline at end of file
diff --git a/templates/local/mobile/latest/select_question.mustache b/templates/local/mobile/latest/select_question.mustache
new file mode 100644
index 00000000..e609e22c
--- /dev/null
+++ b/templates/local/mobile/latest/select_question.mustache
@@ -0,0 +1,51 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_select_question
+
+ Template which defines a select question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * choices - array of objects: choice_id, content, completed and value of each question choice.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "choices": [
+ {
+ "content": "Red",
+ "completed": 0,
+ "value": 1
+ },
+ {
+ "content": "Blue",
+ "completed": 0,
+ "value": 0
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+
+
+ ">
+ <%#choices%>
+ disabled="true"<%/completed%><%#value%> selected="true"<%/value%> value="<%id%>"><%content%>
+ <%/choices%>
+
+
\ No newline at end of file
diff --git a/templates/local/mobile/latest/slider_question.mustache b/templates/local/mobile/latest/slider_question.mustache
new file mode 100644
index 00000000..318dce0b
--- /dev/null
+++ b/templates/local/mobile/latest/slider_question.mustache
@@ -0,0 +1,59 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_slider_question
+
+ Template which defines a slider question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * completed - boolean: True if question already completed.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "extradata": {
+ "minrange" : 1
+ "maxrange" : 10
+ "startingvalue" : 5
+ "stepvalue" : 1
+ "leftlabel" : "left label"
+ "rightlabel" : "right label"
+ "centerlabel": "center label"
+ },
+ "completed": 0
+ }
+
+}}
+{{=<% %>=}}
+<%#extradata%>
+
+ disabled="true"<%/completed%>
+ min="<%extradata.minrange%>" max="<%extradata.maxrange%>"
+ pin="true" step="<%extradata.stepvalue%>"
+ [(ngModel)]="CONTENT_OTHERDATA.<%fieldkey%>">
+ <%extradata.leftlabel%>
+ <%extradata.rightlabel%>
+
+
+
+ class="disabled"<%/completed%>>
+
+
+
+<%/extradata%>
diff --git a/templates/local/mobile/latest/text_question.mustache b/templates/local/mobile/latest/text_question.mustache
new file mode 100644
index 00000000..8113aea9
--- /dev/null
+++ b/templates/local/mobile/latest/text_question.mustache
@@ -0,0 +1,43 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_text_question
+
+ Template which defines a text question display in the mobile app.
+
+ Context variables required for this template:
+ * fieldkey - integer: ID of the question.
+ * completed - boolean: True if question already completed.
+
+ Example context (json):
+ {
+ "fieldkey": 985,
+ "completed": 0
+ }
+}}
+{{=<% %>=}}
+
+
+ <%#completed%>
+
+ <%/completed%>
+
+ <%^completed%>
+ "
+ core-auto-rows>
+ <%/completed%>
+
\ No newline at end of file
diff --git a/templates/local/mobile/latest/view_activity_page.mustache b/templates/local/mobile/latest/view_activity_page.mustache
new file mode 100644
index 00000000..eb7a8a47
--- /dev/null
+++ b/templates/local/mobile/latest/view_activity_page.mustache
@@ -0,0 +1,221 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/mobile_view_activity_page
+
+ Template which defines a questionnaire display in the mobile app.
+
+ Context variables required for this template:
+ * questionnaire - object: "intro" and "autonumquestions" strings for first respondent link.
+ * previous - object: "url" and "title" strings for previous link.
+ * respnumber - object: Current positio ("currpos") and "total" number of responses.
+ * next - object: "url" and "title" strings for next link.
+ * lastrespondent - object: "url" and "title" strings for last respondent link.
+ * listlink - string: Url of the link back to the response list.
+ * printaction - string: HTML to launch the print function.
+
+ Example context (json):
+ {
+ "cmid": 985,
+ "userid": 267,
+ "intro": "Welcome to the questionnaire",
+ "autonumquestions": "1",
+ "id": "342",
+ "rid": 0,
+ "surveyid": "23",
+ "action": "nextpage",
+ "pagenum": 0,
+ "nextpage": 1,
+ "prevpage": 0,
+ "hasmorepages": 1,
+ "completed": "1",
+ "complete_userdate": "Monday, 17 December 2018, 3:34pm",
+ "emptypage": "0",
+ "emptypage_content": "This is an empty page.",
+ "pagequestions": [
+ {
+ "id": 5432,
+ "type_id": 4,
+ "qnum": "Q1",
+ "content": "Answer this question",
+ "required": "1",
+ "fieldkey": "response_1_23",
+ "isselect": "0",
+ "isbool": "0",
+ "isradiobutton": "1",
+ "ischeckbox": "0",
+ "istextessay": "0",
+ "israte": "0",
+ "choices": [
+ {
+ "id": 745,
+ "content": "Red",
+ "value": " ",
+ "choice_id": 745,
+ "min": 0,
+ "max": 5,
+ "minstr": "Low",
+ "maxstr": "High",
+ "na": 0
+ }
+ ]
+ }
+ ]
+ }
+}}
+{{=<% %>=}}
+
diff --git a/templates/progressbar.mustache b/templates/progressbar.mustache
new file mode 100644
index 00000000..b3f83b6d
--- /dev/null
+++ b/templates/progressbar.mustache
@@ -0,0 +1,65 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle 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 the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_questionnaire/completepage
+
+ Template which defines a questionnaire completion page.
+
+ Classes required for JS:
+ * /questionnaire/module.js
+
+ Data attributes required for JS:
+ * none
+
+ Context variables required for this template: (Note sections have been used to allow non-inclusion)
+ * completepercent - string: progress through the questionnaire as a percentage
+ * targethelp: object - data for the help tooltip template
+
+ Example context (json):
+ {
+ "percent": "90",
+ "progresshelp": {
+ "title": "Help with something",
+ "text": "Help with something",
+ "url": "http://example.org/help",
+ "linktext": "",
+ "icon":{
+ "extraclasses": "iconhelp",
+ "attributes": [
+ {"name": "src", "value": "../../../pix/help.svg"},
+ {"name": "alt", "value": "Help icon"}
+ ]
+ }
+ }
+ }
+}}
+