├── .eslintrc ├── .gitignore ├── .gitmodules ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── ajax.php ├── amd ├── build │ ├── communicator.min.js │ ├── communicator.min.js.map │ ├── contenthubregistration.min.js │ ├── contenthubregistration.min.js.map │ ├── embed.min.js │ └── embed.min.js.map └── src │ ├── communicator.js │ ├── contenthubregistration.js │ └── embed.js ├── autoloader.php ├── backup └── moodle2 │ ├── backup_hvp_activity_task.class.php │ ├── backup_hvp_stepslib.php │ ├── restore_hvp_activity_task.class.php │ └── restore_hvp_stepslib.php ├── classes ├── admin_setting_html.php ├── content_hub_service.php ├── content_type_cache_form.php ├── content_user_data.php ├── curl.php ├── editor_ajax.php ├── editor_framework.php ├── event.php ├── event │ ├── attempt_submitted.php │ ├── course_module_instance_list_viewed.php │ └── course_module_viewed.php ├── file_storage.php ├── framework.php ├── mobile_auth.php ├── output │ └── mobile.php ├── privacy │ └── provider.php ├── results.php ├── task │ ├── look_for_updates.php │ ├── remove_old_auth_tokens.php │ ├── remove_old_log_entries.php │ └── remove_tmpfiles.php ├── upload_libraries_form.php ├── user_grades.php ├── view_assets.php └── xapi_result.php ├── content_hub_registration.php ├── dataviews.js ├── db ├── access.php ├── events.php ├── install.php ├── install.xml ├── messages.php ├── mobile.php ├── tasks.php └── upgrade.php ├── editor.js ├── embed.php ├── grade.php ├── index.php ├── lang └── en │ └── hvp.php ├── lib.php ├── library_list.php ├── locallib.php ├── mod_form.php ├── pix ├── icon.png └── icon.svg ├── renderer.php ├── review.php ├── settings-hide-key.js ├── settings.php ├── share.php ├── styles.css ├── templates ├── contact_site_administrator.mustache ├── content_hub_registration.mustache ├── content_hub_registration_box.mustache ├── hub_options.mustache ├── iframe_embedding_disabled.mustache ├── mobile_view_page.mustache └── review.mustache ├── thirdpartylibs.xml ├── upgrade_content_page.php ├── version.php ├── view.css ├── view.php ├── xapi-collector.js └── xapi-custom-report.css /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "amd": true 5 | }, 6 | "globals": { 7 | "M": true, 8 | "Y": true, 9 | "H5PIntegration": true, 10 | "H5PEditor": true, 11 | "H5P": true 12 | }, 13 | "rules": { 14 | "comma-dangle": "off", 15 | "no-compare-neg-zero": "error", 16 | "no-cond-assign": "error", 17 | "no-console": ["error", {"allow": ["warn", "error"]}], 18 | "no-constant-condition": "error", 19 | "no-control-regex": "error", 20 | "no-debugger": "error", 21 | "no-dupe-args": "error", 22 | "no-dupe-keys": "error", 23 | "no-duplicate-case": "error", 24 | "no-empty": "warn", 25 | "no-empty-character-class": "error", 26 | "no-ex-assign": "error", 27 | "no-extra-boolean-cast": "error", 28 | "no-extra-parens": "off", 29 | "no-extra-semi": "error", 30 | "no-func-assign": "error", 31 | "no-inner-declarations": "error", 32 | "no-invalid-regexp": "error", 33 | "no-irregular-whitespace": "error", 34 | "no-obj-calls": "error", 35 | "no-prototype-builtins": "off", 36 | "no-regex-spaces": "error", 37 | "no-sparse-arrays": "error", 38 | "no-unexpected-multiline": "error", 39 | "no-unreachable": "warn", 40 | "no-unsafe-finally": "error", 41 | "no-unsafe-negation": "error", 42 | "use-isnan": "error", 43 | "valid-jsdoc": ["warn", { "requireReturn": false, "requireParamDescription": false, "requireReturnDescription": false }], 44 | "valid-typeof": "error", 45 | "array-callback-return": "warn", 46 | "block-scoped-var": "warn", 47 | "complexity": "warn", 48 | "consistent-return": "warn", 49 | "curly": "error", 50 | "dot-notation": "warn", 51 | "no-alert": "warn", 52 | "no-caller": "error", 53 | "no-case-declarations": "error", 54 | "no-div-regex": "error", 55 | "no-empty-pattern": "error", 56 | "no-empty-function": "warn", 57 | "no-eq-null": "error", 58 | "no-eval": "error", 59 | "no-extend-native": "error", 60 | "no-extra-bind": "warn", 61 | "no-fallthrough": "error", 62 | "no-floating-decimal": "warn", 63 | "no-global-assign": "warn", 64 | "no-implied-eval": "error", 65 | "no-invalid-this": "error", 66 | "no-iterator": "error", 67 | "no-labels": "error", 68 | "no-loop-func": "error", 69 | "no-multi-spaces": "warn", 70 | "no-multi-str": "error", 71 | "no-new-func": "error", 72 | "no-new-wrappers": "error", 73 | "no-octal": "error", 74 | "no-octal-escape": "error", 75 | "no-proto": "error", 76 | "no-redeclare": "warn", 77 | "no-return-assign": "error", 78 | "no-script-url": "error", 79 | "no-self-assign": "error", 80 | "no-self-compare": "error", 81 | "no-sequences": "warn", 82 | "no-throw-literal": "warn", 83 | "no-unmodified-loop-condition": "error", 84 | "no-unused-expressions": "error", 85 | "no-unused-labels": "error", 86 | "no-useless-call": "warn", 87 | "no-useless-escape": "warn", 88 | "no-with": "error", 89 | "wrap-iife": ["error", "any"], 90 | 91 | "no-delete-var": "error", 92 | "no-undef": "error", 93 | "no-undef-init": "error", 94 | "no-unused-vars": ["error", { "caughtErrors": "none" }], 95 | 96 | "array-bracket-spacing": "warn", 97 | "block-spacing": "warn", 98 | "brace-style": ["warn", "1tbs"], 99 | "camelcase": "warn", 100 | "capitalized-comments": ["warn", "always", { "ignoreConsecutiveComments": true }], 101 | "comma-spacing": ["warn", { "before": false, "after": true }], 102 | "comma-style": ["warn", "last"], 103 | "computed-property-spacing": "error", 104 | "consistent-this": "off", 105 | "eol-last": "off", 106 | "func-call-spacing": ["warn", "never"], 107 | "func-names": "off", 108 | "func-style": "off", 109 | "indent": ["off", 4, { "SwitchCase": 1 }], 110 | "key-spacing": ["warn", { "beforeColon": false, "afterColon": true, "mode": minimum }], 111 | "keyword-spacing": "warn", 112 | "linebreak-style": ["error", "unix"], 113 | "lines-around-comment": "off", 114 | "max-len": ["error", 132], 115 | "max-lines": "off", 116 | "max-depth": "warn", 117 | "max-nested-callbacks": ["warn", 5], 118 | "max-params": "off", 119 | "max-statements": "off", 120 | "max-statements-per-line": ["warn", { max: 2 }], 121 | "new-cap": ["warn", { "properties": false }], 122 | "new-parens": "warn", 123 | "newline-after-var": "off", 124 | "newline-before-return": "off", 125 | "newline-per-chained-call": "off", 126 | "no-array-constructor": "off", 127 | "no-bitwise": "error", 128 | "no-continue": "off", 129 | "no-inline-comments": "off", 130 | "no-lonely-if": "off", 131 | "no-mixed-operators": "off", 132 | "no-mixed-spaces-and-tabs": "error", 133 | "no-multiple-empty-lines": "warn", 134 | "no-negated-condition": "off", 135 | "no-nested-ternary": "warn", 136 | "no-new-object": "off", 137 | "no-plusplus": "off", 138 | "no-tabs": "error", 139 | "no-ternary": "off", 140 | "no-trailing-spaces": "error", 141 | "no-underscore-dangle": "off", 142 | "no-unneeded-ternary": "off", 143 | "no-whitespace-before-property": "warn", 144 | "object-curly-newline": "off", 145 | "object-curly-spacing": "warn", 146 | "object-property-newline": "off", 147 | "one-var": "off", 148 | "one-var-declaration-per-line": ["warn", "initializations"], 149 | "operator-assignment": "off", 150 | "operator-linebreak": "off", 151 | "padded-blocks": "off", 152 | "quote-props": ["warn", "as-needed", {"unnecessary": false, "keywords": true, "numbers": true}], 153 | "quotes": "off", 154 | "require-jsdoc": "warn", 155 | "semi": "error", 156 | "semi-spacing": ["warn", {"before": false, "after": true}], 157 | "sort-vars": "off", 158 | "space-before-blocks": "warn", 159 | "space-before-function-paren": ["warn", "never"], 160 | "space-in-parens": "warn", 161 | "space-infix-ops": "warn", 162 | "space-unary-ops": "warn", 163 | "spaced-comment": "warn", 164 | "unicode-bom": "error", 165 | "wrap-regex": "off", 166 | 167 | "no-restricted-properties": ["warn", { 168 | "object": "M", 169 | "property": "str", 170 | "message": "Use AMD module 'core/str' or M.util.get_string()" 171 | }], 172 | 173 | }, 174 | overrides: [ 175 | { 176 | files: ["**/yui/src/**/*.js"], 177 | 178 | rules: { 179 | "no-undef": "off", 180 | "no-unused-vars": "off", 181 | "no-unused-expressions": "off" 182 | } 183 | }, 184 | { 185 | files: ["**/amd/src/*.js"], 186 | rules: { 187 | "no-unused-vars": "error", 188 | "no-implicit-globals": "error" 189 | } 190 | } 191 | ] 192 | } 193 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | files/tmp/* 2 | files/content/* 3 | files/libraries/* 4 | *~ 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "library"] 2 | path = library 3 | url = https://github.com/h5p/h5p-php-library.git 4 | branch = moodle 5 | [submodule "editor"] 6 | path = editor 7 | url = https://github.com/h5p/h5p-editor-php-library.git 8 | branch = stable 9 | [submodule "reporting"] 10 | path = reporting 11 | url = https://github.com/h5p/h5p-php-report.git 12 | branch = stable 13 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6, 3 | "globals": { 4 | "H5PDataView": true, 5 | "H5P": true, 6 | "H5PIntegration": true, 7 | "HVPSettingsHideKey": true, 8 | "console": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This is the language of our project. 2 | language: php 3 | 4 | # Installs the required version of Firefox for Behat, an updated version 5 | # of PostgreSQL and extra APT packages. Java 8 is only required 6 | # for Mustache command. 7 | addons: 8 | postgresql: "9.5" 9 | 10 | services: 11 | - mysql 12 | - postgresql 13 | - docker 14 | 15 | # Cache NPM's and Composer's caches to speed up build times. 16 | cache: 17 | directories: 18 | - $HOME/.composer/cache 19 | - $HOME/.npm 20 | 21 | # Determines which versions of PHP to test our project against. Each version 22 | # listed here will create a separate build and run the tests against that 23 | # version of PHP. 24 | php: 25 | - 7.0 26 | - 7.1 27 | - 7.2 28 | - 7.3 29 | - 7.4 30 | 31 | # This section sets up the environment variables for the build. 32 | env: 33 | global: 34 | # This line determines which version branch of Moodle to test against. 35 | - MOODLE_BRANCH=MOODLE_39_STABLE 36 | - IGNORE_PATHS=lang,editor,library,reporting 37 | # This matrix is used for testing against multiple databases. So for 38 | # each version of PHP being tested, one build will be created for each 39 | # database listed here. EG: for PHP 5.6, one build will be created 40 | # using PHP 5.6 and pgsql. In addition, another build will be created 41 | # using PHP 5.6 and mysqli. 42 | matrix: 43 | - DB=pgsql 44 | - DB=mysqli 45 | 46 | # Optionally, it is possible to specify a different Moodle repo to use 47 | # (git://github.com/moodle/moodle.git is used by default): 48 | # - MOODLE_REPO=git://github.com/username/moodle.git 49 | 50 | # This lists steps that are run before the installation step. 51 | before_install: 52 | # This disables XDebug which should speed up the build. 53 | - phpenv config-rm xdebug.ini 54 | # Currently we are inside of the clone of your repository. We move up two 55 | # directories to build the project. 56 | - cd ../.. 57 | # Install this project into a directory called "ci". 58 | - composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3 59 | # Update the $PATH so scripts from this project can be called easily. 60 | - export PATH="$(cd ci/bin; pwd):$(cd ci/vendor/bin; pwd):$PATH" 61 | 62 | # This lists steps that are run for installation and setup. 63 | install: 64 | # Run the default install. The overview of what this does: 65 | # - Clone the Moodle project into a directory called moodle. 66 | # - Create a data directory called moodledata. 67 | # - Create Moodle config.php, database, etc. 68 | # - Copy your plugin(s) into Moodle. 69 | # - Run Composer install within Moodle. 70 | # - Run NPM install in Moodle and in your plugin if it has a "package.json". 71 | # - Run "grunt ignorefiles" within Moodle to update ignore file lists. 72 | # - If your plugin has Behat features, then Behat will be setup. 73 | # - If your plugin has unit tests, then PHPUnit will be setup. 74 | - moodle-plugin-ci install 75 | 76 | # This lists steps that are run for the purposes of testing. Any of 77 | # these steps can be re-ordered or removed to your liking. And of 78 | # course, you can add any of your own custom steps. 79 | script: 80 | # This step lints your PHP files to check for syntax errors. 81 | - moodle-plugin-ci phplint 82 | # This step runs the PHP Copy/Paste Detector on your plugin. 83 | # This helps to find code duplication. 84 | - moodle-plugin-ci phpcpd 85 | # This step runs the PHP Mess Detector on your plugin. This helps to find 86 | # potential problems with your code which can result in 87 | # refactoring opportunities. 88 | - moodle-plugin-ci phpmd 89 | # This step runs the Moodle Code Checker to make sure that your plugin 90 | # conforms to the Moodle coding standards. It is highly recommended 91 | # that you keep this step. 92 | - moodle-plugin-ci codechecker 93 | # This step runs Moodle PHPDoc checker on your plugin. 94 | # - moodle-plugin-ci phpdoc 95 | # This step runs some light validation on the plugin file structure 96 | # and code. Validation can be plugin specific. 97 | - moodle-plugin-ci validate 98 | # This step validates your plugin's upgrade steps. 99 | - moodle-plugin-ci savepoints 100 | # This step validates the HTML and Javascript in your Mustache templates. 101 | # - moodle-plugin-ci mustache 102 | # This step runs Grunt tasks on the plugin. By default, it tries to run 103 | # tasks relevant to your plugin and Moodle version, but you can run 104 | # specific tasks by passing them as options, 105 | # EG: moodle-plugin-ci grunt -t task1 -t task2 106 | # - moodle-plugin-ci grunt 107 | # This step runs the PHPUnit tests of your plugin. If your plugin has 108 | # PHPUnit tests, then it is highly recommended that you keep this step. 109 | - moodle-plugin-ci phpunit 110 | # This step runs the Behat tests of your plugin. If your plugin has 111 | # Behat tests, then it is highly recommended that you keep this step. 112 | # There are two important options that you may want to use: 113 | # - The auto rerun option allows you to rerun failures X number of times, 114 | # default is 2, EG usage: --auto-rerun 3 115 | # - The dump option allows you to print the failure HTML to the console, 116 | # handy for debugging, EG usage: --dump 117 | # - The suite option allows you to set the theme to use for behat test. If 118 | # not specified, the default theme is used, EG usage: --suite boost -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # H5P Moodle Plugin 2 | 3 | Create and add rich content inside your LMS for free. Some examples of what you 4 | get with H5P are Interactive Video, Quizzes, Collage and Timeline. 5 | 6 | ## Usage 7 | 8 | If you intend to use the repository directly in production, make sure that you're using the "Stable" branch, as this is the production branch. 9 | There are no guarantees for the state of the other branches at any given time. 10 | Also make sure that all submodules are pulled as well using: 11 | 12 | ``` 13 | git submodule update --init 14 | ``` 15 | 16 | ## Description 17 | 18 | One of the great benefits with using H5P is that it gives you access to lots of 19 | different [interactive content types](https://h5p.org/content-types-and-applications). 20 | 21 | Another great benefit with H5P is that it allows you to easily share and reuse 22 | content. To reuse content, you just download the H5P you would like to edit and 23 | make your changes – e.g. translate to a new language or adjust it to a new 24 | situation. 25 | 26 | H5P is: 27 | 28 | * Open Source 29 | * Free to Use 30 | * HTML5 31 | * Responsive 32 | 33 | The H5P community is actively contributing to improve H5P. Updates and new 34 | features are continuously made available on the community portal 35 | [H5P.org](https://h5p.org). 36 | 37 | View our [setup for Moodle](https://h5p.org/moodle) to get information on how 38 | to get started with H5P. 39 | 40 | ### GDPR Compliance 41 | Information useful to help you achieve GDPR compliance while using this plugin 42 | can be found at [H5P.org's GDPR Compliance](https://h5p.org/plugin-gdpr-compliance) page. 43 | 44 | ### Development Version 45 | Warning! Never use the development version in production, there are no guarantees for which state the development branches are in at a given time. 46 | 47 | Inside your `moodle/mod` folder you run the following command: 48 | ``` 49 | git clone -b master https://github.com/h5p/h5p-moodle-plugin.git hvp && cd hvp && git submodule update --init 50 | ``` 51 | 52 | ### Enabling The Plugin 53 | In Moodle, go to administrator -> plugin overview, and press 'Update database'. 54 | 55 | ## Settings 56 | Settings can be found at: Site Administration -> Plugins -> Activity Modules -> H5P 57 | 58 | ## Contributing 59 | Feel free to contribute by: 60 | * Submitting translations to the [Moodle AMOS translator](https://lang.moodle.org/local/amos/view.php) 61 | * Testing and creating issues. But remember to check if the issues is already 62 | reported before creating a new one. Perhaps you can contribute to an already 63 | existing issue? 64 | * Solving issues and submitting code through Pull Requests to the 'master' branch or on a separate feature branch. 65 | -------------------------------------------------------------------------------- /amd/build/communicator.min.js: -------------------------------------------------------------------------------- 1 | define("mod_hvp/communicator",[],(function(){var H5PEmbedCommunicator=function(){this._actionHandlers={},this.registerEventListeners()};return H5PEmbedCommunicator.prototype._actionHandlers={},H5PEmbedCommunicator.prototype.on=function(action,handler){this._actionHandlers[action]=handler},H5PEmbedCommunicator.prototype.send=function(action,data){void 0===data&&(data={}),data.context="h5p",data.action=action,window.parent.postMessage(data,"*")},H5PEmbedCommunicator.prototype.registerEventListeners=function(){var self=this;window.addEventListener("message",(function(event){window.parent===event.source&&"h5p"===event.data.context&&void 0!==self._actionHandlers[event.data.action]&&self._actionHandlers[event.data.action](event.data)}),!1)},new H5PEmbedCommunicator})); 2 | 3 | //# sourceMappingURL=communicator.min.js.map -------------------------------------------------------------------------------- /amd/build/communicator.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"communicator.min.js","sources":["../src/communicator.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\ndefine([], function() {\n\n /**\n * When embedded the communicator helps talk to the parent page.\n * This is a copy of the H5P.communicator, which we need to communicate in this context\n */\n var H5PEmbedCommunicator = function() {\n this._actionHandlers = {};\n this.registerEventListeners();\n };\n\n /** @type {Object} Maps actions to functions. */\n H5PEmbedCommunicator.prototype._actionHandlers = {};\n\n /**\n * Register action listener.\n *\n * @param {string} action What you are waiting for\n * @param {function} handler What you want done\n */\n H5PEmbedCommunicator.prototype.on = function(action, handler) {\n this._actionHandlers[action] = handler;\n };\n\n /**\n * Send a message to the all mighty father.\n *\n * @param {string} action\n * @param {Object} [data] payload\n */\n H5PEmbedCommunicator.prototype.send = function(action, data) {\n if (data === undefined) {\n data = {};\n }\n data.context = 'h5p';\n data.action = action;\n\n // Parent origin can be anything.\n window.parent.postMessage(data, '*');\n };\n\n\n /**\n * Register event listeners for the communicator.\n *\n * @method registerEventListeners\n */\n H5PEmbedCommunicator.prototype.registerEventListeners = function() {\n var self = this;\n // Register message listener.\n window.addEventListener('message', function receiveMessage(event) {\n if (window.parent !== event.source || event.data.context !== 'h5p') {\n return; // Only handle messages from parent and in the correct context.\n }\n\n if (self._actionHandlers[event.data.action] !== undefined) {\n self._actionHandlers[event.data.action](event.data);\n }\n }, false);\n };\n\n return new H5PEmbedCommunicator();\n\n});\n"],"names":["define","H5PEmbedCommunicator","_actionHandlers","registerEventListeners","prototype","on","action","handler","send","data","undefined","context","window","parent","postMessage","self","this","addEventListener","event","source"],"mappings":"AAeAA,8BAAO,IAAI,eAMHC,qBAAuB,gBAClBC,gBAAkB,QAClBC,iCAITF,qBAAqBG,UAAUF,gBAAkB,GAQjDD,qBAAqBG,UAAUC,GAAK,SAASC,OAAQC,cAC5CL,gBAAgBI,QAAUC,SASnCN,qBAAqBG,UAAUI,KAAO,SAASF,OAAQG,WACtCC,IAATD,OACAA,KAAO,IAEXA,KAAKE,QAAU,MACfF,KAAKH,OAASA,OAGdM,OAAOC,OAAOC,YAAYL,KAAM,MASpCR,qBAAqBG,UAAUD,uBAAyB,eAChDY,KAAOC,KAEXJ,OAAOK,iBAAiB,WAAW,SAAwBC,OACnDN,OAAOC,SAAWK,MAAMC,QAAiC,QAAvBD,MAAMT,KAAKE,cAIDD,IAA5CK,KAAKb,gBAAgBgB,MAAMT,KAAKH,SAChCS,KAAKb,gBAAgBgB,MAAMT,KAAKH,QAAQY,MAAMT,SAEnD,IAGA,IAAIR"} -------------------------------------------------------------------------------- /amd/build/contenthubregistration.min.js: -------------------------------------------------------------------------------- 1 | define("mod_hvp/contenthubregistration",[],(function(){return{init:function(){const settings=H5PSettings;settings.container=document.getElementById("h5p-hub-registration"),H5PHub.createRegistrationUI(settings)}}})); 2 | 3 | //# sourceMappingURL=contenthubregistration.min.js.map -------------------------------------------------------------------------------- /amd/build/contenthubregistration.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"contenthubregistration.min.js","sources":["../src/contenthubregistration.js"],"sourcesContent":["define([], function () {\n return {\n init: function () {\n // Moodle complains if you pass in settings through init (js_call_amd) since they are too large\n const settings = H5PSettings;\n settings.container = document.getElementById('h5p-hub-registration');\n H5PHub.createRegistrationUI(settings);\n },\n };\n});\n"],"names":["define","init","settings","H5PSettings","container","document","getElementById","H5PHub","createRegistrationUI"],"mappings":"AAAAA,wCAAO,IAAI,iBACF,CACLC,KAAM,iBAEEC,SAAWC,YACjBD,SAASE,UAAYC,SAASC,eAAe,wBAC7CC,OAAOC,qBAAqBN"} -------------------------------------------------------------------------------- /amd/build/embed.min.js: -------------------------------------------------------------------------------- 1 | define("mod_hvp/embed",["jquery","mod_hvp/communicator"],(function($,H5PEmbedCommunicator){$(document).ready((function(){$(".h5p-iframe").ready((function(){initEmbedCommunicator=function(){var resizeDelay,instance=H5P.instances[0],parentIsFriendly=!1;H5PEmbedCommunicator.on("ready",(function(){H5PEmbedCommunicator.send("hello")})),H5PEmbedCommunicator.on("hello",(function(){parentIsFriendly=!0,iFrame.contentDocument.body.style.overflow="hidden",document.body.classList.add("h5p-resizing"),H5P.trigger(instance,"resize")})),H5PEmbedCommunicator.on("resizePrepared",(function(){H5PEmbedCommunicator.send("resize",{scrollHeight:iFrame.contentDocument.body.scrollHeight})})),H5PEmbedCommunicator.on("resize",(function(){H5P.trigger(instance,"resize")})),H5P.on(instance,"resize",(function(){H5P.isFullscreen||(clearTimeout(resizeDelay),resizeDelay=setTimeout((function(){parentIsFriendly?H5PEmbedCommunicator.send("prepareResize",{scrollHeight:iFrame.contentDocument.body.scrollHeight,clientHeight:iFrame.contentDocument.body.clientHeight}):H5PEmbedCommunicator.send("hello")}),0))})),H5P.trigger(instance,"resize")};var iFrame=document.querySelector(".h5p-iframe"),H5P=iFrame.contentWindow.H5P;H5P&&H5P.instances&&H5P.instances[0]?initEmbedCommunicator():(console.warn("H5P embed.js: ACK! Embedded H5P.instances[0] in lowest iframe is not set up yet. Waiting for 'initialized' event"),window.H5P.externalDispatcher.on("initialized",(function(event){console.log("H5P embed.js: 'initialized' event received"),H5P=iFrame.contentWindow.H5P,initEmbedCommunicator()})))}))}))})); 2 | 3 | //# sourceMappingURL=embed.min.js.map -------------------------------------------------------------------------------- /amd/build/embed.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"embed.min.js","sources":["../src/embed.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n/* eslint-disable no-undef, no-console, no-unused-vars, max-len */\ndefine(['jquery', 'mod_hvp/communicator'], function($, H5PEmbedCommunicator) {\n\n // Wait for instances to be initialize.\n $(document).ready(function() {\n $('.h5p-iframe').ready(function() {\n initEmbedCommunicator = function() {\n var resizeDelay;\n var instance = H5P.instances[0];\n var parentIsFriendly = false;\n\n // Handle that the resizer is loaded after the iframe.\n H5PEmbedCommunicator.on('ready', function() {\n H5PEmbedCommunicator.send('hello');\n });\n\n // Handle hello message from our parent window.\n H5PEmbedCommunicator.on('hello', function() {\n // Initial setup/handshake is done.\n parentIsFriendly = true;\n\n // Hide scrollbars for correct size.\n iFrame.contentDocument.body.style.overflow = 'hidden';\n\n document.body.classList.add('h5p-resizing');\n\n // Content need to be resized to fit the new iframe size.\n H5P.trigger(instance, 'resize');\n });\n\n // When resize has been prepared tell parent window to resize.\n H5PEmbedCommunicator.on('resizePrepared', function() {\n H5PEmbedCommunicator.send('resize', {\n scrollHeight: iFrame.contentDocument.body.scrollHeight\n });\n });\n\n H5PEmbedCommunicator.on('resize', function() {\n H5P.trigger(instance, 'resize');\n });\n\n H5P.on(instance, 'resize', function() {\n if (H5P.isFullscreen) {\n return; // Skip iframe resize.\n }\n\n // Use a delay to make sure iframe is resized to the correct size.\n clearTimeout(resizeDelay);\n resizeDelay = setTimeout(function() {\n // Only resize if the iframe can be resized.\n if (parentIsFriendly) {\n H5PEmbedCommunicator.send('prepareResize',\n {\n scrollHeight: iFrame.contentDocument.body.scrollHeight,\n clientHeight: iFrame.contentDocument.body.clientHeight\n }\n );\n } else {\n H5PEmbedCommunicator.send('hello');\n }\n }, 0);\n });\n\n // Trigger initial resize for instance.\n H5P.trigger(instance, 'resize');\n };\n var iFrame = document.querySelector('.h5p-iframe');\n var H5P = iFrame.contentWindow.H5P;\n // Check for H5P instances.\n if (!H5P || !H5P.instances || !H5P.instances[0]) {\n console.warn(\"H5P embed.js: ACK! Embedded H5P.instances[0] in lowest iframe is not set up yet. Waiting for 'initialized' event\");\n window.H5P.externalDispatcher.on('initialized', function(event) {\n console.log(\"H5P embed.js: 'initialized' event received\");\n H5P = iFrame.contentWindow.H5P;\n initEmbedCommunicator();\n });\n } else {\n initEmbedCommunicator();\n }\n });\n });\n\n});"],"names":["define","$","H5PEmbedCommunicator","document","ready","initEmbedCommunicator","resizeDelay","instance","H5P","instances","parentIsFriendly","on","send","iFrame","contentDocument","body","style","overflow","classList","add","trigger","scrollHeight","isFullscreen","clearTimeout","setTimeout","clientHeight","querySelector","contentWindow","console","warn","window","externalDispatcher","event","log"],"mappings":"AAeAA,uBAAO,CAAC,SAAU,yBAAyB,SAASC,EAAGC,sBAGnDD,EAAEE,UAAUC,OAAM,WACdH,EAAE,eAAeG,OAAM,WACnBC,sBAAwB,eAChBC,YACAC,SAAWC,IAAIC,UAAU,GACzBC,kBAAmB,EAGvBR,qBAAqBS,GAAG,SAAS,WAC7BT,qBAAqBU,KAAK,YAI9BV,qBAAqBS,GAAG,SAAS,WAE7BD,kBAAmB,EAGnBG,OAAOC,gBAAgBC,KAAKC,MAAMC,SAAW,SAE7Cd,SAASY,KAAKG,UAAUC,IAAI,gBAG5BX,IAAIY,QAAQb,SAAU,aAI1BL,qBAAqBS,GAAG,kBAAkB,WACtCT,qBAAqBU,KAAK,SAAU,CAChCS,aAAcR,OAAOC,gBAAgBC,KAAKM,kBAIlDnB,qBAAqBS,GAAG,UAAU,WAC9BH,IAAIY,QAAQb,SAAU,aAG1BC,IAAIG,GAAGJ,SAAU,UAAU,WACnBC,IAAIc,eAKRC,aAAajB,aACbA,YAAckB,YAAW,WAEjBd,iBACAR,qBAAqBU,KAAK,gBACtB,CACIS,aAAcR,OAAOC,gBAAgBC,KAAKM,aAC1CI,aAAcZ,OAAOC,gBAAgBC,KAAKU,eAIlDvB,qBAAqBU,KAAK,WAE/B,OAIPJ,IAAIY,QAAQb,SAAU,eAEtBM,OAASV,SAASuB,cAAc,eAChClB,IAAMK,OAAOc,cAAcnB,IAE1BA,KAAQA,IAAIC,WAAcD,IAAIC,UAAU,GAQzCJ,yBAPAuB,QAAQC,KAAK,oHACbC,OAAOtB,IAAIuB,mBAAmBpB,GAAG,eAAe,SAASqB,OACrDJ,QAAQK,IAAI,8CACZzB,IAAMK,OAAOc,cAAcnB,IAC3BH"} -------------------------------------------------------------------------------- /amd/src/communicator.js: -------------------------------------------------------------------------------- 1 | // This file is part of Moodle - http://moodle.org/ 2 | // 3 | // Moodle is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // Moodle is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with Moodle. If not, see . 15 | 16 | define([], function() { 17 | 18 | /** 19 | * When embedded the communicator helps talk to the parent page. 20 | * This is a copy of the H5P.communicator, which we need to communicate in this context 21 | */ 22 | var H5PEmbedCommunicator = function() { 23 | this._actionHandlers = {}; 24 | this.registerEventListeners(); 25 | }; 26 | 27 | /** @type {Object} Maps actions to functions. */ 28 | H5PEmbedCommunicator.prototype._actionHandlers = {}; 29 | 30 | /** 31 | * Register action listener. 32 | * 33 | * @param {string} action What you are waiting for 34 | * @param {function} handler What you want done 35 | */ 36 | H5PEmbedCommunicator.prototype.on = function(action, handler) { 37 | this._actionHandlers[action] = handler; 38 | }; 39 | 40 | /** 41 | * Send a message to the all mighty father. 42 | * 43 | * @param {string} action 44 | * @param {Object} [data] payload 45 | */ 46 | H5PEmbedCommunicator.prototype.send = function(action, data) { 47 | if (data === undefined) { 48 | data = {}; 49 | } 50 | data.context = 'h5p'; 51 | data.action = action; 52 | 53 | // Parent origin can be anything. 54 | window.parent.postMessage(data, '*'); 55 | }; 56 | 57 | 58 | /** 59 | * Register event listeners for the communicator. 60 | * 61 | * @method registerEventListeners 62 | */ 63 | H5PEmbedCommunicator.prototype.registerEventListeners = function() { 64 | var self = this; 65 | // Register message listener. 66 | window.addEventListener('message', function receiveMessage(event) { 67 | if (window.parent !== event.source || event.data.context !== 'h5p') { 68 | return; // Only handle messages from parent and in the correct context. 69 | } 70 | 71 | if (self._actionHandlers[event.data.action] !== undefined) { 72 | self._actionHandlers[event.data.action](event.data); 73 | } 74 | }, false); 75 | }; 76 | 77 | return new H5PEmbedCommunicator(); 78 | 79 | }); 80 | -------------------------------------------------------------------------------- /amd/src/contenthubregistration.js: -------------------------------------------------------------------------------- 1 | define([], function () { 2 | return { 3 | init: function () { 4 | // Moodle complains if you pass in settings through init (js_call_amd) since they are too large 5 | const settings = H5PSettings; 6 | settings.container = document.getElementById('h5p-hub-registration'); 7 | H5PHub.createRegistrationUI(settings); 8 | }, 9 | }; 10 | }); 11 | -------------------------------------------------------------------------------- /amd/src/embed.js: -------------------------------------------------------------------------------- 1 | // This file is part of Moodle - http://moodle.org/ 2 | // 3 | // Moodle is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // Moodle is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with Moodle. If not, see . 15 | /* eslint-disable no-undef, no-console, no-unused-vars, max-len */ 16 | define(['jquery', 'mod_hvp/communicator'], function($, H5PEmbedCommunicator) { 17 | 18 | // Wait for instances to be initialize. 19 | $(document).ready(function() { 20 | $('.h5p-iframe').ready(function() { 21 | initEmbedCommunicator = function() { 22 | var resizeDelay; 23 | var instance = H5P.instances[0]; 24 | var parentIsFriendly = false; 25 | 26 | // Handle that the resizer is loaded after the iframe. 27 | H5PEmbedCommunicator.on('ready', function() { 28 | H5PEmbedCommunicator.send('hello'); 29 | }); 30 | 31 | // Handle hello message from our parent window. 32 | H5PEmbedCommunicator.on('hello', function() { 33 | // Initial setup/handshake is done. 34 | parentIsFriendly = true; 35 | 36 | // Hide scrollbars for correct size. 37 | iFrame.contentDocument.body.style.overflow = 'hidden'; 38 | 39 | document.body.classList.add('h5p-resizing'); 40 | 41 | // Content need to be resized to fit the new iframe size. 42 | H5P.trigger(instance, 'resize'); 43 | }); 44 | 45 | // When resize has been prepared tell parent window to resize. 46 | H5PEmbedCommunicator.on('resizePrepared', function() { 47 | H5PEmbedCommunicator.send('resize', { 48 | scrollHeight: iFrame.contentDocument.body.scrollHeight 49 | }); 50 | }); 51 | 52 | H5PEmbedCommunicator.on('resize', function() { 53 | H5P.trigger(instance, 'resize'); 54 | }); 55 | 56 | H5P.on(instance, 'resize', function() { 57 | if (H5P.isFullscreen) { 58 | return; // Skip iframe resize. 59 | } 60 | 61 | // Use a delay to make sure iframe is resized to the correct size. 62 | clearTimeout(resizeDelay); 63 | resizeDelay = setTimeout(function() { 64 | // Only resize if the iframe can be resized. 65 | if (parentIsFriendly) { 66 | H5PEmbedCommunicator.send('prepareResize', 67 | { 68 | scrollHeight: iFrame.contentDocument.body.scrollHeight, 69 | clientHeight: iFrame.contentDocument.body.clientHeight 70 | } 71 | ); 72 | } else { 73 | H5PEmbedCommunicator.send('hello'); 74 | } 75 | }, 0); 76 | }); 77 | 78 | // Trigger initial resize for instance. 79 | H5P.trigger(instance, 'resize'); 80 | }; 81 | var iFrame = document.querySelector('.h5p-iframe'); 82 | var H5P = iFrame.contentWindow.H5P; 83 | // Check for H5P instances. 84 | if (!H5P || !H5P.instances || !H5P.instances[0]) { 85 | console.warn("H5P embed.js: ACK! Embedded H5P.instances[0] in lowest iframe is not set up yet. Waiting for 'initialized' event"); 86 | window.H5P.externalDispatcher.on('initialized', function(event) { 87 | console.log("H5P embed.js: 'initialized' event received"); 88 | H5P = iFrame.contentWindow.H5P; 89 | initEmbedCommunicator(); 90 | }); 91 | } else { 92 | initEmbedCommunicator(); 93 | } 94 | }); 95 | }); 96 | 97 | }); -------------------------------------------------------------------------------- /autoloader.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * References files that should be automatically loaded 19 | * 20 | * @package mod_hvp 21 | * @copyright 2016 Joubel AS 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | defined('MOODLE_INTERNAL') || die(); 25 | 26 | /** 27 | * A simple autoloader which makes it easy to load classes when you need them. 28 | * 29 | * @param string $class name 30 | */ 31 | function hvp_autoloader($class) { 32 | global $CFG; 33 | static $classmap; 34 | if (!isset($classmap)) { 35 | $classmap = array( 36 | // Core. 37 | 'H5PCore' => 'library/h5p.classes.php', 38 | 'H5PFrameworkInterface' => 'library/h5p.classes.php', 39 | 'H5PContentValidator' => 'library/h5p.classes.php', 40 | 'H5PValidator' => 'library/h5p.classes.php', 41 | 'H5PStorage' => 'library/h5p.classes.php', 42 | 'H5PExport' => 'library/h5p.classes.php', 43 | 'H5PDevelopment' => 'library/h5p-development.class.php', 44 | 'H5PFileStorage' => 'library/h5p-file-storage.interface.php', 45 | 'H5PDefaultStorage' => 'library/h5p-default-storage.class.php', 46 | 'H5PEventBase' => 'library/h5p-event-base.class.php', 47 | 'H5PMetadata' => 'library/h5p-metadata.class.php', 48 | 49 | // Editor. 50 | 'H5peditor' => 'editor/h5peditor.class.php', 51 | 'H5PEditorAjax' => 'editor/h5peditor-ajax.class.php', 52 | 'H5PEditorAjaxInterface' => 'editor/h5peditor-ajax.interface.php', 53 | 'H5peditorFile' => 'editor/h5peditor-file.class.php', 54 | 'H5peditorStorage' => 'editor/h5peditor-storage.interface.php', 55 | 56 | // Reporting. 57 | 'H5PReport' => 'reporting/h5p-report.class.php', 58 | 'H5PReportXAPIData' => 'reporting/h5p-report-xapi-data.class.php', 59 | 'ChoiceProcessor' => 'reporting/type-processors/choice-processor.class.php', 60 | 'CompoundProcessor' => 'reporting/type-processors/compound-processor.class.php', 61 | 'FillInProcessor' => 'reporting/type-processors/fill-in-processor.class.php', 62 | 'LongChoiceProcessor' => 'reporting/type-processors/long-choice-processor.class.php', 63 | 'MatchingProcessor' => 'reporting/type-processors/matching-processor.class.php', 64 | 'TrueFalseProcessor' => 'reporting/type-processors/true-false-processor.class.php', 65 | 'IVOpenEndedQuestionProcessor' => 'reporting/type-processors/iv-open-ended-question-processor.class.php', 66 | 'TypeProcessor' => 'reporting/type-processors/type-processor.class.php', 67 | 'DocumentationToolProcessor' => 'reporting/type-processors/compound/documentation-tool-processor.class.php', 68 | 'GoalsPageProcessor' => 'reporting/type-processors/compound/goals-page-processor.class.php', 69 | 'GoalsAssessmentPageProcessor' => 'reporting/type-processors/compound/goals-assessment-page-processor.class.php', 70 | 'StandardPageProcessor' => 'reporting/type-processors/compound/standard-page-processor.class.php', 71 | 72 | // Plugin specific classes are loaded by Moodle. 73 | ); 74 | } 75 | 76 | if (isset($classmap[$class])) { 77 | require_once($CFG->dirroot . '/mod/hvp/' . $classmap[$class]); 78 | } 79 | } 80 | spl_autoload_register('hvp_autoloader'); 81 | -------------------------------------------------------------------------------- /backup/moodle2/backup_hvp_activity_task.class.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines backup_hvp_activity_task class 19 | * 20 | * @package mod_hvp 21 | * @category backup 22 | * @copyright 2016 Joubel AS 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | require_once($CFG->dirroot . '/mod/hvp/backup/moodle2/backup_hvp_stepslib.php'); 29 | 30 | /** 31 | * Provides the steps to perform one complete backup of a H5P instance 32 | * 33 | * @copyright 2018 Joubel AS 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class backup_hvp_activity_task extends backup_activity_task { 37 | 38 | /** 39 | * No specific settings for this activity 40 | */ 41 | protected function define_my_settings() { 42 | } 43 | 44 | /** 45 | * Defines a backup step to store the instance data in the hvp.xml file 46 | */ 47 | protected function define_my_steps() { 48 | global $CFG; 49 | 50 | // Add hvp activity data and content files. 51 | $this->add_step(new backup_hvp_activity_structure_step('hvp_structure', 'hvp.xml')); 52 | 53 | // Allow user to override library backup. 54 | $backuplibraries = !(isset($CFG->mod_hvp_backup_libraries) && $CFG->mod_hvp_backup_libraries === '0'); 55 | 56 | // Exclude hvp libraries step for local 'imports'. 57 | if ($backuplibraries && backup_controller_dbops::backup_includes_files($this->plan->get_backupid())) { 58 | 59 | // Note that this step will only run once per backup as it generates 60 | // a shared resource. 61 | $this->add_step(new backup_hvp_libraries_structure_step('hvp_libraries', 'hvp_libraries.xml')); 62 | } 63 | } 64 | 65 | /** 66 | * Encodes URLs to the index.php and view.php scripts 67 | * 68 | * @param string $content some HTML text that eventually contains URLs to the activity instance scripts 69 | * @return string the content with the URLs encoded 70 | */ 71 | static public function encode_content_links($content) { 72 | global $CFG; 73 | 74 | $base = preg_quote($CFG->wwwroot, "/"); 75 | 76 | // Link to the list of glossaries. 77 | $search = "/(".$base."\/mod\/hvp\/index.php\?id\=)([0-9]+)/"; 78 | $content = preg_replace($search, '$@HVPINDEX*$2@$', $content); 79 | 80 | // Link to hvp view by module id. 81 | $search = "/(".$base."\/mod\/hvp\/view.php\?id\=)([0-9]+)/"; 82 | $content = preg_replace($search, '$@HVPVIEWBYID*$2@$', $content); 83 | 84 | // Link to hvp embed by module id. 85 | $search = "/(".$base."\/mod\/hvp\/embed.php\?id\=)([0-9]+)/"; 86 | $content = preg_replace($search, '$@HVPEMBEDBYID*$2@$', $content); 87 | 88 | return $content; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /backup/moodle2/backup_hvp_stepslib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines backup structure steps for both hvp content and hvp libraries. 19 | * 20 | * @package mod_hvp 21 | * @category backup 22 | * @copyright 2016 Joubel AS 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | /** 29 | * Define the complete hvp structure for backup, with file and id annotations 30 | * 31 | * @copyright 2018 Joubel AS 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class backup_hvp_activity_structure_step extends backup_activity_structure_step { 35 | 36 | /** 37 | * Defines backup element's structure 38 | * 39 | * @return backup_nested_element 40 | * @throws base_element_struct_exception 41 | * @throws base_step_exception 42 | */ 43 | protected function define_structure() { 44 | 45 | // To know if we are including user info. 46 | $userinfo = $this->get_setting_value('userinfo'); 47 | 48 | // Define each element separated. 49 | $hvp = new backup_nested_element('hvp', array('id'), array( 50 | 'name', 51 | 'machine_name', 52 | 'major_version', 53 | 'minor_version', 54 | 'intro', 55 | 'introformat', 56 | 'json_content', 57 | 'embed_type', 58 | 'disable', 59 | 'content_type', 60 | 'source', 61 | 'year_from', 62 | 'year_to', 63 | 'license_version', 64 | 'changes', 65 | 'license_extras', 66 | 'author_comments', 67 | 'slug', 68 | 'timecreated', 69 | 'timemodified', 70 | 'authors', 71 | 'license', 72 | 'completionpass' 73 | )); 74 | 75 | // User data. 76 | $entries = new backup_nested_element('content_user_data'); 77 | $contentuserdata = new backup_nested_element('entry', array( 78 | 'user_id', // Annotated. 79 | 'sub_content_id' 80 | ), array( 81 | 'data_id', 82 | 'data', 83 | 'preloaded', 84 | 'delete_on_content_change', 85 | )); 86 | 87 | // Build the tree. 88 | 89 | $hvp->add_child($entries); 90 | $entries->add_child($contentuserdata); 91 | 92 | // Define sources. 93 | 94 | // Uses library name and version instead of main_library_id. 95 | $hvp->set_source_sql(' 96 | SELECT h.id, 97 | hl.machine_name, 98 | hl.major_version, 99 | hl.minor_version, 100 | h.name, 101 | h.intro, 102 | h.introformat, 103 | h.json_content, 104 | h.embed_type, 105 | h.disable, 106 | h.content_type, 107 | h.slug, 108 | h.timecreated, 109 | h.timemodified, 110 | h.authors, 111 | h.source, 112 | h.year_from, 113 | h.year_to, 114 | h.license_version, 115 | h.changes, 116 | h.license_extras, 117 | h.author_comments, 118 | h.license, 119 | h.completionpass 120 | FROM {hvp} h 121 | JOIN {hvp_libraries} hl ON hl.id = h.main_library_id 122 | WHERE h.id = ?', array(backup::VAR_ACTIVITYID)); 123 | 124 | // All the rest of elements only happen if we are including user info. 125 | if ($userinfo) { 126 | $contentuserdata->set_source_table('hvp_content_user_data', array('hvp_id' => backup::VAR_PARENTID)); 127 | } 128 | 129 | // Define id annotations. 130 | $contentuserdata->annotate_ids('user', 'user_id'); 131 | // In an ideal world we would use the main_library_id and annotate that 132 | // but since we cannot know the required dependencies of the content 133 | // without parsing json_content and crawling the libraries_libraries 134 | // (library dependencies) table it's much easier to just include all 135 | // installed libraries. 136 | 137 | // Define file annotations. 138 | $hvp->annotate_files('mod_hvp', 'intro', null, null); 139 | $hvp->annotate_files('mod_hvp', 'content', null, null); 140 | 141 | // Return the root element (hvp), wrapped into standard activity structure. 142 | return $this->prepare_activity_structure($hvp); 143 | } 144 | } 145 | 146 | /** 147 | * Backup h5p libraries. 148 | * 149 | * Structure step in charge of constructing the hvp_libraries.xml file for 150 | * all the H5P libraries. 151 | * 152 | * @copyright 2018 Joubel AS 153 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 154 | */ 155 | class backup_hvp_libraries_structure_step extends backup_structure_step { 156 | 157 | /** 158 | * Determines if backup step should be executed 159 | * 160 | * @return bool 161 | * @throws backup_step_exception 162 | */ 163 | protected function execute_condition() { 164 | $fullpath = $this->task->get_taskbasepath(); 165 | if (empty($fullpath)) { 166 | throw new backup_step_exception('backup_structure_step_undefined_fullpath'); 167 | } 168 | 169 | // Modify filename to use a globally shared file for all libraries. 170 | $this->filename = "../{$this->filename}"; 171 | 172 | // Append the filename to the full path. 173 | $fullpath = rtrim($fullpath, '/') . '/' . $this->filename; 174 | 175 | // Determine if already generated. 176 | return !file_exists($fullpath); 177 | } 178 | 179 | /** 180 | * Defines the structure to be executed by this backup step 181 | * 182 | * @return backup_nested_element 183 | * @throws base_element_struct_exception 184 | * @throws dml_exception 185 | */ 186 | protected function define_structure() { 187 | // Libraries. 188 | $libraries = new backup_nested_element('hvp_libraries'); 189 | $library = new backup_nested_element('library', array('id'), array( 190 | 'title', 191 | 'machine_name', 192 | 'major_version', 193 | 'minor_version', 194 | 'patch_version', 195 | 'runnable', 196 | 'fullscreen', 197 | 'embed_types', 198 | 'preloaded_js', 199 | 'preloaded_css', 200 | 'drop_library_css', 201 | 'semantics', 202 | 'restricted', 203 | 'tutorial_url', 204 | 'add_to', 205 | 'metadata' 206 | )); 207 | 208 | // Library translations. 209 | $translations = new backup_nested_element('translations'); 210 | $translation = new backup_nested_element('translation', array( 211 | 'language_code' 212 | ), array( 213 | 'language_json' 214 | )); 215 | 216 | // Library dependencies. 217 | $dependencies = new backup_nested_element('dependencies'); 218 | $dependency = new backup_nested_element('dependency', array( 219 | 'required_library_id' 220 | ), array( 221 | 'dependency_type' 222 | )); 223 | 224 | // Build the tree. 225 | $libraries->add_child($library); 226 | 227 | $library->add_child($translations); 228 | $translations->add_child($translation); 229 | 230 | $library->add_child($dependencies); 231 | $dependencies->add_child($dependency); 232 | 233 | // Define sources. 234 | 235 | $library->set_source_table('hvp_libraries', array()); 236 | 237 | $translation->set_source_table('hvp_libraries_languages', array('library_id' => backup::VAR_PARENTID)); 238 | 239 | $dependency->set_source_table('hvp_libraries_libraries', array('library_id' => backup::VAR_PARENTID)); 240 | 241 | // Define file annotations. 242 | $context = \context_system::instance(); 243 | $library->annotate_files('mod_hvp', 'libraries', null, $context->id); 244 | 245 | // Return root element. 246 | return $libraries; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /backup/moodle2/restore_hvp_activity_task.class.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Describe how H5P activites are to be restored from backup 19 | * 20 | * @package mod_hvp 21 | * @category backup 22 | * @copyright 2016 Joubel AS 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | require_once($CFG->dirroot . '/mod/hvp/backup/moodle2/restore_hvp_stepslib.php'); // Because it exists (must). 29 | 30 | /** 31 | * Hvp restore task that provides all the settings and steps to perform one complete restore of the activity. 32 | * 33 | * @copyright 2018 Joubel AS 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class restore_hvp_activity_task extends restore_activity_task { 37 | 38 | /** 39 | * Define (add) particular settings this activity can have 40 | */ 41 | protected function define_my_settings() { 42 | // No particular settings for this activity. 43 | } 44 | 45 | /** 46 | * Define (add) particular steps this activity can have 47 | */ 48 | protected function define_my_steps() { 49 | // Add H5P libraries. 50 | $this->add_step(new restore_hvp_libraries_structure_step('hvp_structure', 'hvp_libraries.xml')); 51 | 52 | // Add H5P content. 53 | $this->add_step(new restore_hvp_activity_structure_step('hvp_structure', 'hvp.xml')); 54 | } 55 | 56 | /** 57 | * Define the contents in the activity that must be 58 | * processed by the link decoder 59 | */ 60 | static public function define_decode_contents() { 61 | $contents = array(); 62 | 63 | $contents[] = new restore_decode_content('hvp', array('intro'), 'hvp'); 64 | 65 | return $contents; 66 | } 67 | 68 | /** 69 | * Define the decoding rules for links belonging 70 | * to the activity to be executed by the link decoder 71 | */ 72 | static public function define_decode_rules() { 73 | $rules = array(); 74 | 75 | $rules[] = new restore_decode_rule('HVPVIEWBYID', '/mod/hvp/view.php?id=$1', 'course_module'); 76 | $rules[] = new restore_decode_rule('HVPINDEX', '/mod/hvp/index.php?id=$1', 'course'); 77 | $rules[] = new restore_decode_rule('HVPEMBEDBYID', '/mod/hvp/embed.php?id=$1', 'course_module'); 78 | 79 | return $rules; 80 | } 81 | 82 | /** 83 | * Define the restore log rules that will be applied 84 | * by the {@link restore_logs_processor} when restoring 85 | * hvp logs. It must return one array 86 | * of {@link restore_log_rule} objects 87 | */ 88 | static public function define_restore_log_rules() { 89 | $rules = array(); 90 | 91 | $rules[] = new restore_log_rule('hvp', 'add', 'view.php?id={course_module}', '{hvp}'); 92 | $rules[] = new restore_log_rule('hvp', 'update', 'view.php?id={course_module}', '{hvp}'); 93 | $rules[] = new restore_log_rule('hvp', 'view', 'view.php?id={course_module}', '{hvp}'); 94 | 95 | return $rules; 96 | } 97 | 98 | /** 99 | * Define the restore log rules that will be applied 100 | * by the {@link restore_logs_processor} when restoring 101 | * course logs. It must return one array 102 | * of {@link restore_log_rule} objects 103 | * 104 | * Note this rules are applied when restoring course logs 105 | * by the restore final task, but are defined here at 106 | * activity level. All them are rules not linked to any module instance (cmid = 0) 107 | */ 108 | static public function define_restore_log_rules_for_course() { 109 | $rules = array(); 110 | 111 | $rules[] = new restore_log_rule('hvp', 'view all', 'index.php?id={course}', null); 112 | 113 | return $rules; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /classes/admin_setting_html.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | defined('MOODLE_INTERNAL') || die(); 18 | 19 | /** 20 | * No setting - just html 21 | * Note: since admin_setting is not namespaced, this can not be namespaced and put into a class 22 | */ 23 | class admin_setting_html extends admin_setting { 24 | 25 | private $hubinfo; 26 | 27 | /** 28 | * not a setting, just html 29 | * 30 | * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. 31 | */ 32 | public function __construct($name, $translation, $hubinfo) { 33 | $this->nosave = true; 34 | $this->hubinfo = $hubinfo; 35 | parent::__construct($name, $translation, '', ''); 36 | } 37 | 38 | /** 39 | * Always returns true 40 | * @return bool Always returns true 41 | */ 42 | public function get_setting() { 43 | return true; 44 | } 45 | 46 | /** 47 | * Always returns true 48 | * @return bool Always returns true 49 | */ 50 | public function get_defaultsetting() { 51 | return true; 52 | } 53 | 54 | /** 55 | * Never write settings 56 | * @return string Always returns an empty string 57 | */ 58 | public function write_setting($data) { 59 | // Do not write any setting. 60 | return ''; 61 | } 62 | 63 | /** 64 | * Returns an HTML string 65 | * @return string Returns an HTML string 66 | */ 67 | public function output_html($data, $query = '') { 68 | global $OUTPUT; 69 | $registrationurl = new moodle_url('/mod/hvp/content_hub_registration.php'); 70 | if ($this->hubinfo === false) { 71 | $this->hubinfo = (object) []; 72 | } 73 | $this->hubinfo->registrationurl = $registrationurl->out(false); 74 | return $OUTPUT->render_from_template('mod_hvp/content_hub_registration_box', $this->hubinfo); 75 | } 76 | } -------------------------------------------------------------------------------- /classes/content_hub_service.php: -------------------------------------------------------------------------------- 1 | . 16 | namespace mod_hvp; 17 | 18 | use Exception; 19 | use moodle_url; 20 | 21 | defined('MOODLE_INTERNAL') || die(); 22 | 23 | /** 24 | * Service for communicating with the content hub 25 | * 26 | * @package mod_hvp 27 | * @copyright 2020 Joubel AS 28 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 29 | */ 30 | class content_hub_service { 31 | 32 | /** 33 | * Get settings for content hub registration UI 34 | * 35 | * @return array 36 | * @throws Exception 37 | */ 38 | public static function get_registration_ui_settings() { 39 | $core = framework::instance(); 40 | $registrationurl = new moodle_url('/mod/hvp/ajax.php', [ 41 | 'action' => 'contenthubregistration', 42 | ]); 43 | $accountsettingsurl = new moodle_url('/admin/settings.php?section=modsettinghvp'); 44 | 45 | return [ 46 | 'registrationURL' => $registrationurl->out(true), 47 | 'accountSettingsUrl' => $accountsettingsurl->out(true), 48 | 'token' => $core::createToken('contentHubRegistration'), 49 | 'l10n' => $core->getLocalization(), 50 | 'licenseAgreementTitle' => get_string('contenthub:licenseagreementtitle', 'hvp'), 51 | 'licenseAgreementDescription' => get_string('contenthub:licenseagreementdescription', 'hvp'), 52 | 'licenseAgreementMainText' => get_string('contenthub:licenseagreementmaintext', 'hvp'), 53 | 'accountInfo' => $core->hubAccountInfo(), 54 | ]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /classes/content_type_cache_form.php: -------------------------------------------------------------------------------- 1 | . 16 | /** 17 | * \mod_hvp\content_type_cache_form class 18 | * 19 | * @package mod_hvp 20 | * @copyright 2017 Joubel AS 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | namespace mod_hvp; 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | global $CFG; 28 | require_once($CFG->libdir . '/formslib.php'); 29 | 30 | /** 31 | * Form to update the content type cache that mirrors the available libraries in the H5P hub. 32 | * 33 | * @package mod_hvp 34 | * @copyright 2017 Joubel AS 35 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 | */ 37 | class content_type_cache_form extends \moodleform { 38 | 39 | /** 40 | * Define form elements 41 | */ 42 | public function definition() { 43 | // Get form. 44 | $mform = $this->_form; 45 | 46 | // Get and format date. 47 | $lastupdate = get_config('mod_hvp', 'content_type_cache_updated_at'); 48 | 49 | $dateformatted = $lastupdate ? \userdate($lastupdate) : get_string('ctcacheneverupdated', 'hvp'); 50 | 51 | // Add last update info. 52 | $mform->addElement('static', 'lastupdate', 53 | get_string('ctcachelastupdatelabel', 'hvp'), $dateformatted); 54 | 55 | $mform->addElement('static', 'lastupdatedescription', '', 56 | get_string('ctcachedescription', 'hvp')); 57 | 58 | // Update cache button. 59 | $this->add_action_buttons(false, get_string('ctcachebuttonlabel', 'hvp')); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /classes/content_user_data.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_hvp content user data. 19 | * 20 | * @package mod_hvp 21 | * @since Moodle 2.7 22 | * @copyright 2016 Joubel AS 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace mod_hvp; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | /** 31 | * Class content_user_data handles user data and corresponding db operations. 32 | * 33 | * @package mod_hvp 34 | * @copyright 2018 Joubel AS 35 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 | */ 37 | class content_user_data { 38 | 39 | /** 40 | * Retrieves ajax parameters for content and update or delete 41 | * user data depending on params. 42 | * 43 | * @throws \coding_exception 44 | * @throws \dml_exception 45 | */ 46 | public static function handle_ajax() { 47 | // Query String Parameters. 48 | $contentid = required_param('content_id', PARAM_INT); 49 | $dataid = required_param('data_type', PARAM_RAW); 50 | $subcontentid = required_param('sub_content_id', PARAM_INT); 51 | 52 | // Form Data. 53 | $data = optional_param('data', null, PARAM_RAW); 54 | $preload = optional_param('preload', null, PARAM_INT); 55 | $invalidate = optional_param('invalidate', null, PARAM_INT); 56 | 57 | if ($contentid === null || $dataid === null || $subcontentid === null) { 58 | // Missing parameters. 59 | \H5PCore::ajaxError(get_string('missingparameters', 'hvp')); 60 | return; 61 | } 62 | 63 | // Saving data. 64 | if ($data !== null && $preload !== null && $invalidate !== null) { 65 | self::store_data($contentid, $subcontentid, $dataid, $data, $preload, $invalidate); 66 | } else { 67 | self::fetch_existing_data($contentid, $subcontentid, $dataid); 68 | } 69 | } 70 | 71 | 72 | /** 73 | * Stores content user data 74 | * 75 | * @param $contentid 76 | * @param $subcontentid 77 | * @param $dataid 78 | * @param $data 79 | * @param $preload 80 | * @param $invalidate 81 | * 82 | * @throws \coding_exception 83 | * @throws \dml_exception 84 | */ 85 | private static function store_data($contentid, $subcontentid, $dataid, $data, $preload, $invalidate) { 86 | // Validate token. 87 | if (!\H5PCore::validToken('contentuserdata', required_param('token', PARAM_RAW))) { 88 | \H5PCore::ajaxError(get_string('invalidtoken', 'hvp')); 89 | return; 90 | } 91 | 92 | if ($contentid === 0) { 93 | $context = \context::instance_by_id(required_param('contextId', PARAM_RAW)); 94 | } else { 95 | // Load course module for content to get context. 96 | $cm = get_coursemodule_from_instance('hvp', $contentid); 97 | if (!$cm) { 98 | \H5PCore::ajaxError('No such content'); 99 | http_response_code(404); 100 | return; 101 | } 102 | $context = \context_module::instance($cm->id); 103 | } 104 | 105 | // Check permissions. 106 | if (!has_capability('mod/hvp:savecontentuserdata', $context)) { 107 | \H5PCore::ajaxError(get_string('nopermissiontosavecontentuserdata', 'hvp')); 108 | http_response_code(403); 109 | return; 110 | } 111 | 112 | if ($data === '0') { 113 | // Delete user data. 114 | self::delete_user_data($contentid, $subcontentid, $dataid); 115 | } else { 116 | // Save user data. 117 | self::save_user_data($contentid, $subcontentid, $dataid, $preload, $invalidate, $data); 118 | } 119 | \H5PCore::ajaxSuccess(); 120 | } 121 | 122 | /** 123 | * Return existing content user data 124 | * 125 | * @param $contentid 126 | * @param $subcontentid 127 | * @param $dataid 128 | * 129 | * @throws \dml_exception 130 | */ 131 | private static function fetch_existing_data($contentid, $subcontentid, $dataid) { 132 | // Fetch user data. 133 | $userdata = self::get_user_data($contentid, $subcontentid, $dataid); 134 | \H5PCore::ajaxSuccess($userdata ? $userdata->data : null); 135 | } 136 | 137 | /** 138 | * Get user data for content. 139 | * 140 | * @param $contentid 141 | * @param $subcontentid 142 | * @param $dataid 143 | * 144 | * @return mixed 145 | * @throws \dml_exception 146 | */ 147 | public static function get_user_data($contentid, $subcontentid, $dataid) { 148 | global $DB, $USER; 149 | 150 | $result = $DB->get_record('hvp_content_user_data', array( 151 | 'user_id' => $USER->id, 152 | 'hvp_id' => $contentid, 153 | 'sub_content_id' => $subcontentid, 154 | 'data_id' => $dataid 155 | ) 156 | ); 157 | 158 | return $result; 159 | } 160 | 161 | /** 162 | * Save user data for specific content in database. 163 | * 164 | * @param $contentid 165 | * @param $subcontentid 166 | * @param $dataid 167 | * @param $preload 168 | * @param $invalidate 169 | * @param $data 170 | * 171 | * @throws \dml_exception 172 | */ 173 | public static function save_user_data($contentid, $subcontentid, $dataid, $preload, $invalidate, $data) { 174 | global $DB, $USER; 175 | 176 | // Determine if we should update or insert. 177 | $update = self::get_user_data($contentid, $subcontentid, $dataid); 178 | 179 | // Wash values to ensure 0 or 1. 180 | $preload = ($preload === '0' || $preload === 0) ? 0 : 1; 181 | $invalidate = ($invalidate === '0' || $invalidate === 0) ? 0 : 1; 182 | 183 | // New data to be inserted. 184 | $newdata = (object)array( 185 | 'user_id' => $USER->id, 186 | 'hvp_id' => $contentid, 187 | 'sub_content_id' => $subcontentid, 188 | 'data_id' => $dataid, 189 | 'data' => $data, 190 | 'preloaded' => $preload, 191 | 'delete_on_content_change' => $invalidate 192 | ); 193 | 194 | // Does not exist. 195 | if ($update === false) { 196 | // Insert new data. 197 | $DB->insert_record('hvp_content_user_data', $newdata); 198 | } else { 199 | // Get old data id. 200 | $newdata->id = $update->id; 201 | 202 | // Update old data. 203 | $DB->update_record('hvp_content_user_data', $newdata); 204 | } 205 | } 206 | 207 | /** 208 | * Delete user data with specific content from database 209 | * 210 | * @param $contentid 211 | * @param $subcontentid 212 | * @param $dataid 213 | * 214 | * @throws \dml_exception 215 | */ 216 | public static function delete_user_data($contentid, $subcontentid, $dataid) { 217 | global $DB, $USER; 218 | 219 | $DB->delete_records('hvp_content_user_data', array( 220 | 'user_id' => $USER->id, 221 | 'hvp_id' => $contentid, 222 | 'sub_content_id' => $subcontentid, 223 | 'data_id' => $dataid 224 | )); 225 | } 226 | 227 | /** 228 | * Load user data for specific content 229 | * 230 | * @param $contentid 231 | * 232 | * @return array User data for specific content if found, else null 233 | * @throws \dml_exception 234 | */ 235 | public static function load_pre_loaded_user_data($contentid) { 236 | global $DB, $USER; 237 | 238 | $preloadeduserdata = array( 239 | 'state' => '{}' 240 | ); 241 | 242 | $results = $DB->get_records('hvp_content_user_data', array( 243 | 'user_id' => $USER->id, 244 | 'hvp_id' => $contentid, 245 | 'sub_content_id' => 0, 246 | 'preloaded' => 1 247 | )); 248 | 249 | // Get data for data ids. 250 | foreach ($results as $contentuserdata) { 251 | $preloadeduserdata[$contentuserdata->data_id] = $contentuserdata->data; 252 | } 253 | 254 | return $preloadeduserdata; 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /classes/curl.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_hvp content user data. 19 | * 20 | * @package mod_hvp 21 | * @since Moodle 3.10 22 | * @copyright 2020 Joubel AS 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace mod_hvp; 27 | 28 | use curl as moodlecurl; 29 | 30 | defined('MOODLE_INTERNAL') || die(); 31 | 32 | /** 33 | * Override Moodle's curl class to provide proper PUT support. 34 | * 35 | * @package mod_hvp 36 | * @copyright 2020 Joubel AS 37 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 | */ 39 | class curl extends moodlecurl { 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | public function post($url, $params = '', $options = array()) { 45 | $options['CURLOPT_POST'] = 1; 46 | $options['CURLOPT_POSTFIELDS'] = $params; 47 | return $this->request($url, $options); 48 | } 49 | 50 | /** 51 | * @inheritdoc 52 | */ 53 | public function put($url, $params = '', $options = array()) { 54 | $options['CURLOPT_CUSTOMREQUEST'] = 'PUT'; 55 | $options['CURLOPT_POSTFIELDS'] = $params; 56 | return $this->request($url, $options); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /classes/editor_ajax.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * \mod_hvp\editor_ajax class 19 | * 20 | * @package mod_hvp 21 | * @copyright 2016 Joubel AS 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_hvp; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | require_once(__DIR__ . '/../autoloader.php'); 30 | 31 | /** 32 | * Moodle's implementation of the H5P Editor Ajax interface. 33 | * Makes it possible for the editor's core ajax functionality to communicate with the 34 | * database used by Moodle. 35 | * 36 | * @package mod_hvp 37 | * @copyright 2016 Joubel AS 38 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 | */ 40 | class editor_ajax implements \H5PEditorAjaxInterface { 41 | 42 | /** 43 | * Gets latest library versions that exists locally 44 | * 45 | * @return array Latest version of all local libraries 46 | * @throws \dml_exception 47 | */ 48 | // @codingStandardsIgnoreLine 49 | public function getLatestLibraryVersions() { 50 | global $DB; 51 | 52 | $maxmajorversionsql = " 53 | SELECT hl.machine_name, MAX(hl.major_version) AS major_version 54 | FROM {hvp_libraries} hl 55 | WHERE hl.runnable = 1 56 | GROUP BY hl.machine_name"; 57 | 58 | $maxminorversionsql = " 59 | SELECT hl2.machine_name, hl2.major_version, MAX(hl2.minor_version) AS minor_version 60 | FROM ({$maxmajorversionsql}) hl1 61 | JOIN {hvp_libraries} hl2 62 | ON hl1.machine_name = hl2.machine_name 63 | AND hl1.major_version = hl2.major_version 64 | GROUP BY hl2.machine_name, hl2.major_version"; 65 | 66 | return $DB->get_records_sql(" 67 | SELECT hl4.id, hl4.machine_name, hl4.title, hl4.major_version, 68 | hl4.minor_version, hl4.patch_version, hl4.has_icon, hl4.restricted 69 | FROM {hvp_libraries} hl4 70 | JOIN ({$maxminorversionsql}) hl3 71 | ON hl4.machine_name = hl3.machine_name 72 | AND hl4.major_version = hl3.major_version 73 | AND hl4.minor_version = hl3.minor_version" 74 | ); 75 | } 76 | 77 | /** 78 | * Get locally stored Content Type Cache. If machine name is provided 79 | * it will only get the given content type from the cache 80 | * 81 | * @param null $machinename 82 | * 83 | * @return array|mixed|null|object Returns results from querying the database 84 | * @throws \dml_exception 85 | */ 86 | // @codingStandardsIgnoreLine 87 | public function getContentTypeCache($machinename = null) { 88 | global $DB; 89 | 90 | if ($machinename) { 91 | return $DB->get_record_sql( 92 | "SELECT id, is_recommended 93 | FROM {hvp_libraries_hub_cache} 94 | WHERE machine_name = ?", 95 | array($machinename) 96 | ); 97 | } 98 | 99 | return $DB->get_records("hvp_libraries_hub_cache"); 100 | } 101 | 102 | /** 103 | * Gets recently used libraries for the current author 104 | * 105 | * @return array machine names. The first element in the array is the 106 | * most recently used. 107 | */ 108 | // @codingStandardsIgnoreLine 109 | public function getAuthorsRecentlyUsedLibraries() { 110 | global $DB; 111 | global $USER; 112 | $recentlyused = array(); 113 | 114 | $results = $DB->get_records_sql( 115 | "SELECT library_name, max(created_at) AS max_created_at 116 | FROM {hvp_events} 117 | WHERE type='content' AND sub_type = 'create' AND user_id = ? 118 | GROUP BY library_name 119 | ORDER BY max_created_at DESC", array($USER->id)); 120 | 121 | foreach ($results as $row) { 122 | $recentlyused[] = $row->library_name; 123 | } 124 | 125 | return $recentlyused; 126 | } 127 | 128 | /** 129 | * Checks if the provided token is valid for this endpoint 130 | * 131 | * @param string $token The token that will be validated for. 132 | * 133 | * @return bool True if successful validation 134 | */ 135 | // @codingStandardsIgnoreLine 136 | public function validateEditorToken($token) { 137 | return \H5PCore::validToken('editorajax', $token); 138 | } 139 | 140 | /** 141 | * Get translations for a language for a list of libraries 142 | * 143 | * @param array $libraries An array of libraries, in the form " . 144 | * @param string $language_code 145 | * 146 | * @return array 147 | * @throws \dml_exception 148 | */ 149 | public function getTranslations($libraries, $language_code) { 150 | global $DB; 151 | 152 | $translations = array(); 153 | 154 | foreach ($libraries as $library) { 155 | $parsedLib = \H5PCore::libraryFromString($library); 156 | 157 | $sql = " 158 | SELECT language_json 159 | FROM {hvp_libraries} lib 160 | LEFT JOIN {hvp_libraries_languages} lang 161 | ON lib.id = lang.library_id 162 | WHERE lib.machine_name = :machine_name AND 163 | lib.major_version = :major_version AND 164 | lib.minor_version = :minor_version AND 165 | lang.language_code = :language_code"; 166 | $translation = $DB->get_field_sql($sql, array( 167 | 'machine_name' => $parsedLib['machineName'], 168 | 'major_version' => $parsedLib['majorVersion'], 169 | 'minor_version' => $parsedLib['minorVersion'], 170 | 'language_code' => $language_code, 171 | )); 172 | 173 | if ($translation !== false) { 174 | $translations[$library] = $translation; 175 | } 176 | } 177 | 178 | return $translations; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /classes/event.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_hvp event logger, makes it easy to track events throughout 19 | * the H5P system. 20 | * 21 | * @package mod_hvp 22 | * @copyright 2016 Joubel AS 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace mod_hvp; 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | class event extends \H5PEventBase { 30 | private $user; 31 | 32 | /** 33 | * @inheritdoc 34 | */ 35 | public function __construct($type, $subtype = null, $contentid = null, 36 | $contenttitle = null, $libraryname = null, $libraryversion = null) { 37 | global $USER; 38 | 39 | // Track the who initiated the event. 40 | $this->user = $USER->id; 41 | 42 | parent::__construct($type, $subtype, $contentid, $contenttitle, $libraryname, $libraryversion); 43 | } 44 | 45 | /** 46 | * Store the event. 47 | * 48 | * @return int Event ID 49 | */ 50 | protected function save() { 51 | global $DB; 52 | 53 | // Get data in array format without null values. 54 | $data = $this->getDataArray(); 55 | 56 | // Add user. 57 | $data['user_id'] = $this->user; 58 | 59 | return $DB->insert_record('hvp_events', $data); 60 | } 61 | 62 | /** 63 | * @inheritdoc 64 | */ 65 | // @codingStandardsIgnoreLine 66 | protected function saveStats() { 67 | global $DB; 68 | $type = $this->type . ' ' . $this->sub_type; 69 | 70 | // Grab current counter to check if it exists. 71 | $id = $DB->get_field_sql( 72 | "SELECT id 73 | FROM {hvp_counters} 74 | WHERE type = ? 75 | AND library_name = ? 76 | AND library_version = ?", 77 | array($type, $this->library_name, $this->library_version) 78 | ); 79 | 80 | if ($id === false) { 81 | // No counter found, insert new one. 82 | $DB->insert_record('hvp_counters', array( 83 | 'type' => $type, 84 | 'library_name' => $this->library_name, 85 | 'library_version' => $this->library_version, 86 | 'num' => 1 87 | )); 88 | } else { 89 | // Update num+1. 90 | $DB->execute( 91 | "UPDATE {hvp_counters} 92 | SET num = num + 1 93 | WHERE id = ?", 94 | array($id) 95 | ); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /classes/event/attempt_submitted.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Attempt submitted event 19 | * 20 | * @package mod_hvp 21 | * @copyright 2020 Joubel AS 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_hvp\event; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | /** 30 | * The mod_hvp instance list viewed event class. 31 | * 32 | * @package mod_hvp 33 | * @copyright @copyright 2016 Joubel AS 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class attempt_submitted extends \core\event\base 37 | { 38 | 39 | /** 40 | * @inheritDoc 41 | */ 42 | protected function init() { 43 | $this->data['crud'] = 'u'; 44 | $this->data['edulevel'] = self::LEVEL_PARTICIPATING; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /classes/event/course_module_instance_list_viewed.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_hvp instance list viewed event. 19 | * 20 | * @package mod_hvp 21 | * @copyright @copyright 2016 Joubel AS 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_hvp\event; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | /** 30 | * The mod_hvp instance list viewed event class. 31 | * 32 | * @package mod_hvp 33 | * @copyright @copyright 2016 Joubel AS 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed 37 | { 38 | // No code required here as the parent class handles it all. 39 | } 40 | -------------------------------------------------------------------------------- /classes/event/course_module_viewed.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_hvp course module viewed event. 19 | * 20 | * @package mod_hvp 21 | * @copyright @copyright 2016 Joubel AS 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_hvp\event; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | /** 30 | * The mod_hvp course module viewed event class. 31 | * 32 | * @package mod_hvp 33 | * @copyright @copyright 2016 Joubel AS 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class course_module_viewed extends \core\event\course_module_viewed 37 | { 38 | 39 | /** 40 | * Set basic properties for the event. 41 | */ 42 | protected function init() { 43 | $this->data['objecttable'] = 'hvp'; 44 | $this->data['crud'] = 'r'; 45 | $this->data['edulevel'] = self::LEVEL_PARTICIPATING; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /classes/mobile_auth.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Helpers for authenticating mobile users through tokens 19 | * 20 | * @package mod_hvp 21 | * @copyright 2019 Joubel AS 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_hvp; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | class mobile_auth { 30 | 31 | const VALID_TIME = 60; 32 | 33 | /** 34 | * Generate embed auth token 35 | * 36 | * @param string $secret Secret phrase added to the hash 37 | * @param int $validfor Time factor that determines how long the token is valid 38 | * 39 | * @return array Login token and secret 40 | * @throws \Exception 41 | */ 42 | public static function create_embed_auth_token($secret = null, $validfor = null) { 43 | if (!$validfor) { 44 | $validfor = self::get_time_factor(); 45 | } 46 | 47 | if (empty($secret)) { 48 | if (function_exists('random_bytes')) { 49 | $secret = base64_encode(random_bytes(15)); 50 | } else if (function_exists('openssl_random_pseudo_bytes')) { 51 | $secret = base64_encode(openssl_random_pseudo_bytes(15)); 52 | } else { 53 | $secret = uniqid('', true); 54 | } 55 | } 56 | 57 | return [ 58 | hash('md5', 'embed_auth' . $validfor . $secret), 59 | $secret 60 | ]; 61 | } 62 | 63 | /** 64 | * Validate embed auth token 65 | * 66 | * @param string $token 67 | * @param string $secret 68 | * 69 | * @return bool True if valid token was supplied 70 | * @throws \Exception 71 | */ 72 | public static function validate_embed_auth_token($token, $secret) { 73 | $timefactor = self::get_time_factor(); 74 | // Splitting into two halves and allowing both allows for fractions roundup in the time factor. 75 | list($generatedtoken) = self::create_embed_auth_token($secret, $timefactor); 76 | list($generatedtoken2) = self::create_embed_auth_token($secret, $timefactor - 1); 77 | return $token === $generatedtoken || $token === $generatedtoken2; 78 | } 79 | 80 | /** 81 | * Check if provided user_id and token are valid for authenticating the user 82 | * 83 | * @param string $userid 84 | * @param string $token 85 | * 86 | * @return bool True if token and user_id is valid 87 | * @throws \dml_exception 88 | */ 89 | public static function has_valid_token($userid, $secret) { 90 | global $DB; 91 | 92 | if (!$userid || !$secret) { 93 | return false; 94 | } 95 | 96 | $auth = $DB->get_record('hvp_auth', array( 97 | 'user_id' => $userid, 98 | )); 99 | if (!$auth) { 100 | return false; 101 | } 102 | 103 | $isvalid = self::validate_embed_auth_token($auth->secret, $secret); 104 | 105 | // Cleanup user's token when used. 106 | if ($isvalid) { 107 | $DB->delete_records('hvp_auth', array( 108 | 'user_id' => $userid 109 | )); 110 | } 111 | 112 | return $isvalid; 113 | } 114 | 115 | /** 116 | * Get time factor for how long the token is valid 117 | * 118 | * @return float 119 | */ 120 | public static function get_time_factor() { 121 | return ceil(time() / self::VALID_TIME); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /classes/output/mobile.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace mod_hvp\output; 18 | 19 | defined('MOODLE_INTERNAL') || die(); 20 | 21 | use context_module; 22 | use mod_hvp; 23 | 24 | class mobile { 25 | 26 | public static function mobile_course_view($args) { 27 | global $DB, $CFG, $OUTPUT, $USER; 28 | 29 | $cmid = $args['cmid']; 30 | if (!$CFG->allowframembedding) { 31 | $context = \context_system::instance(); 32 | if (has_capability('moodle/site:config', $context)) { 33 | $template = 'mod_hvp/iframe_embedding_disabled'; 34 | } else { 35 | $template = 'mod_hvp/contact_site_administrator'; 36 | } 37 | return array( 38 | 'templates' => array( 39 | array( 40 | 'id' => 'noiframeembedding', 41 | 'html' => $OUTPUT->render_from_template($template, []) 42 | ) 43 | ) 44 | ); 45 | } 46 | 47 | // Verify course context. 48 | $cm = get_coursemodule_from_id('hvp', $cmid); 49 | if (!$cm) { 50 | print_error('invalidcoursemodule'); 51 | } 52 | $course = $DB->get_record('course', array('id' => $cm->course)); 53 | if (!$course) { 54 | print_error('coursemisconf'); 55 | } 56 | require_course_login($course, false, $cm, true, true); 57 | $context = context_module::instance($cm->id); 58 | require_capability('mod/hvp:view', $context); 59 | 60 | list($token, $secret) = mod_hvp\mobile_auth::create_embed_auth_token(); 61 | 62 | // Store secret in database. 63 | $auth = $DB->get_record('hvp_auth', array( 64 | 'user_id' => $USER->id, 65 | )); 66 | $currenttimestamp = time(); 67 | if ($auth) { 68 | $DB->update_record('hvp_auth', array( 69 | 'id' => $auth->id, 70 | 'secret' => $token, 71 | 'created_at' => $currenttimestamp, 72 | )); 73 | } else { 74 | $DB->insert_record('hvp_auth', array( 75 | 'user_id' => $USER->id, 76 | 'secret' => $token, 77 | 'created_at' => $currenttimestamp 78 | )); 79 | } 80 | 81 | $data = [ 82 | 'cmid' => $cmid, 83 | 'wwwroot' => $CFG->wwwroot, 84 | 'user_id' => $USER->id, 85 | 'secret' => urlencode($secret) 86 | ]; 87 | 88 | return array( 89 | 'templates' => array( 90 | array( 91 | 'id' => 'main', 92 | 'html' => $OUTPUT->render_from_template('mod_hvp/mobile_view_page', $data), 93 | ), 94 | ), 95 | 'javascript' => file_get_contents($CFG->dirroot . '/mod/hvp/library/js/h5p-resizer.js'), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /classes/task/look_for_updates.php: -------------------------------------------------------------------------------- 1 | . 16 | /** 17 | * Defines the task which looks for H5P updates. 18 | * 19 | * @package mod_hvp 20 | * @copyright 2016 Joubel AS 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | namespace mod_hvp\task; 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | /** 29 | * The mod_hvp look for updates task class 30 | * 31 | * @package mod_hvp 32 | * @copyright 2016 Joubel AS 33 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 | */ 35 | class look_for_updates extends \core\task\scheduled_task { 36 | public function get_name() { 37 | return get_string('lookforupdates', 'mod_hvp'); 38 | } 39 | 40 | public function execute() { 41 | // Check to make sure external communications hasn't been disabled. 42 | if (get_config('mod_hvp', 'hub_is_enabled') || get_config('mod_hvp', 'send_usage_statistics')) { 43 | $core = \mod_hvp\framework::instance(); 44 | $core->fetchLibrariesMetadata(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /classes/task/remove_old_auth_tokens.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines the task which removes old mobile auth tokens from the hvp_auth table. 19 | * 20 | * @package mod_hvp 21 | * @copyright 2019 Joubel AS 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_hvp\task; 26 | 27 | use mod_hvp\mobile_auth; 28 | 29 | defined('MOODLE_INTERNAL') || die(); 30 | 31 | /** 32 | * The mod_hvp remove old auth tokens class 33 | * 34 | * @package mod_hvp 35 | * @copyright 2019 Joubel AS 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | */ 38 | class remove_old_auth_tokens extends \core\task\scheduled_task { 39 | public function get_name() { 40 | return get_string('removeoldmobileauthentries', 'mod_hvp'); 41 | } 42 | 43 | public function execute() { 44 | global $DB; 45 | 46 | require_once(__DIR__ . '/../../autoloader.php'); 47 | $deletethreshold = time() - mobile_auth::VALID_TIME; 48 | $DB->delete_records_select('hvp_auth', 'created_at < :threshold', array( 49 | 'threshold' => $deletethreshold, 50 | )); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /classes/task/remove_old_log_entries.php: -------------------------------------------------------------------------------- 1 | . 16 | /** 17 | * Defines the task which looks for H5P updates. 18 | * 19 | * @package mod_hvp 20 | * @copyright 2016 Joubel AS 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | namespace mod_hvp\task; 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | /** 29 | * The mod_hvp look for updates task class 30 | * 31 | * @package mod_hvp 32 | * @copyright 2016 Joubel AS 33 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 | */ 35 | class remove_old_log_entries extends \core\task\scheduled_task { 36 | public function get_name() { 37 | return get_string('removeoldlogentries', 'mod_hvp'); 38 | } 39 | 40 | public function execute() { 41 | global $DB; 42 | 43 | require_once(__DIR__ . '/../../autoloader.php'); 44 | // @codingStandardsIgnoreLine 45 | $olderthan = (time() - \H5PEventBase::$log_time); 46 | $DB->execute("DELETE FROM {hvp_events} WHERE created_at < {$olderthan}"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /classes/task/remove_tmpfiles.php: -------------------------------------------------------------------------------- 1 | . 16 | /** 17 | * Defines the task which removes old tmp files 18 | * 19 | * @package mod_hvp 20 | * @copyright 2016 Joubel AS 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | namespace mod_hvp\task; 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | /** 29 | * The mod_hvp look for updates task class 30 | * 31 | * @package mod_hvp 32 | * @copyright 2016 Joubel AS 33 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 | */ 35 | class remove_tmpfiles extends \core\task\scheduled_task { 36 | public function get_name() { 37 | return get_string('removetmpfiles', 'mod_hvp'); 38 | } 39 | 40 | public function execute() { 41 | global $DB; 42 | $tmpfiles = $DB->get_records_sql( 43 | "SELECT f.id 44 | FROM {hvp_tmpfiles} tf 45 | JOIN {files} f ON f.id = tf.id 46 | WHERE f.timecreated < ?", 47 | array(time() - 86400) 48 | ); 49 | if (empty($tmpfiles)) { 50 | return; // Nothing to clean up. 51 | } 52 | 53 | $fs = get_file_storage(); 54 | foreach ($tmpfiles as $tmpfile) { 55 | // Delete file. 56 | $file = $fs->get_file_by_id($tmpfile->id); 57 | $file->delete(); 58 | 59 | // Remove tmpfile entry. 60 | $DB->delete_records('hvp_tmpfiles', array('id' => $tmpfile->id)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /classes/upload_libraries_form.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * \mod_hvp\upload_libraries_form class 19 | * 20 | * @package mod_hvp 21 | * @copyright 2016 Joubel AS 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_hvp; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | // Load moodleform class. 30 | require_once("$CFG->libdir/formslib.php"); 31 | 32 | /** 33 | * Form to upload new H5P libraries and upgrade existing once 34 | * 35 | * @package mod_hvp 36 | * @copyright 2016 Joubel AS 37 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 | */ 39 | class upload_libraries_form extends \moodleform { 40 | 41 | /** 42 | * Define form elements 43 | */ 44 | public function definition() { 45 | global $CFG, $OUTPUT; 46 | 47 | // Get form. 48 | $mform = $this->_form; 49 | 50 | // Add File Picker. 51 | $mform->addElement('filepicker', 'h5pfile', get_string('h5pfile', 'hvp'), null, 52 | array('maxbytes' => $CFG->maxbytes, 'accepted_types' => '*.h5p')); 53 | 54 | // Add options. 55 | $mform->addElement('checkbox', 56 | 'onlyupdate', 57 | get_string('options', 'hvp'), 58 | get_string('onlyupdate', 'hvp'), 59 | array('group' => 1) 60 | ); 61 | $mform->setType('onlyupdate', PARAM_BOOL); 62 | $mform->setDefault('onlyupdate', false); 63 | 64 | $mform->addElement('checkbox', 65 | 'disablefileextensioncheck', 66 | '', 67 | get_string('disablefileextensioncheck', 'hvp'), 68 | array('group' => 1) 69 | ); 70 | $mform->setType('disablefileextensioncheck', PARAM_BOOL); 71 | $mform->setDefault('disablefileextensioncheck', false); 72 | 73 | $notification = $OUTPUT->notification( 74 | get_string('disablefileextensioncheckwarning', 'hvp'), 75 | 'notifymessage' 76 | ); 77 | $mform->addElement('static', '', '', $notification); 78 | 79 | // Upload button. 80 | $this->add_action_buttons(false, get_string('upload', 'hvp')); 81 | } 82 | 83 | /** 84 | * Preprocess incoming data 85 | * 86 | * @param array $defaultvalues default values for form 87 | */ 88 | public function data_preprocessing(&$defaultvalues) { 89 | // Aaah.. we meet again h5pfile!. 90 | $draftitemid = file_get_submitted_draft_itemid('h5pfile'); 91 | file_prepare_draft_area($draftitemid, $this->context->id, 'mod_hvp', 'package', 0); 92 | $defaultvalues['h5pfile'] = $draftitemid; 93 | } 94 | 95 | /** 96 | * Validate incoming data 97 | * 98 | * @param array $data array of ("fieldname"=>value) of submitted data 99 | * @param array $files array of uploaded files "element_name"=>tmp_file_path 100 | * @return array of "element_name"=>"error_description" if there are errors, 101 | * or an empty array if everything is OK (true allowed for backwards compatibility too). 102 | */ 103 | public function validation($data, $files) { 104 | global $CFG; 105 | $errors = array(); 106 | 107 | // Check for file. 108 | if (empty($data['h5pfile'])) { 109 | $errors['h5pfile'] = get_string('required'); 110 | return $errors; 111 | } 112 | 113 | $files = $this->get_draft_files('h5pfile'); 114 | if (count($files) < 1) { 115 | $errors['h5pfile'] = get_string('required'); 116 | return $errors; 117 | } 118 | 119 | // Add file so that core framework can find it. 120 | $file = reset($files); 121 | $interface = \mod_hvp\framework::instance('interface'); 122 | 123 | $path = $CFG->tempdir . uniqid('/hvp-'); 124 | $interface->getUploadedH5pFolderPath($path); 125 | $path .= '.h5p'; 126 | $interface->getUploadedH5pPath($path); 127 | $file->copy_content_to($path); 128 | 129 | // Validate package. 130 | $h5pvalidator = \mod_hvp\framework::instance('validator'); 131 | if (!$h5pvalidator->isValidPackage(true, isset($data['onlyupdate']))) { 132 | // Errors while validating the package. 133 | $errors = array_map(function ($message) { 134 | return $message->message; 135 | }, framework::messages('error')); 136 | 137 | $messages = array_merge(framework::messages('info'), $errors); 138 | $errors['h5pfile'] = implode('
', $messages); 139 | } 140 | return $errors; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /classes/user_grades.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_hvp user grades 19 | * 20 | * @package mod_hvp 21 | * @copyright 2016 Joubel AS 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | namespace mod_hvp; 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | require(__DIR__ . '/../lib.php'); 29 | 30 | /** 31 | * Handles grade storage for users 32 | * @package mod_hvp 33 | */ 34 | class user_grades { 35 | 36 | public static function handle_ajax() { 37 | global $DB, $USER, $CFG; 38 | 39 | if (!\H5PCore::validToken('result', required_param('token', PARAM_RAW))) { 40 | \H5PCore::ajaxError(get_string('invalidtoken', 'hvp')); 41 | return; 42 | } 43 | 44 | $cm = get_coursemodule_from_id('hvp', required_param('contextId', PARAM_INT)); 45 | if (!$cm) { 46 | \H5PCore::ajaxError('No such content'); 47 | http_response_code(404); 48 | return; 49 | } 50 | 51 | // Content parameters. 52 | $score = required_param('score', PARAM_INT); 53 | $maxscore = required_param('maxScore', PARAM_INT); 54 | 55 | // Check permission. 56 | $context = \context_module::instance($cm->id); 57 | if (!has_capability('mod/hvp:saveresults', $context)) { 58 | \H5PCore::ajaxError(get_string('nopermissiontosaveresult', 'hvp')); 59 | http_response_code(403); 60 | return; 61 | } 62 | 63 | // Get hvp data from content. 64 | $hvp = $DB->get_record('hvp', array('id' => $cm->instance)); 65 | if (!$hvp) { 66 | \H5PCore::ajaxError('No such content'); 67 | http_response_code(404); 68 | return; 69 | } 70 | 71 | // Create grade object and set grades. 72 | $grade = (object) array( 73 | 'userid' => $USER->id 74 | ); 75 | 76 | // Set grade using Gradebook API. 77 | $hvp->cmidnumber = $cm->idnumber; 78 | $hvp->name = $cm->name; 79 | $hvp->rawgrade = $score; 80 | $hvp->rawgrademax = $maxscore; 81 | hvp_grade_item_update($hvp, $grade); 82 | 83 | // Get content info for log. 84 | $content = $DB->get_record_sql( 85 | "SELECT c.name AS title, l.machine_name AS name, l.major_version, l.minor_version 86 | FROM {hvp} c 87 | JOIN {hvp_libraries} l ON l.id = c.main_library_id 88 | WHERE c.id = ?", 89 | array($hvp->id) 90 | ); 91 | 92 | require_once($CFG->libdir.'/completionlib.php'); 93 | 94 | // Load Course. 95 | $course = $DB->get_record('course', array('id' => $cm->course)); 96 | $completion = new \completion_info( $course ); 97 | 98 | if ( $completion->is_enabled( $cm) ) { 99 | $completion->update_state($cm, COMPLETION_COMPLETE); 100 | } 101 | 102 | // Log results set event. 103 | new \mod_hvp\event( 104 | 'results', 'set', 105 | $hvp->id, $content->title, 106 | $content->name, $content->major_version . '.' . $content->minor_version 107 | ); 108 | 109 | // Trigger Moodle event for async notification messages. 110 | $event = \mod_hvp\event\attempt_submitted::create([ 111 | 'context' => $context, 112 | ]); 113 | $event->trigger(); 114 | 115 | \H5PCore::ajaxSuccess(); 116 | } 117 | 118 | /** 119 | * Since the subcontent types do not have their own row in the table, 120 | * we use the hvp_results_table as a 'staging area' to set and get 121 | * dynamically graded scores. 122 | */ 123 | public static function handle_dynamic_grading() { 124 | global $DB; 125 | 126 | if (!\H5PCore::validToken('result', required_param('token', PARAM_RAW))) { 127 | \H5PCore::ajaxError(get_string('invalidtoken', 'hvp')); 128 | return; 129 | } 130 | 131 | $cm = get_coursemodule_from_id('hvp', required_param('contextId', PARAM_INT)); 132 | if (!$cm) { 133 | \H5PCore::ajaxError('No such content'); 134 | http_response_code(404); 135 | return; 136 | } 137 | 138 | // Content parameters. 139 | $subcontentid = required_param('subcontent_id', PARAM_INT); 140 | $score = required_param('score', PARAM_INT); 141 | 142 | // Update the answer's score. 143 | $data = (object) [ 144 | 'id' => $subcontentid, 145 | 'raw_score' => $score 146 | ]; 147 | $DB->update_record('hvp_xapi_results', $data, false); 148 | 149 | // Load freshly updated record. 150 | $answer = $DB->get_record('hvp_xapi_results', array('id' => $subcontentid)); 151 | 152 | // Get the sum of all the OEQ scores with the same parent. 153 | $totalgradablesscore = intval($DB->get_field_sql( 154 | "SELECT SUM(raw_score) 155 | FROM {hvp_xapi_results} 156 | WHERE parent_id = ? 157 | AND additionals = ?", array($answer->parent_id, 158 | '{"extensions":{"https:\/\/h5p.org\/x-api\/h5p-machine-name":"H5P.FreeTextQuestion"}}') 159 | )); 160 | 161 | // Get the original raw score from the main content type. 162 | $baseanswer = $DB->get_record('hvp_xapi_results', array( 163 | 'id' => $answer->parent_id 164 | )); 165 | 166 | // Get hvp data from content. 167 | $hvp = $DB->get_record('hvp', array('id' => $cm->instance)); 168 | if (!$hvp) { 169 | \H5PCore::ajaxError('No such content'); 170 | http_response_code(404); 171 | return; 172 | } 173 | 174 | // Set grade using Gradebook API. 175 | $hvp->rawgrade = $baseanswer->raw_score + $totalgradablesscore; 176 | $hvp->rawgrademax = $baseanswer->max_score; 177 | hvp_grade_item_update($hvp, (object) array( 178 | 'userid' => $answer->user_id 179 | )); 180 | 181 | // Get the num of ungraded OEQ answers. 182 | $numungraded = intval($DB->get_field_sql( 183 | "SELECT COUNT(*) 184 | FROM {hvp_xapi_results} 185 | WHERE parent_id = ? 186 | AND raw_score IS NULL 187 | AND additionals = ?", array($answer->parent_id, 188 | '{"extensions":{"https:\/\/h5p.org\/x-api\/h5p-machine-name":"H5P.FreeTextQuestion"}}') 189 | )); 190 | 191 | $response = [ 192 | 'score' => $answer->raw_score, 193 | 'maxScore' => $answer->max_score, 194 | 'totalUngraded' => $numungraded, 195 | ]; 196 | \H5PCore::ajaxSuccess($response); 197 | } 198 | 199 | /** 200 | * Fetch score/maxScore for ungraded item + number of ungraded items. 201 | */ 202 | public static function return_subcontent_grade() { 203 | global $DB; 204 | 205 | // Content parameters. 206 | $subcontentid = required_param('subcontent_id', PARAM_INT); 207 | $answer = $DB->get_record('hvp_xapi_results', array('id' => $subcontentid)); 208 | 209 | // Get the num of ungraded OEQ answers. 210 | $numungraded = intval($DB->get_field_sql( 211 | "SELECT COUNT(*) 212 | FROM {hvp_xapi_results} 213 | WHERE parent_id = ? 214 | AND raw_score IS NULL 215 | AND additionals = ?", array($answer->parent_id, 216 | '{"extensions":{"https:\/\/h5p.org\/x-api\/h5p-machine-name":"H5P.FreeTextQuestion"}}') 217 | )); 218 | 219 | $response = [ 220 | 'score' => $answer->raw_score, 221 | 'maxScore' => $answer->max_score, 222 | 'totalUngraded' => $numungraded, 223 | ]; 224 | \H5PCore::ajaxSuccess($response); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /classes/xapi_result.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_hvp content user data. 19 | * 20 | * @package mod_hvp 21 | * @since Moodle 2.7 22 | * @copyright 2017 Joubel AS 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace mod_hvp; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | /** 31 | * Class xapi_result handles xapi results and corresponding db operations. 32 | * 33 | * @package mod_hvp 34 | */ 35 | class xapi_result { 36 | 37 | /** 38 | * Handle xapi results endpoint 39 | */ 40 | public static function handle_ajax() { 41 | // Validate token. 42 | if (!self::validate_token()) { 43 | $core = framework::instance(); 44 | \H5PCore::ajaxError($core->h5pF->t('Invalid security token.'), 45 | 'INVALID_TOKEN'); 46 | return; 47 | } 48 | 49 | $cm = get_coursemodule_from_id('hvp', required_param('contextId', PARAM_INT)); 50 | if (!$cm) { 51 | \H5PCore::ajaxError('No such content'); 52 | http_response_code(404); 53 | return; 54 | } 55 | 56 | $xapiresult = required_param('xAPIResult', PARAM_RAW); 57 | 58 | // Validate. 59 | $context = \context_module::instance($cm->id); 60 | if (!has_capability('mod/hvp:saveresults', $context)) { 61 | \H5PCore::ajaxError(get_string('nopermissiontosaveresult', 'hvp')); 62 | return; 63 | } 64 | 65 | $xapijson = json_decode($xapiresult); 66 | if (!$xapijson) { 67 | \H5PCore::ajaxError('Invalid json in xAPI data.'); 68 | return; 69 | } 70 | 71 | if (!self::validate_xapi_data($xapijson)) { 72 | \H5PCore::ajaxError('Invalid xAPI data.'); 73 | return; 74 | } 75 | 76 | // Delete any old results. 77 | self::remove_xapi_data($cm->instance); 78 | 79 | // Store results. 80 | self::store_xapi_data($cm->instance, $xapijson); 81 | 82 | // Successfully inserted xAPI result. 83 | \H5PCore::ajaxSuccess(); 84 | } 85 | 86 | /** 87 | * Validate xAPI results token 88 | * 89 | * @return bool True if token was valid 90 | */ 91 | private static function validate_token() { 92 | $token = required_param('token', PARAM_ALPHANUM); 93 | return \H5PCore::validToken('xapiresult', $token); 94 | 95 | } 96 | 97 | /** 98 | * Validate xAPI data 99 | * 100 | * @param object $xapidata xAPI data 101 | * 102 | * @return bool True if valid data 103 | */ 104 | private static function validate_xapi_data($xapidata) { 105 | $xapidata = new \H5PReportXAPIData($xapidata); 106 | return $xapidata->validateData(); 107 | } 108 | 109 | /** 110 | * Store xAPI result(s) 111 | * 112 | * @param int $contentid Content id 113 | * @param object $xapidata xAPI data 114 | * @param int $parentid Parent id 115 | */ 116 | private static function store_xapi_data($contentid, $xapidata, $parentid = null) { 117 | global $DB, $USER; 118 | 119 | $xapidata = new \H5PReportXAPIData($xapidata, $parentid); 120 | $insertedid = $DB->insert_record('hvp_xapi_results', (object) array( 121 | 'content_id' => $contentid, 122 | 'user_id' => $USER->id, 123 | 'parent_id' => $xapidata->getParentID(), 124 | 'interaction_type' => $xapidata->getInteractionType(), 125 | 'description' => $xapidata->getDescription(), 126 | 'correct_responses_pattern' => $xapidata->getCorrectResponsesPattern(), 127 | 'response' => $xapidata->getResponse(), 128 | 'additionals' => $xapidata->getAdditionals(), 129 | 'raw_score' => $xapidata->getScoreRaw(), 130 | 'max_score' => $xapidata->getScoreMax(), 131 | )); 132 | 133 | // Save sub content statements data. 134 | if ($xapidata->isCompound()) { 135 | foreach ($xapidata->getChildren($contentid) as $child) { 136 | self::store_xapi_data($contentid, $child, $insertedid); 137 | } 138 | } 139 | } 140 | 141 | /** 142 | * Remove xAPI result(s) 143 | * 144 | * @param int $contentid Content id 145 | */ 146 | private static function remove_xapi_data($contentid) { 147 | global $DB, $USER; 148 | 149 | $DB->delete_records('hvp_xapi_results', array( 150 | 'content_id' => $contentid, 151 | 'user_id' => $USER->id 152 | )); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /content_hub_registration.php: -------------------------------------------------------------------------------- 1 | . 16 | /** 17 | * Responsible for displaying the content upgrade page 18 | * 19 | * @package mod_hvp 20 | * @copyright 2020 Joubel AS 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | use mod_hvp\content_hub_service; 24 | 25 | require_once("../../config.php"); 26 | require_once($CFG->libdir.'/adminlib.php'); 27 | require_once("locallib.php"); 28 | 29 | global $PAGE, $SITE, $OUTPUT, $CFG; 30 | 31 | // No guest autologin. 32 | require_login(0, false); 33 | 34 | $context = \context_system::instance(); 35 | require_capability('mod/hvp:contenthubregistration', $context); 36 | 37 | $pageurl = new moodle_url('/mod/hvp/content_hub_registration.php'); 38 | $PAGE->set_context(context_system::instance()); 39 | $PAGE->set_url($pageurl); 40 | $PAGE->set_title("{$SITE->shortname}: " . get_string('upgrade', 'hvp')); 41 | $PAGE->set_heading(get_string('contenthub:settings:heading', 'mod_hvp')); 42 | 43 | $settings = content_hub_service::get_registration_ui_settings(); 44 | $PAGE->requires->data_for_js('H5PSettings', $settings, true); 45 | $PAGE->requires->js(new moodle_url('library/js/h5p-hub-registration.js'), true); 46 | $PAGE->requires->css(new moodle_url('library/styles/h5p.css')); 47 | $PAGE->requires->css(new moodle_url('library/styles/h5p-hub-registration.css')); 48 | 49 | echo $OUTPUT->header(); 50 | 51 | echo $OUTPUT->render_from_template('mod_hvp/content_hub_registration', []); 52 | $PAGE->requires->js_call_amd('mod_hvp/contenthubregistration', 'init'); 53 | 54 | echo $OUTPUT->footer(); -------------------------------------------------------------------------------- /dataviews.js: -------------------------------------------------------------------------------- 1 | /* global H5PDataView */ 2 | (function($) { 3 | 4 | /** 5 | * Creates a new dataview. 6 | * 7 | * @private 8 | * @param {object} dataView Structure 9 | * @param {string} dataView.source AJAX URL for data view 10 | * @param {object[]} dataView.headers Header text and props 11 | * @param {boolean[]} dataView.filters Which fields to allow filtering for 12 | * @param {object} dataView.order Default order by and direction 13 | * @param {object} dataView.l10n Translations 14 | * @param {Element} wrapper Where in the DOM should the dataview be appended 15 | * @param {function} loaded Callback for when the dataview is ready 16 | */ 17 | var createDataView = function(dataView, wrapper, loaded) { 18 | new H5PDataView( 19 | wrapper, 20 | dataView.source, 21 | dataView.headers, 22 | dataView.l10n, 23 | undefined, 24 | dataView.filters, 25 | loaded, 26 | dataView.order 27 | ); 28 | }; 29 | 30 | // Create data views when page is done loading. 31 | $(document).ready(function() { 32 | for (var id in H5PIntegration.dataViews) { 33 | if (!H5PIntegration.dataViews.hasOwnProperty(id)) { 34 | continue; 35 | } 36 | 37 | var wrapper = $('#' + id).get(0); 38 | if (wrapper !== undefined) { 39 | createDataView(H5PIntegration.dataViews[id], wrapper); 40 | } 41 | } 42 | }); 43 | })(H5P.jQuery); 44 | -------------------------------------------------------------------------------- /db/access.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Capability definitions for the hvp module. 19 | * 20 | * Available archetypes: 21 | * manager 22 | * coursecreator 23 | * editingteacher 24 | * teacher 25 | * student 26 | * guest 27 | * user 28 | * frontpage 29 | * 30 | * @package mod_hvp 31 | * @copyright 2016 Joubel AS 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | 35 | defined('MOODLE_INTERNAL') || die(); 36 | 37 | $capabilities = array( 38 | 39 | 'mod/hvp:view' => array( 40 | 'captype' => 'read', 41 | 'contextlevel' => CONTEXT_MODULE, 42 | 'archetypes' => array( 43 | 'manager' => CAP_ALLOW, 44 | 'editingteacher' => CAP_ALLOW, 45 | 'teacher' => CAP_ALLOW, 46 | 'student' => CAP_ALLOW, 47 | 'guest' => CAP_ALLOW 48 | ) 49 | ), 50 | 51 | 'mod/hvp:addinstance' => array( 52 | 'riskbitmask' => RISK_SPAM | RISK_XSS, 53 | 'captype' => 'write', 54 | 'contextlevel' => CONTEXT_COURSE, 55 | 'archetypes' => array( 56 | 'editingteacher' => CAP_ALLOW, 57 | 'manager' => CAP_ALLOW 58 | ), 59 | 'clonepermissionsfrom' => 'moodle/course:manageactivities' 60 | ), 61 | 62 | 'mod/hvp:manage' => array( 63 | 'riskbitmask' => RISK_SPAM | RISK_XSS, 64 | 'captype' => 'write', 65 | 'contextlevel' => CONTEXT_MODULE, 66 | 'archetypes' => array( 67 | 'editingteacher' => CAP_ALLOW, 68 | 'manager' => CAP_ALLOW 69 | ), 70 | 'clonepermissionsfrom' => 'mod/hvp:addinstance' 71 | ), 72 | 73 | 'mod/hvp:share' => array( 74 | 'riskbitmask' => RISK_SPAM, 75 | 'captype' => 'write', 76 | 'contextlevel' => CONTEXT_MODULE, 77 | 'archetypes' => array( 78 | 'editingteacher' => CAP_ALLOW, 79 | 'manager' => CAP_ALLOW 80 | ), 81 | 'clonepermissionsfrom' => 'mod/hvp:addinstance' 82 | ), 83 | 84 | 'mod/hvp:getexport' => array( 85 | 'captype' => 'read', 86 | 'contextlevel' => CONTEXT_MODULE, 87 | 'archetypes' => array( 88 | 'manager' => CAP_ALLOW, 89 | 'editingteacher' => CAP_ALLOW, 90 | 'teacher' => CAP_ALLOW 91 | ) 92 | ), 93 | 94 | 'mod/hvp:getembedcode' => array( 95 | 'captype' => 'read', 96 | 'contextlevel' => CONTEXT_MODULE, 97 | 'archetypes' => array( 98 | 'manager' => CAP_ALLOW, 99 | 'editingteacher' => CAP_ALLOW, 100 | 'teacher' => CAP_ALLOW 101 | ) 102 | ), 103 | 104 | 'mod/hvp:saveresults' => array( 105 | 'riskbitmask' => RISK_SPAM, 106 | 'captype' => 'write', 107 | 'contextlevel' => CONTEXT_MODULE, 108 | 'archetypes' => array( 109 | 'student' => CAP_ALLOW 110 | ) 111 | ), 112 | 113 | 'mod/hvp:savecontentuserdata' => array( 114 | 'captype' => 'write', 115 | 'contextlevel' => CONTEXT_MODULE, 116 | 'archetypes' => array( 117 | 'manager' => CAP_ALLOW, 118 | 'editingteacher' => CAP_ALLOW, 119 | 'teacher' => CAP_ALLOW, 120 | 'student' => CAP_ALLOW 121 | ) 122 | ), 123 | 124 | 'mod/hvp:viewresults' => array( 125 | 'captype' => 'write', 126 | 'contextlevel' => CONTEXT_MODULE, 127 | 'archetypes' => array( 128 | 'student' => CAP_ALLOW 129 | ) 130 | ), 131 | 132 | 'mod/hvp:viewallresults' => array( 133 | 'captype' => 'read', 134 | 'contextlevel' => CONTEXT_MODULE, 135 | 'archetypes' => array( 136 | 'manager' => CAP_ALLOW, 137 | 'editingteacher' => CAP_ALLOW, 138 | 'teacher' => CAP_ALLOW 139 | ) 140 | ), 141 | 142 | 'mod/hvp:restrictlibraries' => array( 143 | 'captype' => 'write', 144 | 'contextlevel' => CONTEXT_SYSTEM, 145 | 'archetypes' => array( 146 | 'manager' => CAP_ALLOW 147 | ) 148 | ), 149 | 150 | 'mod/hvp:userestrictedlibraries' => array( 151 | 'captype' => 'write', 152 | 'contextlevel' => CONTEXT_COURSE, 153 | 'archetypes' => array( 154 | 'manager' => CAP_ALLOW 155 | ) 156 | ), 157 | 158 | 'mod/hvp:updatelibraries' => array( 159 | 'captype' => 'write', 160 | 'contextlevel' => CONTEXT_SYSTEM, 161 | 'archetypes' => array( 162 | 'manager' => CAP_ALLOW 163 | ) 164 | ), 165 | 166 | 'mod/hvp:getcachedassets' => array( 167 | 'captype' => 'read', 168 | 'contextlevel' => CONTEXT_SYSTEM, 169 | 'archetypes' => array( 170 | 'manager' => CAP_ALLOW, 171 | 'editingteacher' => CAP_ALLOW, 172 | 'teacher' => CAP_ALLOW, 173 | 'student' => CAP_ALLOW, 174 | 'user' => CAP_ALLOW, 175 | 'guest' => CAP_ALLOW 176 | ) 177 | ), 178 | 179 | 'mod/hvp:installrecommendedh5plibraries' => array( 180 | 'captype' => 'write', 181 | 'contextlevel' => CONTEXT_COURSE, 182 | 'archetypes' => array( 183 | 'manager' => CAP_ALLOW 184 | ) 185 | ), 186 | 187 | // Receive a confirmation message of own h5p submission. 188 | 'mod/hvp:emailconfirmsubmission' => array( 189 | 'captype' => 'read', 190 | 'contextlevel' => CONTEXT_MODULE, 191 | 'archetypes' => array() 192 | ), 193 | 194 | // Receive a notification message of other peoples' h5p submissions. 195 | 'mod/hvp:emailnotifysubmission' => array( 196 | 'captype' => 'read', 197 | 'contextlevel' => CONTEXT_MODULE, 198 | 'archetypes' => array() 199 | ), 200 | 201 | 'mod/hvp:contenthubregistration' => [ 202 | 'captype' => 'write', 203 | 'contextlevel' => CONTEXT_SYSTEM, 204 | 'archetypes' => [], // Only admins by default. 205 | ], 206 | 207 | ); 208 | -------------------------------------------------------------------------------- /db/events.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Add event handlers for H5P 19 | * 20 | * @package mod_hvp 21 | * @category event 22 | * @copyright 2020 Joubel AS 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | $observers = array( 30 | 31 | // Handle attempt submitted event, as a way to send confirmation messages asynchronously. 32 | array( 33 | 'eventname' => '\mod_hvp\event\attempt_submitted', 34 | 'includefile' => '/mod/hvp/locallib.php', 35 | 'callback' => 'hvp_attempt_submitted_handler', 36 | 'internal' => false 37 | ), 38 | ); 39 | -------------------------------------------------------------------------------- /db/install.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | defined('MOODLE_INTERNAL') || die(); 18 | 19 | function xmldb_hvp_install() { 20 | 21 | // Try to install all the default content types. 22 | require_once(__DIR__ . '/../autoloader.php'); 23 | 24 | // Fetch info about library updates. 25 | $core = \mod_hvp\framework::instance('core'); 26 | $core->fetchLibrariesMetadata(); 27 | 28 | // Check that plugin is set up correctly. 29 | $core->checkSetupForRequirements(); 30 | 31 | // Print any messages. 32 | echo '

' . get_string('welcomeheader', 'hvp') . '

' . 33 | '

' . 34 | get_string('welcomegettingstarted', 'hvp', array( 35 | 'moodle_tutorial' => 'href="https://h5p.org/moodle" target="_blank"', 36 | 'example_content' => 'href="https://h5p.org/content-types-and-applications" target="_blank"' 37 | )) . 38 | '

' . 39 | '

' . 40 | get_string('welcomecommunity', 'hvp', array( 41 | 'forums' => 'href="https://h5p.org/forum" target="_blank"' 42 | )) . 43 | '

' . 44 | '

' . get_string('welcomecontactus', 'hvp', 45 | 'href="https://h5p.org/contact" target="_blank"') . 46 | '

'; 47 | 48 | // Notify of communication with H5P Hub. 49 | // @codingStandardsIgnoreLine 50 | echo "

H5P fetches content types directly from the H5P Hub. In order to do this the H5P plugin will communicate with the Hub once a day to fetch information about new and updated content types. It will send in anonymous data to the Hub about H5P usage. Read more at the plugin communication page at H5P.org.

"; 51 | 52 | \mod_hvp\framework::printMessages('info', \mod_hvp\framework::messages('info')); 53 | \mod_hvp\framework::printMessages('error', \mod_hvp\framework::messages('error')); 54 | } 55 | -------------------------------------------------------------------------------- /db/messages.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines message providers (types of message sent) for the hvp module. 19 | * 20 | * @package mod_hvp 21 | * @copyright 2020 Joubel AS 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | $messageproviders = array( 28 | // Notify teacher that a student has submitted an attempt. 29 | 'submission' => array( 30 | 'capability' => 'mod/hvp:emailnotifysubmission' 31 | ), 32 | 33 | // Confirm a student's quiz attempt. 34 | 'confirmation' => array( 35 | 'capability' => 'mod/hvp:emailconfirmsubmission', 36 | 'defaults' => array( 37 | 'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_ENABLED, 38 | ), 39 | ), 40 | ); 41 | -------------------------------------------------------------------------------- /db/mobile.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | defined('MOODLE_INTERNAL') || die(); 18 | 19 | $addons = array( 20 | "mod_hvp" => array( // Plugin identifier. 21 | 'handlers' => array( // Different places where the plugin will display content. 22 | 'coursehvp' => array( // Handler unique name (alphanumeric). 23 | 'delegate' => 'CoreCourseModuleDelegate', // Delegate (where to display the link to the plugin). 24 | 'method' => 'mobile_course_view', // Main function in \mod_certificate\output\mobile. 25 | 'displaydata' => array( 26 | 'icon' => $CFG->wwwroot . '/mod/hvp/pix/icon.svg', 27 | 'class' => '', 28 | ), 29 | ) 30 | ) 31 | ) 32 | ); 33 | -------------------------------------------------------------------------------- /db/tasks.php: -------------------------------------------------------------------------------- 1 | . 16 | /** 17 | * Defines the task which looks for H5P updates. 18 | * 19 | * @package mod_hvp 20 | * @copyright 2016 Joubel AS 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | defined('MOODLE_INTERNAL') || die(); 25 | 26 | $tasks = array( 27 | array( 28 | 'classname' => 'mod_hvp\task\look_for_updates', 29 | 'blocking' => 0, 30 | 'minute' => 'R', 31 | 'hour' => 'R', 32 | 'day' => '*', 33 | 'dayofweek' => '*', 34 | 'month' => '*' 35 | ), 36 | array( 37 | 'classname' => 'mod_hvp\task\remove_tmpfiles', 38 | 'blocking' => 0, 39 | 'minute' => 'R', 40 | 'hour' => 'R', 41 | 'day' => '*', 42 | 'dayofweek' => '*', 43 | 'month' => '*' 44 | ), 45 | array( 46 | 'classname' => 'mod_hvp\task\remove_old_log_entries', 47 | 'blocking' => 0, 48 | 'minute' => 'R', 49 | 'hour' => 'R', 50 | 'day' => '*', 51 | 'dayofweek' => '*', 52 | 'month' => '*' 53 | ), 54 | array( 55 | 'classname' => 'mod_hvp\task\remove_old_auth_tokens', 56 | 'blocking' => 0, 57 | 'minute' => 'R', 58 | 'hour' => '*', 59 | 'day' => '*', 60 | 'dayofweek' => '*', 61 | 'month' => '*' 62 | ) 63 | ); 64 | -------------------------------------------------------------------------------- /editor.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | /** 4 | * Get closest row of element 5 | * 6 | * @param {jQuery} $el 7 | * @returns {jQuery} 8 | */ 9 | function getRow($el) { 10 | return $el.closest('.fitem'); 11 | } 12 | 13 | /** 14 | * Initializes editor 15 | */ 16 | function init() { 17 | var $editor = $('.h5p-editor'); 18 | var $fileField = $('input[name="h5pfile"]'); 19 | 20 | if (H5PIntegration.hubIsEnabled) { 21 | // TODO: This can easily break in new themes. Improve robustness of this 22 | // by not including h5paction in form, when it should not be used. 23 | $('input[name="h5paction"]').parents('.fitem').last().hide(); 24 | } 25 | 26 | const mformId = H5PIntegration.editor && H5PIntegration.editor.formId !== null 27 | ? H5PIntegration.editor.formId 28 | : 'mform1'; 29 | 30 | // Cancel validation and submission of form if clicking cancel button 31 | const cancelSubmitCallback = function ($button) { 32 | return $button.is('[name="cancel"]'); 33 | }; 34 | 35 | H5PEditor.init( 36 | $('#' + mformId), 37 | $('input[name="h5paction"]'), 38 | getRow($fileField), 39 | getRow($editor), 40 | $editor, 41 | $('input[name="h5plibrary"]'), 42 | $('input[name="h5pparams"]'), 43 | $('input[name="h5pmaxscore"]'), 44 | $('input[name="name"]'), 45 | cancelSubmitCallback 46 | ); 47 | } 48 | 49 | $(document).ready(init); 50 | })(H5P.jQuery); 51 | -------------------------------------------------------------------------------- /embed.php: -------------------------------------------------------------------------------- 1 | . 16 | /** 17 | * Embed H5P Content 18 | * 19 | * @package mod_hvp 20 | * @copyright 2016 Joubel AS 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | require_once("../../config.php"); 24 | require_once("locallib.php"); 25 | 26 | global $PAGE, $DB, $CFG, $OUTPUT; 27 | 28 | $id = required_param('id', PARAM_INT); 29 | 30 | // Allow login through an authentication token. 31 | $userid = optional_param('user_id', null, PARAM_ALPHANUMEXT); 32 | $secret = optional_param('secret', null, PARAM_RAW); 33 | $disabledownload = false; 34 | $disablefullscreen = false; 35 | if (\mod_hvp\mobile_auth::has_valid_token($userid, $secret)) { 36 | $user = get_complete_user_data('id', $userid); 37 | complete_user_login($user); 38 | $disabledownload = true; 39 | $disablefullscreen = true; 40 | } 41 | 42 | // Verify course context. 43 | $cm = get_coursemodule_from_id('hvp', $id); 44 | if (!$cm) { 45 | print_error('invalidcoursemodule'); 46 | } 47 | $course = $DB->get_record('course', array('id' => $cm->course)); 48 | if (!$course) { 49 | print_error('coursemisconf'); 50 | } 51 | 52 | try { 53 | require_course_login($course, true, $cm, true, true); 54 | } catch (Exception $e) { 55 | $PAGE->set_pagelayout('embedded'); 56 | $root = \mod_hvp\view_assets::getsiteroot(); 57 | $embedfailedsvg = new \moodle_url("{$root}/mod/hvp/library/images/h5p.svg"); 58 | echo '' . 59 | '
' . 62 | '
' . 63 | '
' . 66 | get_string('embedloginfailed', 'hvp') . 67 | '
' . 68 | ''; 69 | return; 70 | } 71 | $context = context_module::instance($cm->id); 72 | require_capability('mod/hvp:view', $context); 73 | 74 | // Set up view assets. 75 | $view = new \mod_hvp\view_assets($cm, $course, [ 76 | 'disabledownload' => $disabledownload, 77 | 'disablefullscreen' => $disablefullscreen 78 | ]); 79 | $content = $view->getcontent(); 80 | $view->validatecontent(); 81 | 82 | // Release session while loading the rest of our assets. 83 | core\session\manager::write_close(); 84 | 85 | // Configure page. 86 | $PAGE->set_url(new \moodle_url('/mod/hvp/embed.php', array('id' => $id))); 87 | $PAGE->set_title(format_string($content['title'])); 88 | $PAGE->set_heading($course->fullname); 89 | 90 | // Disable activity header on Moodle 4.0+ 91 | if ($CFG->branch >= 400) { 92 | $PAGE->activityheader->disable(); 93 | } 94 | 95 | // Embed specific page setup. 96 | $PAGE->add_body_class('h5p-embed'); 97 | $PAGE->set_pagelayout('embedded'); 98 | $root = \mod_hvp\view_assets::getsiteroot(); 99 | $PAGE->requires->js_call_amd('mod_hvp/embed'); 100 | // Add H5P assets to page. 101 | $view->addassetstopage(); 102 | $view->logviewed(); 103 | 104 | // Print page HTML. 105 | echo $OUTPUT->header(); 106 | echo '
'; 107 | 108 | // Print any messages. 109 | \mod_hvp\framework::printMessages('info', \mod_hvp\framework::messages('info')); 110 | \mod_hvp\framework::printMessages('error', \mod_hvp\framework::messages('error')); 111 | 112 | $view->outputview(); 113 | echo $OUTPUT->footer(); 114 | -------------------------------------------------------------------------------- /grade.php: -------------------------------------------------------------------------------- 1 | . 16 | /** 17 | * View all results for H5P Content 18 | * 19 | * @package mod_hvp 20 | * @copyright 2016 Joubel AS 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | require_once(dirname(__FILE__) . '/../../config.php'); 25 | require_once("locallib.php"); 26 | 27 | global $DB, $PAGE, $USER, $COURSE; 28 | 29 | $id = required_param('id', PARAM_INT); 30 | $userid = optional_param('userid', 0, PARAM_INT); 31 | 32 | if (! $cm = get_coursemodule_from_id('hvp', $id)) { 33 | print_error('invalidcoursemodule'); 34 | } 35 | if (! $course = $DB->get_record('course', array('id' => $cm->course))) { 36 | print_error('coursemisconf'); 37 | } 38 | require_course_login($course, false, $cm); 39 | 40 | // Check permission. 41 | $context = \context_module::instance($cm->id); 42 | 43 | // Load H5P Content. 44 | $hvp = $DB->get_record_sql( 45 | "SELECT h.id, 46 | h.name AS title, 47 | hl.machine_name, 48 | hl.major_version, 49 | hl.minor_version 50 | FROM {hvp} h 51 | JOIN {hvp_libraries} hl ON hl.id = h.main_library_id 52 | WHERE h.id = ?", 53 | array($cm->instance)); 54 | 55 | if ($hvp === false) { 56 | print_error('invalidhvp', 'mod_hvp'); 57 | } 58 | 59 | // Redirect to report if a specific user is chosen. 60 | if ($userid) { 61 | redirect(new moodle_url('/mod/hvp/review.php', 62 | array( 63 | 'id' => $hvp->id, 64 | 'course' => $course->id, 65 | 'user' => $userid 66 | )) 67 | ); 68 | } 69 | hvp_require_view_results_permission((int)$USER->id, $context, $cm->id); 70 | 71 | // Log content result view. 72 | new \mod_hvp\event( 73 | 'results', 'content', 74 | $hvp->id, $hvp->title, 75 | $hvp->machine_name, "{$hvp->major_version}.{$hvp->minor_version}" 76 | ); 77 | 78 | // Set page properties. 79 | $pageurl = new moodle_url('/mod/hvp/grade.php', array('id' => $hvp->id)); 80 | $PAGE->set_url($pageurl); 81 | $title = get_string('gradeheading', 'hvp', $hvp->title); 82 | $PAGE->set_title($title); 83 | $PAGE->set_heading($course->fullname); 84 | 85 | // List all results for specific content. 86 | $dataviewid = 'h5p-results'; 87 | 88 | // Add required assets for data views. 89 | $root = \mod_hvp\view_assets::getsiteroot(); 90 | $PAGE->requires->js(new moodle_url($root . '/mod/hvp/library/js/jquery.js'), true); 91 | $PAGE->requires->js(new moodle_url($root . '/mod/hvp/library/js/h5p-utils.js'), true); 92 | $PAGE->requires->js(new moodle_url($root . '/mod/hvp/library/js/h5p-data-view.js'), true); 93 | $PAGE->requires->js(new moodle_url($root . '/mod/hvp/dataviews.js'), true); 94 | $PAGE->requires->css(new moodle_url($root . '/mod/hvp/styles.css')); 95 | 96 | // Add JavaScript settings to data views. 97 | $settings = array( 98 | 'dataViews' => array( 99 | "{$dataviewid}" => array( 100 | 'source' => "{$root}/mod/hvp/ajax.php?action=results&content_id={$hvp->id}", 101 | 'headers' => array( 102 | (object) array( 103 | 'text' => get_string('user', 'hvp'), 104 | 'sortable' => true 105 | ), 106 | (object) array( 107 | 'text' => get_string('score', 'hvp'), 108 | 'sortable' => true 109 | ), 110 | (object) array( 111 | 'text' => get_string('maxscore', 'hvp'), 112 | 'sortable' => true 113 | ), 114 | (object) array( 115 | 'text' => get_string('finished', 'hvp'), 116 | 'sortable' => true 117 | ), 118 | (object) array( 119 | 'text' => get_string('dataviewreportlabel', 'hvp') 120 | ) 121 | ), 122 | 'filters' => array(true), 123 | 'order' => (object) array( 124 | 'by' => 3, 125 | 'dir' => 0 126 | ), 127 | 'l10n' => array( 128 | 'loading' => get_string('loadingdata', 'hvp'), 129 | 'ajaxFailed' => get_string('ajaxfailed', 'hvp'), 130 | 'noData' => get_string('nodata', 'hvp'), 131 | 'currentPage' => get_string('currentpage', 'hvp'), 132 | 'nextPage' => get_string('nextpage', 'hvp'), 133 | 'previousPage' => get_string('previouspage', 'hvp'), 134 | 'search' => get_string('search', 'hvp'), 135 | 'empty' => get_string('empty', 'hvp') 136 | ) 137 | ) 138 | ) 139 | ); 140 | $PAGE->requires->data_for_js('H5PIntegration', $settings, true); 141 | 142 | // Print page HTML. 143 | echo $OUTPUT->header(); 144 | echo '
'; 145 | 146 | // Print H5P Content. 147 | echo "

{$title}

"; 148 | echo '
' . get_string('javascriptloading', 'hvp') . '
'; 149 | 150 | echo $OUTPUT->footer(); 151 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | . 16 | /** 17 | * Form for creating new H5P Content 18 | * 19 | * @package mod_hvp 20 | * @copyright 2016 Joubel AS 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | require_once('../../config.php'); 25 | 26 | // Get Course ID. 27 | $id = optional_param('id', 0, PARAM_INT); 28 | 29 | // Set URL. 30 | $url = new \moodle_url('/mod/hvp/index.php', array('id' => $id)); 31 | $PAGE->set_url($url); 32 | 33 | // Load Course. 34 | $course = $DB->get_record('course', array('id' => $id)); 35 | if (!$course) { 36 | print_error('invalidcourseid'); 37 | } 38 | 39 | // Require login. 40 | require_course_login($course); 41 | $PAGE->set_pagelayout('incourse'); 42 | $coursecontext = context_course::instance($course->id); 43 | 44 | // Trigger instances list viewed event. 45 | $params = array( 46 | 'context' => context_course::instance($course->id) 47 | ); 48 | $event = \mod_hvp\event\course_module_instance_list_viewed::create($params); 49 | $event->add_record_snapshot('course', $course); 50 | $event->trigger(); 51 | 52 | // Set title and heading. 53 | $PAGE->set_title($course->shortname . ': ' . get_string('modulenameplural', 'mod_hvp')); 54 | $PAGE->set_heading($course->fullname); 55 | 56 | echo $OUTPUT->header(); 57 | 58 | // Load H5P list data. 59 | $rawh5ps = $DB->get_records_sql("SELECT cm.id AS coursemodule, 60 | cw.section, 61 | cm.visible, 62 | h.name, 63 | hl.title AS librarytitle 64 | FROM {course_modules} cm, 65 | {course_sections} cw, 66 | {modules} md, 67 | {hvp} h, 68 | {hvp_libraries} hl 69 | WHERE cm.course = ? 70 | AND cm.instance = h.id 71 | AND cm.section = cw.id 72 | AND md.name = 'hvp' 73 | AND md.id = cm.module 74 | AND hl.id = h.main_library_id 75 | ", array($course->id)); 76 | if (!$rawh5ps) { 77 | notice(get_string('noh5ps', 'mod_hvp'), "../../course/view.php?id={$course->id}"); 78 | die; 79 | } 80 | 81 | $modinfo = get_fast_modinfo($course, null); 82 | if (empty($modinfo->instances['hvp'])) { 83 | $h5ps = $rawh5ps; 84 | } else { 85 | // Lets try to order these bad boys. 86 | $h5ps = []; 87 | foreach ($modinfo->instances['hvp'] as $cm) { 88 | if (!$cm->uservisible || !isset($rawh5ps[$cm->id])) { 89 | continue; // Not visible or not found. 90 | } 91 | if (!empty($cm->extra)) { 92 | $rawh5ps[$cm->id]->extra = $cm->extra; 93 | } 94 | $h5ps[] = $rawh5ps[$cm->id]; 95 | } 96 | } 97 | 98 | // Print H5P list. 99 | $table = new html_table(); 100 | $table->attributes['class'] = 'generaltable mod_index'; 101 | 102 | $table->head = array(); 103 | $table->align = array(); 104 | 105 | $usesections = course_format_uses_sections($course->format); 106 | if ($usesections) { 107 | // Section name. 108 | $table->head[] = get_string('sectionname', 'format_'.$course->format); 109 | $table->align[] = 'center'; 110 | } 111 | 112 | // Activity name. 113 | $table->head[] = get_string('name'); 114 | $table->align[] = 'left'; 115 | 116 | // Content type. 117 | $table->head[] = 'Content Type'; 118 | $table->align[] = 'left'; 119 | 120 | // Add data rows. 121 | foreach ($h5ps as $h5p) { 122 | $row = []; 123 | 124 | if ($usesections) { 125 | // Section name. 126 | $row[] = get_section_name($course, $h5p->section); 127 | } 128 | 129 | // Activity name. 130 | $attrs = ($h5p->visible ? '' : ' class="dimmed"'); 131 | $h5p->name = format_string($h5p->name); 132 | $row[] = "coursemodule}\"{$attrs}>{$h5p->name}"; 133 | 134 | // Activity type. 135 | $row[] = $h5p->librarytitle; 136 | 137 | $table->data[] = $row; 138 | } 139 | 140 | echo html_writer::table($table); 141 | 142 | echo $OUTPUT->footer(); 143 | -------------------------------------------------------------------------------- /library_list.php: -------------------------------------------------------------------------------- 1 | . 16 | /** 17 | * Responsible for displaying the library list page 18 | * 19 | * @package mod_hvp 20 | * @copyright 2016 Joubel AS 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | require_once("../../config.php"); 25 | require_once($CFG->libdir.'/adminlib.php'); 26 | require_once("locallib.php"); 27 | 28 | // No guest autologin. 29 | require_login(0, false); 30 | 31 | $pageurl = new moodle_url('/mod/hvp/library_list.php'); 32 | $PAGE->set_url($pageurl); 33 | 34 | // Inform moodle which menu entry currently is active! 35 | admin_externalpage_setup('h5plibraries'); 36 | 37 | $PAGE->set_title("{$SITE->shortname}: " . get_string('libraries', 'hvp')); 38 | 39 | // Create upload libraries form. 40 | $uploadform = new \mod_hvp\upload_libraries_form(); 41 | if ($formdata = $uploadform->get_data()) { 42 | // Handle submitted valid form. 43 | $h5pstorage = \mod_hvp\framework::instance('storage'); 44 | $h5pstorage->savePackage(null, null, true); 45 | } 46 | 47 | $core = \mod_hvp\framework::instance(); 48 | 49 | $hubon = $core->h5pF->getOption('hub_is_enabled', true); 50 | if ($hubon) { 51 | // Create content type cache form. 52 | $ctcacheform = new \mod_hvp\content_type_cache_form(); 53 | 54 | // On form submit. 55 | if ($ctcacheform->get_data()) { 56 | // Update cache and reload page. 57 | $core->updateContentTypeCache(); 58 | redirect($pageurl); 59 | } 60 | } 61 | 62 | $numnotfiltered = $core->h5pF->getNumNotFiltered(); 63 | $libraries = $core->h5pF->loadLibraries(); 64 | 65 | // Add settings for each library. 66 | $settings = array(); 67 | $i = 0; 68 | foreach ($libraries as $versions) { 69 | foreach ($versions as $library) { 70 | $usage = $core->h5pF->getLibraryUsage($library->id, $numnotfiltered ? true : false); 71 | if ($library->runnable) { 72 | $upgrades = $core->getUpgrades($library, $versions); 73 | $upgradeurl = empty($upgrades) ? false : (new moodle_url('/mod/hvp/upgrade_content_page.php', array( 74 | 'library_id' => $library->id 75 | )))->out(false); 76 | 77 | $restricted = (isset($library->restricted) && $library->restricted == 1 ? true : false); 78 | $restrictedurl = (new moodle_url('/mod/hvp/ajax.php', array( 79 | 'action' => 'restrict_library', 80 | 'token' => \H5PCore::createToken('library_' . $library->id), 81 | 'restrict' => ($restricted ? 0 : 1), 82 | 'library_id' => $library->id 83 | )))->out(false); 84 | } else { 85 | $upgradeurl = null; 86 | $restricted = null; 87 | $restrictedurl = null; 88 | } 89 | 90 | $settings['libraryList']['listData'][] = array( 91 | 'title' => $library->title . ' (' . \H5PCore::libraryVersion($library) . ')', 92 | 'restricted' => $restricted, 93 | 'restrictedUrl' => $restrictedurl, 94 | 'numContent' => $core->h5pF->getNumContent($library->id), 95 | 'numContentDependencies' => $usage['content'] === -1 ? '' : $usage['content'], 96 | 'numLibraryDependencies' => $usage['libraries'], 97 | 'upgradeUrl' => $upgradeurl, 98 | 'detailsUrl' => null, // Not implemented in Moodle. 99 | 'deleteUrl' => null // Not implemented in Moodle. 100 | ); 101 | 102 | $i++; 103 | } 104 | } 105 | 106 | // All translations are made server side. 107 | $settings['libraryList']['listHeaders'] = array( 108 | get_string('librarylisttitle', 'hvp'), 109 | get_string('librarylistrestricted', 'hvp'), 110 | get_string('librarylistinstances', 'hvp'), 111 | get_string('librarylistinstancedependencies', 'hvp'), 112 | get_string('librarylistlibrarydependencies', 'hvp'), 113 | get_string('librarylistactions', 'hvp') 114 | ); 115 | 116 | // Add js. 117 | $liburl = \mod_hvp\view_assets::getsiteroot() . '/mod/hvp/library/'; 118 | 119 | hvp_admin_add_generic_css_and_js($PAGE, $liburl, $settings); 120 | $PAGE->requires->js(new moodle_url($liburl . 'js/h5p-library-list.js' . hvp_get_cache_buster()), true); 121 | 122 | // RENDER PAGE OUTPUT. 123 | 124 | echo $OUTPUT->header(); 125 | 126 | // Print any messages. 127 | \mod_hvp\framework::printMessages('info', \mod_hvp\framework::messages('info')); 128 | \mod_hvp\framework::printMessages('error', \mod_hvp\framework::messages('error')); 129 | 130 | // Page Header. 131 | echo '

' . get_string('libraries', 'hvp') . '

'; 132 | 133 | if ($hubon) { 134 | // Content type cache form. 135 | echo '

' . get_string('contenttypecacheheader', 'hvp') . '

'; 136 | $ctcacheform->display(); 137 | } 138 | 139 | // Upload Form. 140 | echo '

' . get_string('uploadlibraries', 'hvp') . '

'; 141 | $uploadform->display(); 142 | 143 | // Installed Libraries List. 144 | echo '

' . get_string('installedlibraries', 'hvp') . '

'; 145 | echo '
'; 146 | 147 | echo $OUTPUT->footer(); 148 | -------------------------------------------------------------------------------- /pix/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h5p/moodle-mod_hvp/e0255812be2c4b74dae34650da79dc945cdb6b80/pix/icon.png -------------------------------------------------------------------------------- /pix/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 17 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /renderer.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines the renderer for the hvp (H5P) module. 19 | * 20 | * @package mod_hvp 21 | * @copyright 2016 Joubel AS 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | /** 28 | * The renderer for the hvp module. 29 | * 30 | * @copyright 2016 Joubel AS 31 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 32 | * 33 | * @SuppressWarnings(PHPMD) 34 | */ 35 | class mod_hvp_renderer extends plugin_renderer_base { 36 | 37 | /** 38 | * Alter which stylesheets are loaded for H5P. This is useful for adding 39 | * your own custom styles or replacing existing ones. 40 | * 41 | * @param object $scripts List of stylesheets that will be loaded 42 | * @param array $libraries Array of libraries indexed by the library's machineName 43 | * @param string $embedtype Possible values: div, iframe, external, editor 44 | */ 45 | public function hvp_alter_styles(&$scripts, $libraries, $embedtype) { 46 | } 47 | 48 | /** 49 | * Alter which scripts are loaded for H5P. Useful for adding your 50 | * own custom scripts or replacing existing ones. 51 | * 52 | * @param object $scripts List of JavaScripts that will be loaded 53 | * @param array $libraries Array of libraries indexed by the library's machineName 54 | * @param string $embedtype Possible values: div, iframe, external, editor 55 | */ 56 | public function hvp_alter_scripts(&$scripts, $libraries, $embedtype) { 57 | } 58 | 59 | /** 60 | * Alter semantics before they are processed. This is useful for changing 61 | * how the editor looks and how content parameters are filtered. 62 | * 63 | * @param object $semantics Semantics as object 64 | * @param string $name Machine name of library 65 | * @param int $majorversion Major version of library 66 | * @param int $minorversion Minor version of library 67 | */ 68 | public function hvp_alter_semantics(&$semantics, $name, $majorversion, $minorversion) { 69 | } 70 | 71 | /** 72 | * Alter parameters of H5P content after it has been filtered through 73 | * semantics. This is useful for adapting the content to the current context. 74 | * 75 | * @param object $parameters The content parameters for the library 76 | * @param string $name The machine readable name of the library 77 | * @param int $majorversion Major version of the library 78 | * @param int $minorversion Minor version of the library 79 | */ 80 | public function hvp_alter_filtered_parameters(&$parameters, $name, $majorversion, $minorversion) { 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /review.php: -------------------------------------------------------------------------------- 1 | . 16 | /** 17 | * View all results for H5P Content 18 | * 19 | * @package mod_hvp 20 | * @copyright 2016 Joubel AS 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | require_once(dirname(__FILE__) . '/../../config.php'); 25 | require_once("locallib.php"); 26 | global $USER, $PAGE, $DB, $CFG, $OUTPUT, $COURSE; 27 | 28 | $id = required_param('id', PARAM_INT); 29 | $userid = optional_param('user', (int) $USER->id, PARAM_INT); 30 | 31 | if (!$cm = get_coursemodule_from_instance('hvp', $id)) { 32 | print_error('invalidcoursemodule'); 33 | } 34 | if (!$course = $DB->get_record('course', ['id' => $cm->course])) { 35 | print_error('coursemisconf'); 36 | } 37 | require_login($course, false, $cm); 38 | 39 | // Check permission. 40 | $context = \context_module::instance($cm->id); 41 | hvp_require_view_results_permission($userid, $context, $cm->id); 42 | 43 | // Load H5P Content. 44 | $hvp = $DB->get_record_sql( 45 | "SELECT h.id, 46 | h.name AS title, 47 | hl.machine_name, 48 | hl.major_version, 49 | hl.minor_version 50 | FROM {hvp} h 51 | JOIN {hvp_libraries} hl ON hl.id = h.main_library_id 52 | WHERE h.id = ?", 53 | [$id]); 54 | 55 | if ($hvp === false) { 56 | print_error('invalidhvp', 'mod_hvp'); 57 | } 58 | 59 | // Set page properties. 60 | $pageurl = new moodle_url('/mod/hvp/review.php', [ 61 | 'id' => $hvp->id, 62 | ]); 63 | $basepath = \mod_hvp\view_assets::getsiteroot(); 64 | $PAGE->set_url($pageurl); 65 | $PAGE->set_title($hvp->title); 66 | $PAGE->set_heading($COURSE->fullname); 67 | $PAGE->requires->css(new moodle_url($basepath . '/mod/hvp/xapi-custom-report.css')); 68 | 69 | // We have to get grades from gradebook as well. 70 | $xapiresults = $DB->get_records_sql(" 71 | SELECT x.*, i.grademax 72 | FROM {hvp_xapi_results} x 73 | JOIN {grade_items} i ON i.iteminstance = x.content_id 74 | WHERE x.user_id = ? 75 | AND x.content_id = ? 76 | AND i.itemtype = 'mod' 77 | AND i.itemmodule = 'hvp'", [$userid, $id] 78 | ); 79 | 80 | if (!$xapiresults) { 81 | echo $OUTPUT->header(); 82 | echo "

" . get_string('noanswersubmitted', 'hvp') . "

"; 83 | echo $OUTPUT->footer(); 84 | return; 85 | } 86 | 87 | $totalrawscore = null; 88 | $totalmaxscore = null; 89 | $totalscaledscore = null; 90 | $scaledscoreperscore = null; 91 | 92 | // Assemble our question tree. 93 | $basequestion = null; 94 | 95 | // Find base question. 96 | foreach ($xapiresults as $question) { 97 | if ($question->parent_id === null) { 98 | // This is the root of our tree. 99 | $basequestion = $question; 100 | 101 | if (isset($question->raw_score) && isset($question->grademax) && isset($question->max_score)) { 102 | $scaledscoreperscore = $question->max_score ? ($question->grademax / $question->max_score) : 0; 103 | $question->score_scale = round($scaledscoreperscore, 2); 104 | $totalrawscore = $question->raw_score; 105 | $totalmaxscore = $question->max_score; 106 | if ($question->max_score && $question->raw_score === $question->max_score) { 107 | $totalscaledscore = round($question->grademax, 2); 108 | } else { 109 | $totalscaledscore = round($question->score_scale * $question->raw_score, 2); 110 | } 111 | } 112 | break; 113 | } 114 | } 115 | 116 | foreach ($xapiresults as $question) { 117 | if ($question->parent_id === null) { 118 | // Already processed. 119 | continue; 120 | } else if (isset($xapiresults[$question->parent_id])) { 121 | // Add to parent. 122 | $xapiresults[$question->parent_id]->children[] = $question; 123 | } 124 | 125 | // Set scores. 126 | if (!isset($question->raw_score)) { 127 | $question->raw_score = 0; 128 | } 129 | if (isset($question->raw_score) && isset($question->grademax) && isset($question->max_score)) { 130 | $question->scaled_score_per_score = $scaledscoreperscore; 131 | $question->parent_max_score = $totalmaxscore; 132 | $question->score_scale = round($question->raw_score * $scaledscoreperscore, 2); 133 | } 134 | 135 | // Set score labels. 136 | $question->score_label = get_string('reportingscorelabel', 'hvp'); 137 | $question->scaled_score_label = get_string('reportingscaledscorelabel', 'hvp'); 138 | $question->score_delimiter = get_string('reportingscoredelimiter', 'hvp'); 139 | $question->scaled_score_delimiter = get_string('reportingscaledscoredelimiter', 'hvp'); 140 | $question->questions_remaining_label = get_string('reportingquestionsremaininglabel', 'hvp'); 141 | } 142 | 143 | // Initialize reporter. 144 | $reporter = H5PReport::getInstance(); 145 | $reporthtml = $reporter->generateReport($basequestion, null, count($xapiresults) <= 1); 146 | $styles = $reporter->getStylesUsed(); 147 | $scripts = $reporter->getScriptsUsed(); 148 | foreach ($styles as $style) { 149 | $PAGE->requires->css(new moodle_url($basepath . '/mod/hvp/reporting/' . $style)); 150 | } 151 | foreach ($scripts as $script) { 152 | $PAGE->requires->js(new moodle_url($basepath . '/mod/hvp/reporting/' . $script)); 153 | } 154 | 155 | $PAGE->requires->js(new moodle_url($basepath . '/mod/hvp/library/js/jquery.js'), true); 156 | 157 | // Send the enpoints necessary for dynamic grading to the view. 158 | $setsubcontentendpoint = "{$basepath}/mod/hvp/ajax.php?contextId={$context->instanceid}&token=" . 159 | \H5PCore::createToken('result') . '&action=updatesubcontentscore'; 160 | $getsubcontentendpoint = "{$basepath}/mod/hvp/ajax.php?contextId={$context->instanceid}&token=" . 161 | \H5PCore::createToken('result') . '&action=getsubcontentscore'; 162 | $datatosend = array( 163 | 'setSubContentEndpoint' => $setsubcontentendpoint, 164 | 'getSubContentEndpoint' => $getsubcontentendpoint, 165 | ); 166 | $PAGE->requires->data_for_js('data_for_page', $datatosend, true); 167 | 168 | $renderer = $PAGE->get_renderer('mod_hvp'); 169 | 170 | // Print title and report. 171 | $title = $hvp->title; 172 | 173 | // Show user name if other then self. 174 | if ($userid !== (int) $USER->id) { 175 | $userresult = $DB->get_record('user', ["id" => $userid], 'username'); 176 | if (isset($userresult) && isset($userresult->username)) { 177 | $title .= ": {$userresult->username}"; 178 | } 179 | } 180 | 181 | // Create title. 182 | $reviewcontext = [ 183 | 'title' => $title, 184 | 'report' => $reporthtml, 185 | 'rawScore' => $totalrawscore, 186 | 'maxScore' => $totalmaxscore, 187 | 'scaledScore' => round($totalscaledscore, 2), 188 | 'maxScaledScore' => round($basequestion->grademax, 2), 189 | ]; 190 | 191 | // Print page HTML. 192 | echo $OUTPUT->header(); 193 | echo $renderer->render_from_template('hvp/review', $reviewcontext); 194 | echo $OUTPUT->footer(); 195 | -------------------------------------------------------------------------------- /settings-hide-key.js: -------------------------------------------------------------------------------- 1 | /* global HVPSettingsHideKey */ 2 | /** 3 | * Prepares for hiding of the Site Key setting since you cannot modify attributes and such in Moodle. 4 | */ 5 | (function($) { 6 | $(document).ready(function() { 7 | if (!window.HVPSettingsHideKey) { 8 | return; 9 | } 10 | 11 | var $input = $('#' + HVPSettingsHideKey.input); 12 | if (!$input.length) { 13 | return; 14 | } 15 | 16 | var placeHolder = $input.val() || HVPSettingsHideKey.value ? HVPSettingsHideKey.placeholder : HVPSettingsHideKey.empty; 17 | $input.attr('maxlength', 36) 18 | .attr('placeholder', placeHolder) 19 | .data('value', HVPSettingsHideKey.value) 20 | .val(''); 21 | 22 | $('