├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature---improvement-request.md │ ├── question.md │ └── support.md └── workflows │ └── ci.yml ├── .jshintrc ├── COPYING.txt ├── Changes.md ├── Readme.md ├── Support.md ├── amd ├── build │ ├── local │ │ ├── content.min.js │ │ ├── content.min.js.map │ │ └── content │ │ │ ├── actions.min.js │ │ │ └── actions.min.js.map │ ├── thegrid.min.js │ └── thegrid.min.js.map └── src │ ├── local │ ├── content.js │ └── content │ │ └── actions.js │ └── thegrid.js ├── backup └── moodle2 │ ├── backup_format_grid_plugin.class.php │ └── restore_format_grid_plugin.class.php ├── classes ├── admin_setting_configinteger.php ├── admin_setting_information.php ├── admin_setting_markdown.php ├── observer.php ├── output │ ├── courseformat │ │ ├── content.php │ │ ├── content │ │ │ ├── delegatedsection.php │ │ │ ├── section.php │ │ │ ├── section │ │ │ │ ├── cmsummary.php │ │ │ │ ├── controlmenu.php │ │ │ │ └── summary.php │ │ │ ├── sectionnavigation.php │ │ │ └── sectionselector.php │ │ └── state │ │ │ └── course.php │ └── renderer.php ├── privacy │ └── provider.php ├── task │ ├── update_displayed_images_adhoc.php │ └── update_displayed_images_task.php └── toolbox.php ├── db ├── events.php ├── install.xml └── upgrade.php ├── form └── sectionfilemanager.php ├── format.php ├── lang └── en │ └── format_grid.php ├── lib.php ├── pix ├── grid_logo.jpg └── icon.png ├── settings.php ├── styles.css ├── styles_boost.css ├── templates ├── coursestyles.mustache ├── grid.mustache ├── grid_admin_setting_information.mustache ├── grid_admin_setting_markdown.mustache ├── grid_completion.mustache ├── grid_section.mustache ├── local │ ├── content.mustache │ └── content │ │ ├── delegatedsection.mustache │ │ ├── section.mustache │ │ ├── section │ │ ├── cmsummary.mustache │ │ └── content.mustache │ │ ├── sectionnavigation.mustache │ │ └── sectionselector.mustache └── singlepagesummaryimage.mustache ├── tests ├── behat │ ├── BEHAT_COMMANDS.txt │ ├── move_sections.feature │ ├── sectionbreak.feature │ └── uploadimage.feature └── fixtures │ └── Duckling.jpg └── version.php /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text eol=lf 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.php text 7 | *.js text 8 | *.css text 9 | 10 | # Denote all files that are truly binary and should not be modified. 11 | *.png binary 12 | *.jpg binary 13 | *.mbz binary -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report an issue 4 | title: '' 5 | labels: Bug, Pending replication 6 | assignees: gjb2048 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behaviour: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behaviour** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Versions (please complete the following information):** 27 | - Moodle: [e.g. 4.1] 28 | - Format: [e.g. 401.0.1] 29 | - Browser and version [e.g. Chrome Version 108.0.5359.125 (Official Build) (64-bit) on Windows 10] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature---improvement-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature / improvement request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: Enhancement, Funding required, Pending investigation 6 | assignees: gjb2048 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | 22 | **Versions you are currently using (please complete the following information):** 23 | - Moodle: [e.g. 4.1] 24 | - Format: [e.g. 401.1.0] 25 | - Browser and version [e.g. Chrome Version 108.0.5359.125 (Official Build) (64-bit) on Windows 10] 26 | 27 | ** Funding ** 28 | All new features / improvements need to be financially funded. There might be a few exceptions where I decide that I'll learn something new or want to add the functionality, but this will be on a case by case basis. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Question about the format 4 | title: '' 5 | labels: Question 6 | assignees: gjb2048 7 | 8 | --- 9 | 10 | Please use this when you have a general question about the format that is not a bug, enhancement / improvement or support. 11 | 12 | **Versions (please complete the following information):** 13 | - Moodle: [e.g. 4.1] 14 | - Format: [e.g. 401.1.0] 15 | - Browser and version [e.g. Chrome Version 108.0.5359.125 (Official Build) (64-bit) on Windows 10] 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support 3 | about: Support for your installation of the format 4 | title: '' 5 | labels: Support, Funding required 6 | assignees: gjb2048 7 | 8 | --- 9 | 10 | Please use this when you want support with your installation of the format that is not a bug, enhancement / improvement or general question. 11 | 12 | **Versions (please complete the following information):** 13 | - Moodle: [e.g. 4.1] 14 | - Format: [e.g. 401.1.0] 15 | - Browser and version [e.g. Chrome Version 108.0.5359.125 (Official Build) (64-bit) on Windows 10] 16 | 17 | ** Funding ** 18 | Support needs to be financially funded. There might be a few exceptions but this will be on a case by case basis. 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Moodle Plugin CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-22.04 8 | 9 | services: 10 | mariadb: 11 | image: mariadb:10.6 12 | env: 13 | MYSQL_USER: 'root' 14 | MYSQL_ALLOW_EMPTY_PASSWORD: "true" 15 | MYSQL_CHARACTER_SET_SERVER: "utf8mb4" 16 | MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci" 17 | ports: 18 | - 3306:3306 19 | options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | php: ['8.3'] 25 | moodle-branch: ['MOODLE_405_STABLE'] 26 | database: [mariadb] 27 | 28 | steps: 29 | - name: Check out repository code 30 | uses: actions/checkout@v4 31 | with: 32 | path: plugin 33 | 34 | - name: Setup PHP ${{ matrix.php }} 35 | uses: shivammathur/setup-php@v2 36 | with: 37 | php-version: ${{ matrix.php }} 38 | extensions: ${{ matrix.extensions }} 39 | ini-values: max_input_vars=5000 40 | coverage: none 41 | 42 | - name: Initialise moodle-plugin-ci 43 | run: | 44 | composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^4 45 | echo $(cd ci/bin; pwd) >> $GITHUB_PATH 46 | echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH 47 | sudo locale-gen en_AU.UTF-8 48 | echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV 49 | # Install nvm v0.39.7 (Temporary workaround for https://github.com/moodlehq/moodle-plugin-ci/issues/309). 50 | #curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash 51 | - name: Install moodle-plugin-ci 52 | run: | 53 | moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 54 | env: 55 | DB: ${{ matrix.database }} 56 | MOODLE_BRANCH: ${{ matrix.moodle-branch }} 57 | IGNORE_NAMES: 'sectionfilemanager.php' 58 | MUSTACHE_IGNORE_NAMES: 'content.mustache,section.mustache' 59 | 60 | - name: PHP Lint 61 | if: ${{ always() }} 62 | run: moodle-plugin-ci phplint 63 | 64 | # - name: PHP Copy/Paste Detector 65 | # continue-on-error: true # This step will show errors but will not fail 66 | # if: ${{ always() }} 67 | # run: moodle-plugin-ci phpcpd 68 | 69 | - name: PHP Mess Detector 70 | continue-on-error: true # This step will show errors but will not fail 71 | if: ${{ always() }} 72 | run: moodle-plugin-ci phpmd 73 | 74 | - name: Moodle Code Checker 75 | if: ${{ always() }} 76 | run: moodle-plugin-ci codechecker 77 | # run: moodle-plugin-ci codechecker --max-warnings 0 78 | 79 | # - name: Moodle PHPDoc Checker 80 | # if: ${{ always() }} 81 | # run: moodle-plugin-ci phpdoc 82 | 83 | - name: Validating 84 | if: ${{ always() }} 85 | run: moodle-plugin-ci validate 86 | 87 | - name: Check upgrade savepoints 88 | if: ${{ always() }} 89 | run: moodle-plugin-ci savepoints 90 | 91 | - name: Mustache Lint 92 | if: ${{ always() }} 93 | run: moodle-plugin-ci mustache 94 | 95 | # - name: Grunt 96 | # if: ${{ always() }} 97 | # run: moodle-plugin-ci grunt --max-lint-warnings 0 98 | 99 | - name: PHPUnit tests 100 | if: ${{ always() }} 101 | run: moodle-plugin-ci phpunit 102 | 103 | - name: Behat features 104 | if: ${{ always() }} 105 | run: moodle-plugin-ci behat --profile chrome 106 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../.jshintrc", 3 | "globals": { 4 | "$": false, 5 | "console": false, 6 | "Modernizr": false, 7 | "Headroom": false 8 | } 9 | } -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Grid course format 2 | ============================ 3 | A topics based format that uses a grid of user selectable images to select a section. 4 | 5 | Required release of Moodle 6 | ========================== 7 | This version works with Moodle 4.5 version 2024100700.00 (Build: 20241007) and above within the MOODLE_405_STABLE branch until the 8 | next release. 9 | 10 | Please ensure that your hardware and software complies with 'Requirements' in '[Installing Moodle](https://docs.moodle.org/405/en/Installing_Moodle)'. 11 | 12 | Free software 13 | ============= 14 | The Grid format is 'free' software under the terms of the GNU GPLv3 License, please see 'COPYING.txt'. 15 | 16 | The primary source for downloading this branch of the format is https://moodle.org/plugins/view.php?plugin=format_grid 17 | with 'Your Moodle version:' set at 'Moodle 4.5'. 18 | 19 | The secondary source is https://github.com/gjbarnard/moodle-format_grid/tags 20 | 21 | If you download from the development area - https://github.com/gjbarnard/moodle-format_grid - consider that the code is unstable and 22 | not for use in production environments. This is because I develop the next release in stages and use GitHub as a means of backup. 23 | Therefore the code is not finished, subject to alteration and requires testing. 24 | 25 | You have all the rights granted to you by the GPLv3 license. If you are unsure about anything, then the 26 | FAQ - http://www.gnu.org/licenses/gpl-faq.html - is a good place to look. 27 | 28 | If you reuse any of the code then I kindly ask that you make reference to the format. 29 | 30 | If you make improvements or bug fixes then I would appreciate if you would send them back to me by forking from 31 | https://github.com/gjbarnard/moodle-format_grid and doing a 'Pull Request' so that the rest of the Moodle community benefits. 32 | 33 | Support 34 | ======= 35 | Please see Support.md. 36 | 37 | Installation 38 | ============ 39 | 1. Ensure you have the release of Moodle as stated above in 'Required release of Moodle'. This is essential as the 40 | format relies on underlying core code that is out of my control. 41 | 2. Put Moodle in 'Maintenance Mode' (docs.moodle.org/en/admin/setting/maintenancemode) so that there are no 42 | users using it bar you as the administrator - if you have not already done so. 43 | 3. Copy 'grid' to '/course/format/' if you have not already done so. 44 | 4. Go back in as an administrator and follow standard the 'plugin' update notification. If needed, go to 45 | 'Site administration' -> 'Notifications' if this does not happen. 46 | 5. Put Moodle out of Maintenance Mode. 47 | 6. You may need to check that the permissions within the 'grid' folder are 755 for folders and 644 for files. 48 | 49 | Uninstallation 50 | ============== 51 | 1. Put Moodle in 'Maintenance Mode' so that there are no users using it bar you as the administrator. 52 | 2. It is recommended but not essential to change all of the courses that use the format to another. If this is 53 | not done Moodle will pick the last format in your list of formats to use but display in 'Edit settings' of the 54 | course the first format in the list. You can then set the desired format. 55 | 3. In '/course/format/' remove the folder 'grid'. 56 | 4. In the database, remove the row with the 'plugin' of 'format_grid' and 'name' of 'version' in the 'config_plugins' table 57 | and drop the 'format_grid_icon' and 'format_grid_summary' tables. 58 | 5. Put Moodle out of Maintenance Mode. 59 | 60 | Upgrade instructions 61 | ==================== 62 | 1. Ensure you have the release of Moodle as stated above in 'Required release of Moodle'. This is essential as the 63 | format relies on underlying core code that is out of my control. 64 | 2. Put Moodle in 'Maintenance Mode' so that there are no users using it bar you as the administrator. 65 | 3. In '/course/format/' move old 'grid' directory to a backup folder outside of Moodle. 66 | 4. Copy new 'grid' to '/course/format/'. 67 | 5. Go back in as an administrator and follow standard the 'plugin' update notification. If needed, go to 68 | 'Site administration' -> 'Notifications' if this does not happen. 69 | 6. If you have upgraded from Moodle 1.9 and were using the Grid format there, please follow 'Upgrading from M1.9' below 70 | and then return back here. 71 | 7. If automatic 'Purge all caches' appears not to work by lack of display etc. then perform a manual 'Purge all caches' 72 | under 'Home -> Site administration -> Development -> Purge all caches'. 73 | 8. Put Moodle out of Maintenance Mode. 74 | 75 | Downgrading 76 | =========== 77 | If for any reason you need to downgrade to a previous release of the format then the procedure will inform you how to 78 | do so: 79 | 80 | 1. Put Moodle in 'Maintenance Mode' so that there are no users using it bar you as the administrator. 81 | 2. In '/course/format/' remove the folder 'grid' i.e. ALL it's contents - this is VITAL. 82 | 3. Put in the replacement 'grid' folder into '/course/format/'. 83 | 4. This step depends on if you are downgrading to a version prior to 15th July 2012, this should therefore only be for 84 | Moodle 2.3.x and below versions. If you are, perform step 4.1 otherwise, perform step 4.2. 85 | 4.1 In the database, remove the row with the 'plugin' of 'format_grid' and 'name' of 'version' in the 'config_plugins' table 86 | and drop the 'format_grid_icon' and 'format_grid_summary' tables. If automatic 'Purge all caches' appears not to work by 87 | lack of display etc. then perform a manual 'Purge all caches' under 'Home -> Site administration -> Development -> 88 | Purge all caches'. 89 | 4.2 In the database, change the row with the 'plugin' of 'format_grid' and 'name' of 'version' in the 'config_plugins' table 90 | to have the same 'value' as '$plugin->version' in the 'grid/version.php' file i.e. like '2013083000'. Then perform a manual 91 | 'Purge all caches' under 'Home -> Site administration -> Development -> Purge all caches'. 92 | 5. Go back in as an administrator and follow standard the 'plugin' update notification. If needed, go to 93 | 'Site administration' -> 'Notifications' if this does not happen. 94 | 6. Put Moodle out of Maintenance Mode. 95 | 96 | Reporting issues 97 | ================ 98 | Please see Support.md. 99 | 100 | File information 101 | ================ 102 | 103 | Languages 104 | --------- 105 | The grid/lang folder contains the language files for the format. 106 | 107 | Note that existing formats store their language strings in the main moodle.php, which you can also do, but this separate file is 108 | recommended for contributed formats. 109 | 110 | Of course you can have other folders as well as English etc. if you want to provide multiple languages. 111 | 112 | Styles 113 | ------ 114 | The file grid/styles.css contains the CSS styles for the format which can be overridden by the theme. 115 | 116 | Backup 117 | ------ 118 | The files: 119 | 120 | grid/backup/moodle2/backup_format_grid_plugin.class.php 121 | grid/backup/moodle2/restore_format_grid_plugin.class.php 122 | 123 | are responsible for backup and restore. 124 | 125 | Backup and restore run automatically when backing up the course. 126 | You can't back up the course format data independently. 127 | 128 | Roadmap 129 | ============= 130 | 1. Maintenance of issues. 131 | 2. Ongoing structured walk through and refactoring. 132 | 3. Updates to support major releases when they happen. 133 | 134 | Parked 135 | ------ 136 | 1. Improved instructions including Moodle docs. 137 | 2. User definable grid row icon numbers - https://moodle.org/mod/forum/discuss.php?d=196716 138 | 139 | Known issues 140 | ============= 141 | Listed on: https://github.com/gjbarnard/moodle-format_grid/issues 142 | 143 | History 144 | ============= 145 | Please see Changes.md 146 | 147 | Developed and maintained by 148 | =========================== 149 | G J Barnard MSc. BSc(Hons)(Sndw). MBCS. CEng. CITP. PGCE. 150 | 151 | - Moodle profile | [Moodle.org](https://moodle.org/user/profile.php?id=442195) 152 | - Web profile | [About.me](https://about.me/gjbarnard) 153 | - Website | [Website](https://gjbarnard.co.uk) 154 | -------------------------------------------------------------------------------- /Support.md: -------------------------------------------------------------------------------- 1 | The Grid format story 2 | ===================== 3 | The Grid format was originally written by Paul Krix, then taken over by Julian Ridden, and then me in 2012. It is topics based format that 4 | uses a grid of user selectable images to pop up a light box of the section. The format takes time, skill, knowledge and talent to develop. This 5 | is why I'm now asking for your financial support in my endeavours. Without your support, there will no longer be any further major releases of the format. 6 | 7 | If you'd like to sponsor, get support or fund improvements, then please do get in touch via: 8 | 9 | - gjbarnard | Gmail dt com address. 10 | - GitHub | Please outline your issue / improvement on '[GitHub](https://github.com/gjbarnard/moodle-format_grid/issues)'. 11 | - @gjbarnard | '[X](https://twitter.com/gjbarnard)'. 12 | 13 | Open source software 14 | ==================== 15 | As the Grid format is licensed under the [GNU GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html) License it comes with NO support, 16 | please see 'COPYING.txt'. If you would like support from me then I'm happy to provide it for a fee (please see my contact details 17 | below). Otherwise, the Moodle '[Courses and course formats](https://moodle.org/mod/forum/view.php?id=47)' forum is an excellent place 18 | to ask questions. 19 | 20 | The Grid format can be obtained from: 21 | 22 | * [Moodle.org](https://moodle.org/plugins/view.php?plugin=format_grid). 23 | * [GitHub](https://github.com/gjbarnard/moodle-format_grid/releases). 24 | 25 | You have all the rights granted to you by the GPLv3 license. If you are unsure about anything, then the 26 | FAQ - [GPL FAQ](https://www.gnu.org/licenses/gpl-faq.html) - is a good place to look. 27 | 28 | If you reuse any of the code then I kindly ask that you make reference to the format. 29 | 30 | If you make improvements or bug fixes then I would appreciate if you would send them back to me by forking from 31 | [GitHub](https://github.com/gjbarnard/moodle-format_grid/) and doing a 'Pull Request' so that the rest of the Moodle community 32 | benefits. 33 | 34 | Required release of Moodle 35 | ========================== 36 | This version works with Moodle 4.5 version 2024100700.00 (Build: 20241007) and above within the MOODLE_405_STABLE branch until the 37 | next release. 38 | 39 | Please ensure that your hardware and software complies with 'Requirements' in '[Installing Moodle](https://docs.moodle.org/405/en/Installing_Moodle)'. 40 | 41 | Reporting issues 42 | ================ 43 | Before reporting an issue, please ensure that you are running the current release for the major release of Moodle you are using. It 44 | is essential that you are operating the required release of Moodle as stated above, this is because the format relies on core 45 | functionality that is out of its control. 46 | 47 | If you think you've discovered a genuine bug with the format then please look at the Moodle Course and course formats forum first to see if it 48 | has already been repoted. Secondly, look at [GitHub](https://github.com/gjbarnard/moodle-format_grid/issues). 49 | 50 | I operate a policy that I will fix all genuine issues in 'my' (not other developers of the format) code, when fully described and 51 | replicatable. 52 | 53 | It is essential that you provide as much information as possible, the critical information being the contents of the format's 54 | version.php file / or the top of the 'Information' settings tab. Other information such as specific Moodle release, format name and 55 | release also helps. A screen shot can be really useful in visualising the issue along with any files you consider to be relevant. 56 | 57 | You can use either the '[Course and course formats forum](https://moodle.org/mod/forum/view.php?id=47)' or '[GitHub](https://github.com/gjbarnard/moodle-format_grid/issues)'. 58 | 59 | Currently developed and maintained by 60 | ===================================== 61 | G J Barnard MSc. BSc(Hons)(Sndw). MBCS. CEng. CITP. PGCE. 62 | 63 | - Moodle profile | [Moodle.org](https://moodle.org/user/profile.php?id=442195) 64 | - Web profile | [About.me](https://about.me/gjbarnard) 65 | - Website | [Website](https://gjbarnard.co.uk) 66 | -------------------------------------------------------------------------------- /amd/build/local/content.min.js: -------------------------------------------------------------------------------- 1 | define("format_grid/local/content",["exports","core_courseformat/local/content","format_grid/local/content/actions","core_course/events"],(function(_exports,_content,_actions,CourseEvents){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} 2 | /** 3 | * Grid format Course index main component. 4 | * 5 | * @module format_grid/local/content 6 | * @class format_grid/local/content 7 | * @copyright 2023 G J Barnard based upon work done by: 8 | * @copyright 2020 Ferran Recio 9 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 10 | */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_content=_interopRequireDefault(_content),_actions=_interopRequireDefault(_actions),CourseEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CourseEvents);class GridComponent extends _content.default{create(descriptor){this.name="grid_course_format",this.selectors={SECTION:"[data-for='section']",SECTION_ITEM:"[data-for='section_title']",SECTION_CMLIST:"[data-for='cmlist']",COURSE_SECTIONLIST:"[data-for='course_sectionlist']",CM:"[data-for='cmitem']",TOGGLER:'[data-action="togglecoursecontentsection"]',COLLAPSE:'[data-toggle="collapse"]',TOGGLEALL:'[data-toggle="toggleall"]',ACTIVITYTAG:"li",SECTIONTAG:"li"},this.selectorGenerators={cmNameFor:id=>`[data-cm-name-for='${id}']`,sectionNameFor:id=>`[data-section-name-for='${id}']`},this.classes={COLLAPSED:"collapsed",ACTIVITY:"activity",STATEDREADY:"stateready",SECTION:"section"},this.dettachedCms={},this.dettachedSections={},this.sections={},this.cms={},this.sectionReturn=descriptor.sectionReturn??null,this.debouncedReloads=new Map}stateReady(state){this._indexContents(),this.addEventListener(this.element,"click",this._sectionTogglers);const toogleAll=this.getElement(this.selectors.TOGGLEALL);if(toogleAll){const collapseElementIds=[...this.getElements(this.selectors.COLLAPSE)].map((element=>element.id));toogleAll.setAttribute("aria-controls",collapseElementIds.join(" ")),this.addEventListener(toogleAll,"click",this._allSectionToggler),this.addEventListener(toogleAll,"keydown",(e=>{" "===e.key&&this._allSectionToggler(e)})),this._refreshAllSectionsToggler(state)}this.reactive.supportComponents&&(this.reactive.isEditing&&new _actions.default(this),this.element.classList.add(this.classes.STATEDREADY)),this.addEventListener(this.element,CourseEvents.manualCompletionToggled,this._completionHandler),this.addEventListener(document,"scroll",this._scrollHandler)}}return _exports.default=GridComponent,_exports.default})); 11 | 12 | //# sourceMappingURL=content.min.js.map -------------------------------------------------------------------------------- /amd/build/local/content.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"content.min.js","sources":["../../src/local/content.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\n/**\n * Grid format Course index main component.\n *\n * @module format_grid/local/content\n * @class format_grid/local/content\n * @copyright 2023 G J Barnard based upon work done by:\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Component from 'core_courseformat/local/content';\n// Course actions is needed for actions that are not migrated to components.\nimport GridDispatchActions from 'format_grid/local/content/actions';\nimport * as CourseEvents from 'core_course/events';\n\nexport default class GridComponent extends Component {\n\n /**\n * Constructor hook.\n *\n * @param {Object} descriptor the component descriptor\n */\n create(descriptor) {\n // Optional component name for debugging.\n this.name = 'grid_course_format';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_ITEM: `[data-for='section_title']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n COURSE_SECTIONLIST: `[data-for='course_sectionlist']`,\n CM: `[data-for='cmitem']`,\n TOGGLER: `[data-action=\"togglecoursecontentsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n TOGGLEALL: `[data-toggle=\"toggleall\"]`,\n // Formats can override the activity tag but a default one is needed to create new elements.\n ACTIVITYTAG: 'li',\n SECTIONTAG: 'li',\n };\n this.selectorGenerators = {\n cmNameFor: (id) => `[data-cm-name-for='${id}']`,\n sectionNameFor: (id) => `[data-section-name-for='${id}']`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n COLLAPSED: `collapsed`,\n // Course content classes.\n ACTIVITY: `activity`,\n STATEDREADY: `stateready`,\n SECTION: `section`,\n };\n // Array to save dettached elements during element resorting.\n this.dettachedCms = {};\n this.dettachedSections = {};\n // Index of sections and cms components.\n this.sections = {};\n this.cms = {};\n // The page section return.\n this.sectionReturn = descriptor.sectionReturn ?? null;\n this.debouncedReloads = new Map();\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data\n */\n stateReady(state) {\n this._indexContents();\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Collapse/Expand all sections button.\n const toogleAll = this.getElement(this.selectors.TOGGLEALL);\n if (toogleAll) {\n\n // Ensure collapse menu button adds aria-controls attribute referring to each collapsible element.\n const collapseElements = this.getElements(this.selectors.COLLAPSE);\n const collapseElementIds = [...collapseElements].map(element => element.id);\n toogleAll.setAttribute('aria-controls', collapseElementIds.join(' '));\n\n this.addEventListener(toogleAll, 'click', this._allSectionToggler);\n this.addEventListener(toogleAll, 'keydown', e => {\n // Collapse/expand all sections when Space key is pressed on the toggle button.\n if (e.key === ' ') {\n this._allSectionToggler(e);\n }\n });\n this._refreshAllSectionsToggler(state);\n }\n\n if (this.reactive.supportComponents) {\n // Actions are only available in edit mode.\n if (this.reactive.isEditing) {\n new GridDispatchActions(this);\n }\n\n // Mark content as state ready.\n this.element.classList.add(this.classes.STATEDREADY);\n }\n\n // Capture completion events.\n this.addEventListener(\n this.element,\n CourseEvents.manualCompletionToggled,\n this._completionHandler\n );\n\n // Capture page scroll to update page item.\n this.addEventListener(\n document,\n \"scroll\",\n this._scrollHandler\n );\n }\n}\n"],"names":["GridComponent","Component","create","descriptor","name","selectors","SECTION","SECTION_ITEM","SECTION_CMLIST","COURSE_SECTIONLIST","CM","TOGGLER","COLLAPSE","TOGGLEALL","ACTIVITYTAG","SECTIONTAG","selectorGenerators","cmNameFor","id","sectionNameFor","classes","COLLAPSED","ACTIVITY","STATEDREADY","dettachedCms","dettachedSections","sections","cms","sectionReturn","debouncedReloads","Map","stateReady","state","_indexContents","addEventListener","this","element","_sectionTogglers","toogleAll","getElement","collapseElementIds","getElements","map","setAttribute","join","_allSectionToggler","e","key","_refreshAllSectionsToggler","reactive","supportComponents","isEditing","GridDispatchActions","classList","add","CourseEvents","manualCompletionToggled","_completionHandler","document","_scrollHandler"],"mappings":";;;;;;;;;u1BA8BqBA,sBAAsBC,iBAOvCC,OAAOC,iBAEEC,KAAO,0BAEPC,UAAY,CACbC,QAAU,uBACVC,aAAe,6BACfC,eAAiB,sBACjBC,mBAAqB,kCACrBC,GAAK,sBACLC,QAAU,6CACVC,SAAW,2BACXC,UAAY,4BAEZC,YAAa,KACbC,WAAY,WAEXC,mBAAqB,CACtBC,UAAYC,IAAQ,sBAAqBA,OACzCC,eAAiBD,IAAQ,2BAA0BA,aAGlDE,QAAU,CACXC,UAAY,YAEZC,SAAW,WACXC,YAAc,aACdjB,QAAU,gBAGTkB,aAAe,QACfC,kBAAoB,QAEpBC,SAAW,QACXC,IAAM,QAENC,cAAgBzB,WAAWyB,eAAiB,UAC5CC,iBAAmB,IAAIC,IAQhCC,WAAWC,YACFC,sBAEAC,iBAAiBC,KAAKC,QAAS,QAASD,KAAKE,wBAG5CC,UAAYH,KAAKI,WAAWJ,KAAK9B,UAAUQ,cAC7CyB,UAAW,OAILE,mBAAqB,IADFL,KAAKM,YAAYN,KAAK9B,UAAUO,WACR8B,KAAIN,SAAWA,QAAQlB,KACxEoB,UAAUK,aAAa,gBAAiBH,mBAAmBI,KAAK,WAE3DV,iBAAiBI,UAAW,QAASH,KAAKU,yBAC1CX,iBAAiBI,UAAW,WAAWQ,IAE1B,MAAVA,EAAEC,UACGF,mBAAmBC,WAG3BE,2BAA2BhB,OAGhCG,KAAKc,SAASC,oBAEVf,KAAKc,SAASE,eACVC,iBAAoBjB,WAIvBC,QAAQiB,UAAUC,IAAInB,KAAKf,QAAQG,mBAIvCW,iBACDC,KAAKC,QACLmB,aAAaC,wBACbrB,KAAKsB,yBAIJvB,iBACDwB,SACA,SACAvB,KAAKwB"} -------------------------------------------------------------------------------- /amd/build/local/content/actions.min.js: -------------------------------------------------------------------------------- 1 | define("format_grid/local/content/actions",["exports","core_courseformat/local/content/actions","core/log"],(function(_exports,_actions,_log){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} 2 | /** 3 | * Grid Course state actions dispatcher. 4 | * 5 | * @module format_grid/local/content/actions 6 | * @class format_grid/local/content/actions 7 | * @copyright 2025 G J Barnard based upon work done by: 8 | * @copyright 2021 Ferran Recio 9 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 10 | */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_actions=_interopRequireDefault(_actions),_log=_interopRequireDefault(_log);class _default extends _actions.default{create(){super.create()}_checkSectionlist(_ref){let{state:state}=_ref;_log.default.debug("_checkSectionlist state.course.sectionlist "+state.course.sectionlist),_log.default.debug("_checkSectionlist state.course.numsectionswithoutdeligated "+state.course.numsectionswithoutdeligated),this._setAddSectionLocked(state.course.sectionlist.length>=state.course.maxsections)}_setAddSectionLocked(locked){const courseAddSection=this.getElement(this.selectors.COURSEADDSECTION);if(courseAddSection){const addSection=courseAddSection.querySelector(this.selectors.ADDSECTION);_log.default.debug("_setAddSectionLocked addSection "+addSection.classList),addSection&&addSection.classList.toggle(this.classes.DISPLAYNONE,locked);courseAddSection.querySelector(this.selectors.MAXSECTIONSWARNING).classList.toggle(this.classes.DISPLAYNONE,!locked)}}}return _exports.default=_default,_exports.default})); 11 | 12 | //# sourceMappingURL=actions.min.js.map -------------------------------------------------------------------------------- /amd/build/local/content/actions.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"actions.min.js","sources":["../../../src/local/content/actions.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\n/**\n * Grid Course state actions dispatcher.\n *\n * @module format_grid/local/content/actions\n * @class format_grid/local/content/actions\n * @copyright 2025 G J Barnard based upon work done by:\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseActions from 'core_courseformat/local/content/actions';\n\nimport Log from 'core/log';\n\nexport default class extends BaseActions {\n\n /**\n * Constructor hook.\n */\n create() {\n super.create();\n }\n\n /**\n * Check the section list and disable some options if needed.\n *\n * @param {Object} detail the update details.\n * @param {Object} detail.state the state object.\n */\n _checkSectionlist({state}) {\n Log.debug('_checkSectionlist state.course.sectionlist ' + state.course.sectionlist);\n Log.debug('_checkSectionlist state.course.numsectionswithoutdeligated ' + state.course.numsectionswithoutdeligated);\n // Disable \"add section\" actions if the course max sections has been exceeded.\n this._setAddSectionLocked(state.course.sectionlist.length >= state.course.maxsections);\n //this._setAddSectionLocked(state.course.numsectionswithoutdeligated >= state.course.maxsectionswithoutdeligated);\n }\n\n /**\n * Disable all add sections actions.\n *\n * @param {boolean} locked the new locked value.\n */\n _setAddSectionLocked(locked) {\n const courseAddSection = this.getElement(this.selectors.COURSEADDSECTION);\n if (courseAddSection) {\n const addSection = courseAddSection.querySelector(this.selectors.ADDSECTION);\n Log.debug('_setAddSectionLocked addSection ' + addSection.classList);\n if (addSection) {\n addSection.classList.toggle(this.classes.DISPLAYNONE, locked);\n }\n const noMoreSections = courseAddSection.querySelector(this.selectors.MAXSECTIONSWARNING);\n noMoreSections.classList.toggle(this.classes.DISPLAYNONE, !locked);\n }\n }\n}\n"],"names":["BaseActions","create","_checkSectionlist","state","debug","course","sectionlist","numsectionswithoutdeligated","_setAddSectionLocked","length","maxsections","locked","courseAddSection","this","getElement","selectors","COURSEADDSECTION","addSection","querySelector","ADDSECTION","classList","toggle","classes","DISPLAYNONE","MAXSECTIONSWARNING"],"mappings":";;;;;;;;;wLA6B6BA,iBAKzBC,eACUA,SASVC,4BAAkBC,MAACA,yBACXC,MAAM,8CAAgDD,MAAME,OAAOC,0BACnEF,MAAM,8DAAgED,MAAME,OAAOE,kCAElFC,qBAAqBL,MAAME,OAAOC,YAAYG,QAAUN,MAAME,OAAOK,aAS9EF,qBAAqBG,cACXC,iBAAmBC,KAAKC,WAAWD,KAAKE,UAAUC,qBACpDJ,iBAAkB,OACZK,WAAaL,iBAAiBM,cAAcL,KAAKE,UAAUI,yBAC7Df,MAAM,mCAAqCa,WAAWG,WACtDH,YACAA,WAAWG,UAAUC,OAAOR,KAAKS,QAAQC,YAAaZ,QAEnCC,iBAAiBM,cAAcL,KAAKE,UAAUS,oBACtDJ,UAAUC,OAAOR,KAAKS,QAAQC,aAAcZ"} -------------------------------------------------------------------------------- /amd/build/thegrid.min.js: -------------------------------------------------------------------------------- 1 | define("format_grid/thegrid",["exports","core_course/events","jquery","core/log"],(function(_exports,CourseEvents,_jquery,_log){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,CourseEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj} 2 | /** 3 | * JS module for the grid. 4 | * 5 | * @module format_grid/thegrid 6 | * @copyright © 2023-onwards G J Barnard. 7 | * @author G J Barnard - gjbarnard at gmail dot com and {@link http://moodle.org/user/profile.php?id=442195} 8 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 9 | */(CourseEvents),_jquery=_interopRequireDefault(_jquery),_log=_interopRequireDefault(_log);let registered=!1,mctFired=!1;_exports.init=(sectionnumbers,popup,showcompletion)=>{if(_log.default.debug("Grid thegrid JS init"),registered)_log.default.debug("Grid thegrid JS init already registered");else if(popup){_log.default.debug("Grid thegrid sectionnumbers "+sectionnumbers),document.addEventListener(CourseEvents.manualCompletionToggled,(()=>{mctFired=!0})),registered=!0;var currentmodalsection=null,modalshown=!1,currentsection=null,currentsectionshown=!1,endsection=sectionnumbers.length-1,sectionchange=function(direction){null===currentsection&&(currentsection=direction<0?endsection:0),null!==currentsection&&((0,_jquery.default)("#section-"+sectionnumbers[currentsection]).removeClass("grid-current-section"),(currentsection+=direction)<0?currentsection=endsection:currentsection>endsection&&(currentsection=0),(0,_jquery.default)("#section-"+sectionnumbers[currentsection]).addClass("grid-current-section"))},updatecurrentsection=function(){currentsectionshown&&(0,_jquery.default)("#section-"+sectionnumbers[currentsection]).removeClass("grid-current-section"),currentsection=currentmodalsection-1,currentsectionshown&&(0,_jquery.default)("#section-"+sectionnumbers[currentsection]).addClass("grid-current-section")};(0,_jquery.default)("#gridPopup").on("show.bs.modal",(function(event){if(modalshown=!0,null===currentmodalsection){var trigger=(0,_jquery.default)(event.relatedTarget);currentmodalsection=trigger.data("section")}updatecurrentsection();var gml=(0,_jquery.default)("#gridPopupLabel"),triggersectionname=(0,_jquery.default)("#gridpopupsection-"+currentmodalsection).data("sectiontitle");gml.text(triggersectionname),(0,_jquery.default)(this).find("#gridpopupsection-"+currentmodalsection).addClass("active"),(0,_jquery.default)("#gridPopupCarousel").on("slid.bs.carousel",(function(event){var item=(0,_jquery.default)(".gridcarousel-item.active"),st=item.data("sectiontitle");gml.text(st),_log.default.debug("Carousel direction: "+event.direction),currentmodalsection=item.data("section"),updatecurrentsection()}))})),(0,_jquery.default)("#gridPopup").on("hidden.bs.modal",(function(){updatecurrentsection(),null!==currentmodalsection&&(currentmodalsection=null),(0,_jquery.default)(".gridcarousel-item").removeClass("active"),showcompletion&&mctFired&&(mctFired=!1,window.location.reload()),modalshown=!1})),(0,_jquery.default)(".grid-section .grid-modal").on("keydown",(function(event){if(13==event.which||27==event.which){event.preventDefault();var trigger=(0,_jquery.default)(event.currentTarget);currentmodalsection=trigger.data("section"),(0,_jquery.default)("#gridPopup").modal("show")}})),(0,_jquery.default)(document).on("keydown",(function(event){37==event.which?(event.preventDefault(),currentsectionshown=!0,sectionchange(-1),_log.default.debug("Left: "+sectionnumbers[currentsection]),modalshown&&(0,_jquery.default)("#gridPopupCarouselLeft").trigger("click")):39==event.which?(event.preventDefault(),currentsectionshown=!0,sectionchange(1),_log.default.debug("Right: "+sectionnumbers[currentsection]),modalshown&&(0,_jquery.default)("#gridPopupCarouselRight").trigger("click")):13!=event.which&&27!=event.which||!modalshown&¤tsectionshown&&(null===currentmodalsection&&(currentmodalsection=sectionnumbers[currentsection]),(0,_jquery.default)("#gridPopup").modal("show"))}))}}})); 10 | 11 | //# sourceMappingURL=thegrid.min.js.map -------------------------------------------------------------------------------- /amd/build/thegrid.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"thegrid.min.js","sources":["../src/thegrid.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\n/**\n * JS module for the grid.\n *\n * @module format_grid/thegrid\n * @copyright © 2023-onwards G J Barnard.\n * @author G J Barnard - gjbarnard at gmail dot com and {@link http://moodle.org/user/profile.php?id=442195}\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as CourseEvents from 'core_course/events';\nimport jQuery from 'jquery';\nimport log from 'core/log';\n\n/**\n * Whether the event listener has already been registered for this module.\n *\n * @type {boolean}\n */\nlet registered = false;\n\n/**\n * If the manualCompletionToggled event has fired.\n *\n * @type {boolean}\n */\nlet mctFired = false;\n\n/**\n * Function to intialise and register event listeners for this module.\n *\n * @param {array} sectionnumbers Show completion is on.\n * @param {boolean} popup Popup is used.\n * @param {boolean} showcompletion Show completion is on.\n */\nexport const init = (sectionnumbers, popup, showcompletion) => {\n log.debug('Grid thegrid JS init');\n if (registered) {\n log.debug('Grid thegrid JS init already registered');\n return;\n }\n if (popup) {\n log.debug('Grid thegrid sectionnumbers ' + sectionnumbers);\n\n // Listen for toggled manual completion states of activities.\n document.addEventListener(CourseEvents.manualCompletionToggled, () => {\n mctFired = true;\n });\n registered = true;\n\n // Modal.\n var currentmodalsection = null;\n var modalshown = false;\n\n // Grid current section.\n var currentsection = null;\n var currentsectionshown = false;\n var endsection = sectionnumbers.length - 1;\n\n /**\n * Change the current selected section, arrow keys only.\n * If the modal is shown then will also move the carousel.\n * @param {int} direction -1 = left and 1 = right.\n */\n var sectionchange = function (direction) {\n if (currentsection === null) {\n if (direction < 0) {\n currentsection = endsection;\n } else {\n currentsection = 0;\n }\n }\n if (currentsection !== null) {\n jQuery('#section-' + sectionnumbers[currentsection]).removeClass('grid-current-section');\n currentsection = currentsection + direction;\n if (currentsection < 0) {\n currentsection = endsection;\n } else if (currentsection > endsection) {\n currentsection = 0;\n }\n jQuery('#section-' + sectionnumbers[currentsection]).addClass('grid-current-section');\n }\n };\n\n var updatecurrentsection = function () {\n if (currentsectionshown) {\n jQuery('#section-' + sectionnumbers[currentsection]).removeClass('grid-current-section');\n }\n currentsection = currentmodalsection - 1;\n if (currentsectionshown) {\n jQuery('#section-' + sectionnumbers[currentsection]).addClass('grid-current-section');\n }\n };\n\n jQuery('#gridPopup').on('show.bs.modal', function (event) {\n modalshown = true;\n if (currentmodalsection === null) {\n var trigger = jQuery(event.relatedTarget);\n currentmodalsection = trigger.data('section');\n }\n\n updatecurrentsection();\n\n var gml = jQuery('#gridPopupLabel');\n var triggersectionname = jQuery('#gridpopupsection-' + currentmodalsection).data('sectiontitle');\n gml.text(triggersectionname);\n\n var modal = jQuery(this);\n modal.find('#gridpopupsection-' + currentmodalsection).addClass('active');\n\n jQuery('#gridPopupCarousel').on('slid.bs.carousel', function (event) {\n var item = jQuery('.gridcarousel-item.active');\n var st = item.data('sectiontitle');\n gml.text(st);\n log.debug(\"Carousel direction: \" + event.direction);\n currentmodalsection = item.data('section');\n updatecurrentsection();\n });\n });\n\n jQuery('#gridPopup').on('hidden.bs.modal', function () {\n updatecurrentsection();\n\n if (currentmodalsection !== null) {\n currentmodalsection = null;\n }\n jQuery('.gridcarousel-item').removeClass('active');\n if (showcompletion && mctFired) {\n mctFired = false;\n window.location.reload();\n }\n modalshown = false;\n });\n\n jQuery(\".grid-section .grid-modal\").on('keydown', function (event) {\n // Clicked within the modal\n if ((event.which == 13) || (event.which == 27)) {\n event.preventDefault();\n var trigger = jQuery(event.currentTarget);\n currentmodalsection = trigger.data('section');\n jQuery('#gridPopup').modal('show');\n }\n });\n\n jQuery(document).on('keydown', function (event) {\n if (event.which == 37) {\n // Left.\n event.preventDefault();\n currentsectionshown = true;\n sectionchange(-1);\n log.debug(\"Left: \" + sectionnumbers[currentsection]);\n if (modalshown) {\n jQuery('#gridPopupCarouselLeft').trigger('click');\n }\n } else if (event.which == 39) {\n // Right.\n event.preventDefault();\n currentsectionshown = true;\n sectionchange(1);\n log.debug(\"Right: \" + sectionnumbers[currentsection]);\n if (modalshown) {\n jQuery('#gridPopupCarouselRight').trigger('click');\n }\n } else if ((event.which == 13) || (event.which == 27)) {\n // Enter (13) and ESC keys (27).\n if ((!modalshown) && (currentsectionshown)) {\n if (currentmodalsection === null) {\n currentmodalsection = sectionnumbers[currentsection];\n }\n jQuery('#gridPopup').modal('show');\n }\n }\n });\n }\n};\n"],"names":["registered","mctFired","sectionnumbers","popup","showcompletion","debug","document","addEventListener","CourseEvents","manualCompletionToggled","currentmodalsection","modalshown","currentsection","currentsectionshown","endsection","length","sectionchange","direction","removeClass","addClass","updatecurrentsection","on","event","trigger","relatedTarget","data","gml","triggersectionname","text","this","find","item","st","window","location","reload","which","preventDefault","currentTarget","modal"],"mappings":";;;;;;;;kGAiCIA,YAAa,EAObC,UAAW,gBASK,CAACC,eAAgBC,MAAOC,kCACpCC,MAAM,wBACNL,wBACIK,MAAM,mDAGVF,MAAO,cACHE,MAAM,+BAAiCH,gBAG3CI,SAASC,iBAAiBC,aAAaC,yBAAyB,KAC5DR,UAAW,KAEfD,YAAa,MAGTU,oBAAsB,KACtBC,YAAa,EAGbC,eAAiB,KACjBC,qBAAsB,EACtBC,WAAaZ,eAAea,OAAS,EAOrCC,cAAgB,SAAUC,WACH,OAAnBL,iBAEIA,eADAK,UAAY,EACKH,WAEA,GAGF,OAAnBF,qCACO,YAAcV,eAAeU,iBAAiBM,YAAY,yBACjEN,gBAAkCK,WACb,EACjBL,eAAiBE,WACVF,eAAiBE,aACxBF,eAAiB,uBAEd,YAAcV,eAAeU,iBAAiBO,SAAS,0BAIlEC,qBAAuB,WACnBP,yCACO,YAAcX,eAAeU,iBAAiBM,YAAY,wBAErEN,eAAiBF,oBAAsB,EACnCG,yCACO,YAAcX,eAAeU,iBAAiBO,SAAS,6CAI/D,cAAcE,GAAG,iBAAiB,SAAUC,UAC/CX,YAAa,EACe,OAAxBD,oBAA8B,KAC1Ba,SAAU,mBAAOD,MAAME,eAC3Bd,oBAAsBa,QAAQE,KAAK,WAGvCL,2BAEIM,KAAM,mBAAO,mBACbC,oBAAqB,mBAAO,qBAAuBjB,qBAAqBe,KAAK,gBACjFC,IAAIE,KAAKD,qBAEG,mBAAOE,MACbC,KAAK,qBAAuBpB,qBAAqBS,SAAS,8BAEzD,sBAAsBE,GAAG,oBAAoB,SAAUC,WACtDS,MAAO,mBAAO,6BACdC,GAAKD,KAAKN,KAAK,gBACnBC,IAAIE,KAAKI,iBACL3B,MAAM,uBAAyBiB,MAAML,WACzCP,oBAAsBqB,KAAKN,KAAK,WAChCL,iDAID,cAAcC,GAAG,mBAAmB,WACvCD,uBAE4B,OAAxBV,sBACAA,oBAAsB,0BAEnB,sBAAsBQ,YAAY,UACrCd,gBAAkBH,WAClBA,UAAW,EACXgC,OAAOC,SAASC,UAEpBxB,YAAa,yBAGV,6BAA6BU,GAAG,WAAW,SAAUC,UAEpC,IAAfA,MAAMc,OAAgC,IAAfd,MAAMc,MAAc,CAC5Cd,MAAMe,qBACFd,SAAU,mBAAOD,MAAMgB,eAC3B5B,oBAAsBa,QAAQE,KAAK,+BAC5B,cAAcc,MAAM,gCAI5BjC,UAAUe,GAAG,WAAW,SAAUC,OAClB,IAAfA,MAAMc,OAENd,MAAMe,iBACNxB,qBAAsB,EACtBG,eAAe,gBACXX,MAAM,SAAWH,eAAeU,iBAChCD,gCACO,0BAA0BY,QAAQ,UAEvB,IAAfD,MAAMc,OAEbd,MAAMe,iBACNxB,qBAAsB,EACtBG,cAAc,gBACVX,MAAM,UAAYH,eAAeU,iBACjCD,gCACO,2BAA2BY,QAAQ,UAEvB,IAAfD,MAAMc,OAAgC,IAAfd,MAAMc,QAE/BzB,YAAgBE,sBACU,OAAxBH,sBACAA,oBAAsBR,eAAeU,qCAElC,cAAc2B,MAAM"} -------------------------------------------------------------------------------- /amd/src/local/content.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 | /** 17 | * Grid format Course index main component. 18 | * 19 | * @module format_grid/local/content 20 | * @class format_grid/local/content 21 | * @copyright 2023 G J Barnard based upon work done by: 22 | * @copyright 2020 Ferran Recio 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | import Component from 'core_courseformat/local/content'; 27 | // Course actions is needed for actions that are not migrated to components. 28 | import GridDispatchActions from 'format_grid/local/content/actions'; 29 | import * as CourseEvents from 'core_course/events'; 30 | 31 | export default class GridComponent extends Component { 32 | 33 | /** 34 | * Constructor hook. 35 | * 36 | * @param {Object} descriptor the component descriptor 37 | */ 38 | create(descriptor) { 39 | // Optional component name for debugging. 40 | this.name = 'grid_course_format'; 41 | // Default query selectors. 42 | this.selectors = { 43 | SECTION: `[data-for='section']`, 44 | SECTION_ITEM: `[data-for='section_title']`, 45 | SECTION_CMLIST: `[data-for='cmlist']`, 46 | COURSE_SECTIONLIST: `[data-for='course_sectionlist']`, 47 | CM: `[data-for='cmitem']`, 48 | TOGGLER: `[data-action="togglecoursecontentsection"]`, 49 | COLLAPSE: `[data-toggle="collapse"]`, 50 | TOGGLEALL: `[data-toggle="toggleall"]`, 51 | // Formats can override the activity tag but a default one is needed to create new elements. 52 | ACTIVITYTAG: 'li', 53 | SECTIONTAG: 'li', 54 | }; 55 | this.selectorGenerators = { 56 | cmNameFor: (id) => `[data-cm-name-for='${id}']`, 57 | sectionNameFor: (id) => `[data-section-name-for='${id}']`, 58 | }; 59 | // Default classes to toggle on refresh. 60 | this.classes = { 61 | COLLAPSED: `collapsed`, 62 | // Course content classes. 63 | ACTIVITY: `activity`, 64 | STATEDREADY: `stateready`, 65 | SECTION: `section`, 66 | }; 67 | // Array to save dettached elements during element resorting. 68 | this.dettachedCms = {}; 69 | this.dettachedSections = {}; 70 | // Index of sections and cms components. 71 | this.sections = {}; 72 | this.cms = {}; 73 | // The page section return. 74 | this.sectionReturn = descriptor.sectionReturn ?? null; 75 | this.debouncedReloads = new Map(); 76 | } 77 | 78 | /** 79 | * Initial state ready method. 80 | * 81 | * @param {Object} state the state data 82 | */ 83 | stateReady(state) { 84 | this._indexContents(); 85 | // Activate section togglers. 86 | this.addEventListener(this.element, 'click', this._sectionTogglers); 87 | 88 | // Collapse/Expand all sections button. 89 | const toogleAll = this.getElement(this.selectors.TOGGLEALL); 90 | if (toogleAll) { 91 | 92 | // Ensure collapse menu button adds aria-controls attribute referring to each collapsible element. 93 | const collapseElements = this.getElements(this.selectors.COLLAPSE); 94 | const collapseElementIds = [...collapseElements].map(element => element.id); 95 | toogleAll.setAttribute('aria-controls', collapseElementIds.join(' ')); 96 | 97 | this.addEventListener(toogleAll, 'click', this._allSectionToggler); 98 | this.addEventListener(toogleAll, 'keydown', e => { 99 | // Collapse/expand all sections when Space key is pressed on the toggle button. 100 | if (e.key === ' ') { 101 | this._allSectionToggler(e); 102 | } 103 | }); 104 | this._refreshAllSectionsToggler(state); 105 | } 106 | 107 | if (this.reactive.supportComponents) { 108 | // Actions are only available in edit mode. 109 | if (this.reactive.isEditing) { 110 | new GridDispatchActions(this); 111 | } 112 | 113 | // Mark content as state ready. 114 | this.element.classList.add(this.classes.STATEDREADY); 115 | } 116 | 117 | // Capture completion events. 118 | this.addEventListener( 119 | this.element, 120 | CourseEvents.manualCompletionToggled, 121 | this._completionHandler 122 | ); 123 | 124 | // Capture page scroll to update page item. 125 | this.addEventListener( 126 | document, 127 | "scroll", 128 | this._scrollHandler 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /amd/src/local/content/actions.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 | /** 17 | * Grid Course state actions dispatcher. 18 | * 19 | * @module format_grid/local/content/actions 20 | * @class format_grid/local/content/actions 21 | * @copyright 2025 G J Barnard based upon work done by: 22 | * @copyright 2021 Ferran Recio 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | import BaseActions from 'core_courseformat/local/content/actions'; 27 | 28 | import Log from 'core/log'; 29 | 30 | export default class extends BaseActions { 31 | 32 | /** 33 | * Constructor hook. 34 | */ 35 | create() { 36 | super.create(); 37 | } 38 | 39 | /** 40 | * Check the section list and disable some options if needed. 41 | * 42 | * @param {Object} detail the update details. 43 | * @param {Object} detail.state the state object. 44 | */ 45 | _checkSectionlist({state}) { 46 | Log.debug('_checkSectionlist state.course.sectionlist ' + state.course.sectionlist); 47 | Log.debug('_checkSectionlist state.course.numsectionswithoutdeligated ' + state.course.numsectionswithoutdeligated); 48 | // Disable "add section" actions if the course max sections has been exceeded. 49 | this._setAddSectionLocked(state.course.sectionlist.length >= state.course.maxsections); 50 | //this._setAddSectionLocked(state.course.numsectionswithoutdeligated >= state.course.maxsectionswithoutdeligated); 51 | } 52 | 53 | /** 54 | * Disable all add sections actions. 55 | * 56 | * @param {boolean} locked the new locked value. 57 | */ 58 | _setAddSectionLocked(locked) { 59 | const courseAddSection = this.getElement(this.selectors.COURSEADDSECTION); 60 | if (courseAddSection) { 61 | const addSection = courseAddSection.querySelector(this.selectors.ADDSECTION); 62 | Log.debug('_setAddSectionLocked addSection ' + addSection.classList); 63 | if (addSection) { 64 | addSection.classList.toggle(this.classes.DISPLAYNONE, locked); 65 | } 66 | const noMoreSections = courseAddSection.querySelector(this.selectors.MAXSECTIONSWARNING); 67 | noMoreSections.classList.toggle(this.classes.DISPLAYNONE, !locked); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /amd/src/thegrid.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 | /** 17 | * JS module for the grid. 18 | * 19 | * @module format_grid/thegrid 20 | * @copyright © 2023-onwards G J Barnard. 21 | * @author G J Barnard - gjbarnard at gmail dot com and {@link http://moodle.org/user/profile.php?id=442195} 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | import * as CourseEvents from 'core_course/events'; 26 | import jQuery from 'jquery'; 27 | import log from 'core/log'; 28 | 29 | /** 30 | * Whether the event listener has already been registered for this module. 31 | * 32 | * @type {boolean} 33 | */ 34 | let registered = false; 35 | 36 | /** 37 | * If the manualCompletionToggled event has fired. 38 | * 39 | * @type {boolean} 40 | */ 41 | let mctFired = false; 42 | 43 | /** 44 | * Function to intialise and register event listeners for this module. 45 | * 46 | * @param {array} sectionnumbers Show completion is on. 47 | * @param {boolean} popup Popup is used. 48 | * @param {boolean} showcompletion Show completion is on. 49 | */ 50 | export const init = (sectionnumbers, popup, showcompletion) => { 51 | log.debug('Grid thegrid JS init'); 52 | if (registered) { 53 | log.debug('Grid thegrid JS init already registered'); 54 | return; 55 | } 56 | if (popup) { 57 | log.debug('Grid thegrid sectionnumbers ' + sectionnumbers); 58 | 59 | // Listen for toggled manual completion states of activities. 60 | document.addEventListener(CourseEvents.manualCompletionToggled, () => { 61 | mctFired = true; 62 | }); 63 | registered = true; 64 | 65 | // Modal. 66 | var currentmodalsection = null; 67 | var modalshown = false; 68 | 69 | // Grid current section. 70 | var currentsection = null; 71 | var currentsectionshown = false; 72 | var endsection = sectionnumbers.length - 1; 73 | 74 | /** 75 | * Change the current selected section, arrow keys only. 76 | * If the modal is shown then will also move the carousel. 77 | * @param {int} direction -1 = left and 1 = right. 78 | */ 79 | var sectionchange = function (direction) { 80 | if (currentsection === null) { 81 | if (direction < 0) { 82 | currentsection = endsection; 83 | } else { 84 | currentsection = 0; 85 | } 86 | } 87 | if (currentsection !== null) { 88 | jQuery('#section-' + sectionnumbers[currentsection]).removeClass('grid-current-section'); 89 | currentsection = currentsection + direction; 90 | if (currentsection < 0) { 91 | currentsection = endsection; 92 | } else if (currentsection > endsection) { 93 | currentsection = 0; 94 | } 95 | jQuery('#section-' + sectionnumbers[currentsection]).addClass('grid-current-section'); 96 | } 97 | }; 98 | 99 | var updatecurrentsection = function () { 100 | if (currentsectionshown) { 101 | jQuery('#section-' + sectionnumbers[currentsection]).removeClass('grid-current-section'); 102 | } 103 | currentsection = currentmodalsection - 1; 104 | if (currentsectionshown) { 105 | jQuery('#section-' + sectionnumbers[currentsection]).addClass('grid-current-section'); 106 | } 107 | }; 108 | 109 | jQuery('#gridPopup').on('show.bs.modal', function (event) { 110 | modalshown = true; 111 | if (currentmodalsection === null) { 112 | var trigger = jQuery(event.relatedTarget); 113 | currentmodalsection = trigger.data('section'); 114 | } 115 | 116 | updatecurrentsection(); 117 | 118 | var gml = jQuery('#gridPopupLabel'); 119 | var triggersectionname = jQuery('#gridpopupsection-' + currentmodalsection).data('sectiontitle'); 120 | gml.text(triggersectionname); 121 | 122 | var modal = jQuery(this); 123 | modal.find('#gridpopupsection-' + currentmodalsection).addClass('active'); 124 | 125 | jQuery('#gridPopupCarousel').on('slid.bs.carousel', function (event) { 126 | var item = jQuery('.gridcarousel-item.active'); 127 | var st = item.data('sectiontitle'); 128 | gml.text(st); 129 | log.debug("Carousel direction: " + event.direction); 130 | currentmodalsection = item.data('section'); 131 | updatecurrentsection(); 132 | }); 133 | }); 134 | 135 | jQuery('#gridPopup').on('hidden.bs.modal', function () { 136 | updatecurrentsection(); 137 | 138 | if (currentmodalsection !== null) { 139 | currentmodalsection = null; 140 | } 141 | jQuery('.gridcarousel-item').removeClass('active'); 142 | if (showcompletion && mctFired) { 143 | mctFired = false; 144 | window.location.reload(); 145 | } 146 | modalshown = false; 147 | }); 148 | 149 | jQuery(".grid-section .grid-modal").on('keydown', function (event) { 150 | // Clicked within the modal 151 | if ((event.which == 13) || (event.which == 27)) { 152 | event.preventDefault(); 153 | var trigger = jQuery(event.currentTarget); 154 | currentmodalsection = trigger.data('section'); 155 | jQuery('#gridPopup').modal('show'); 156 | } 157 | }); 158 | 159 | jQuery(document).on('keydown', function (event) { 160 | if (event.which == 37) { 161 | // Left. 162 | event.preventDefault(); 163 | currentsectionshown = true; 164 | sectionchange(-1); 165 | log.debug("Left: " + sectionnumbers[currentsection]); 166 | if (modalshown) { 167 | jQuery('#gridPopupCarouselLeft').trigger('click'); 168 | } 169 | } else if (event.which == 39) { 170 | // Right. 171 | event.preventDefault(); 172 | currentsectionshown = true; 173 | sectionchange(1); 174 | log.debug("Right: " + sectionnumbers[currentsection]); 175 | if (modalshown) { 176 | jQuery('#gridPopupCarouselRight').trigger('click'); 177 | } 178 | } else if ((event.which == 13) || (event.which == 27)) { 179 | // Enter (13) and ESC keys (27). 180 | if ((!modalshown) && (currentsectionshown)) { 181 | if (currentmodalsection === null) { 182 | currentmodalsection = sectionnumbers[currentsection]; 183 | } 184 | jQuery('#gridPopup').modal('show'); 185 | } 186 | } 187 | }); 188 | } 189 | }; 190 | -------------------------------------------------------------------------------- /backup/moodle2/backup_format_grid_plugin.class.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2012 G J Barnard in respect to modifications of standard topics format. 22 | * @author G J Barnard - {@link https://about.me/gjbarnard} and 23 | * {@link https://moodle.org/user/profile.php?id=442195} 24 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | /** 28 | * Provides the information to backup grid course format 29 | */ 30 | class backup_format_grid_plugin extends backup_format_plugin { 31 | /** 32 | * Returns the format information to attach to section element. 33 | */ 34 | protected function define_section_plugin_structure() { 35 | 36 | // Define the virtual plugin element with the condition to fulfill. 37 | $plugin = $this->get_plugin_element(null, $this->get_format_condition(), 'grid'); 38 | 39 | // Create one standard named plugin element (the visible container). 40 | // The sectionid and courseid not required as populated on restore. 41 | $recomendedname = $this->get_recommended_name(); 42 | $section = new backup_nested_element($recomendedname, ['sectionid'], ['image', 'contenthash']); 43 | 44 | // Connect the visible container ASAP. 45 | $plugin->add_child($section); 46 | 47 | // Set source to populate the data. 48 | $section->set_source_table('format_grid_image', ['sectionid' => backup::VAR_SECTIONID]); 49 | $section->annotate_files('format_grid', 'sectionimage', 'sectionid'); 50 | 51 | return $plugin; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /classes/admin_setting_configinteger.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Integer admin setting with lower and upper limits. 19 | * 20 | * @package format_grid 21 | * @copyright 2025 G J Barnard. 22 | * @author G J Barnard - 23 | * {@link https://moodle.org/user/profile.php?id=442195} 24 | * {@link https://gjbarnard.co.uk} 25 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 26 | */ 27 | 28 | namespace format_grid; 29 | 30 | /** 31 | * Integer admin setting with lower and upper limits. 32 | */ 33 | class admin_setting_configinteger extends \admin_setting_configtext { 34 | /** @var int lower range limit */ 35 | public $lower; 36 | /** @var int upper range limit */ 37 | public $upper; 38 | 39 | /** 40 | * Config integer constructor 41 | * 42 | * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in 43 | * config_plugins. 44 | * @param string $visiblename localised 45 | * @param string $description long localised info 46 | * @param string $defaultsetting 47 | * @param int $lower lower range limit 48 | * @param int $upper upper range limit 49 | */ 50 | public function __construct($name, $visiblename, $description, $defaultsetting, $lower, $upper) { 51 | if ($upper < $lower) { 52 | throw new coding_exception('Upper range limit is less than the lower range limit.'); 53 | } 54 | $this->lower = $lower; 55 | $this->upper = $upper; 56 | parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_INT); 57 | } 58 | 59 | /** 60 | * Checks if data has empty html. 61 | * 62 | * @param string $data 63 | * 64 | * @return string Empty when no errors. 65 | */ 66 | public function write_setting($data) { 67 | // Trim any spaces to avoid spaces typo. 68 | $data = trim($data); 69 | if ($data === '') { 70 | // Override parent behaviour and set to default if empty string. 71 | $data = $this->get_defaultsetting(); 72 | } 73 | return parent::write_setting($data); 74 | } 75 | 76 | /** 77 | * Validate data before storage 78 | * @param string $data 79 | * 80 | * @return mixed true if ok string if error found 81 | */ 82 | public function validate($data) { 83 | if (!is_number($data)) { 84 | $validated = get_string('asconfigintnan', 'format_grid', ['value' => $data]); 85 | } else { 86 | $validated = parent::validate($data); // Pass parent validation first. 87 | 88 | if ($validated == true) { 89 | if ($data < $this->lower) { 90 | $validated = get_string('asconfigintlower', 'format_grid', ['value' => $data, 'lower' => $this->lower]); 91 | } else if ($data > $this->upper) { 92 | $validated = get_string('asconfigintupper', 'format_grid', ['value' => $data, 'upper' => $this->upper]); 93 | } else { 94 | $validated = true; 95 | } 96 | } 97 | } 98 | 99 | return $validated; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /classes/admin_setting_information.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2022 G J Barnard in respect to modifications of standard topics format. 22 | * @author G J Barnard - {@link https://about.me/gjbarnard} and 23 | * {@link https://moodle.org/user/profile.php?id=442195} 24 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | namespace format_grid; 28 | 29 | use admin_setting; 30 | use core_plugin_manager; 31 | use core\output\html_writer; 32 | 33 | /** 34 | * Setting that displays information. Based on admin_setting_description in adminlib.php. 35 | * 36 | * @copyright © 2022-onwards G J Barnard. 37 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 38 | */ 39 | class admin_setting_information extends admin_setting { 40 | /** @var int The branch this is for. */ 41 | protected $mbranch; 42 | 43 | /** 44 | * Not a setting, just information. 45 | * 46 | * @param string $name Setting name. 47 | * @param string $visiblename Setting name on the device. 48 | * @param string $description Setting description on the device. 49 | * @param string $mbranch The branch this is for. 50 | */ 51 | public function __construct($name, $visiblename, $description, $mbranch) { 52 | $this->nosave = true; 53 | $this->mbranch = $mbranch; 54 | return parent::__construct($name, $visiblename, $description, ''); 55 | } 56 | 57 | /** 58 | * Always returns true. 59 | * 60 | * @return bool Always returns true. 61 | */ 62 | public function get_setting() { 63 | return true; 64 | } 65 | 66 | /** 67 | * Always returns true. 68 | * 69 | * @return bool Always returns true. 70 | */ 71 | public function get_defaultsetting() { 72 | return true; 73 | } 74 | 75 | /** 76 | * Never write settings 77 | * 78 | * @param mixed $data Gets converted to str for comparison against yes value. 79 | * @return string Always returns an empty string. 80 | */ 81 | public function write_setting($data) { 82 | // Do not write any setting. 83 | return ''; 84 | } 85 | 86 | /** 87 | * Returns an HTML string 88 | * 89 | * @param string $data 90 | * @param string $query 91 | * @return string Returns an HTML string 92 | */ 93 | public function output_html($data, $query = '') { 94 | global $CFG, $OUTPUT; 95 | 96 | $formats = \core_plugin_manager::instance()->get_present_plugins('format'); 97 | if (!empty($formats['grid'])) { 98 | $plugininfo = $formats['grid']; 99 | } else { 100 | $plugininfo = core_plugin_manager::instance()->get_plugin_info('format_grid'); 101 | $plugininfo->version = $plugininfo->versiondisk; 102 | } 103 | 104 | $classes[] = 'fa fa-heart'; 105 | $attributes = []; 106 | $attributes['aria-hidden'] = 'true'; 107 | $attributes['class'] = 'fa fa-heart'; 108 | $attributes['title'] = get_string('love', 'format_grid'); 109 | $content = html_writer::tag('span', $attributes['title'], ['class' => 'sr-only']); 110 | $content = html_writer::tag('span', $content, $attributes); 111 | $context['versioninfo'] = get_string( 112 | 'versioninfo', 113 | 'format_grid', 114 | [ 115 | 'moodle' => $CFG->release, 116 | 'release' => $plugininfo->release, 117 | 'version' => $plugininfo->version, 118 | 'love' => $content, 119 | ] 120 | ); 121 | 122 | if (!empty($plugininfo->maturity)) { 123 | switch ($plugininfo->maturity) { 124 | case MATURITY_ALPHA: 125 | $context['maturity'] = get_string('versionalpha', 'format_grid'); 126 | $context['maturityalert'] = 'danger'; 127 | break; 128 | case MATURITY_BETA: 129 | $context['maturity'] = get_string('versionbeta', 'format_grid'); 130 | $context['maturityalert'] = 'danger'; 131 | break; 132 | case MATURITY_RC: 133 | $context['maturity'] = get_string('versionrc', 'format_grid'); 134 | $context['maturityalert'] = 'warning'; 135 | break; 136 | case MATURITY_STABLE: 137 | $context['maturity'] = get_string('versionstable', 'format_grid'); 138 | $context['maturityalert'] = 'info'; 139 | break; 140 | } 141 | } 142 | 143 | if ($CFG->branch != $this->mbranch) { 144 | $context['versioncheck'] = 'Release ' . $plugininfo->release . ', version ' . $plugininfo->version; 145 | $context['versioncheck'] .= ' is incompatible with Moodle ' . $CFG->release; 146 | $context['versioncheck'] .= ', please get the correct version from '; 147 | $context['versioncheck'] .= 'Moodle.org. '; 148 | $context['versioncheck'] .= 'If none is available, then please consider supporting the format by funding it. '; 149 | $context['versioncheck'] .= 'Please contact me via \'gjbarnard at gmail dot com\' or my '; 150 | $context['versioncheck'] .= 'Moodle dot org profile. '; 151 | $context['versioncheck'] .= 'This is my \'Web profile\' if you want '; 152 | $context['versioncheck'] .= 'to know more about me.'; 153 | } 154 | 155 | return $OUTPUT->render_from_template('format_grid/grid_admin_setting_information', $context); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /classes/admin_setting_markdown.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2022 G J Barnard in respect to modifications of standard topics format. 22 | * @author G J Barnard - {@link https://about.me/gjbarnard} and 23 | * {@link https://moodle.org/user/profile.php?id=442195} 24 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | namespace format_grid; 28 | 29 | use admin_setting; 30 | use stdClass; 31 | 32 | /** 33 | * Setting that displays markdown files. Based on admin_setting_description in adminlib.php. 34 | * 35 | * @copyright © 2022-onwards G J Barnard. 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 37 | */ 38 | class admin_setting_markdown extends admin_setting { 39 | /** @var string Filename */ 40 | private $filename; 41 | 42 | /** 43 | * Not a setting, just markup. 44 | * 45 | * @param string $name Setting name. 46 | * @param string $visiblename Setting name on the device. 47 | * @param string $description Setting description on the device. 48 | * @param string $filename The file to show. 49 | */ 50 | public function __construct($name, $visiblename, $description, $filename) { 51 | $this->nosave = true; 52 | $this->filename = $filename; 53 | parent::__construct($name, $visiblename, $description, ''); 54 | } 55 | 56 | /** 57 | * Always returns true. 58 | * 59 | * @return bool Always returns true. 60 | */ 61 | public function get_setting() { 62 | return true; 63 | } 64 | 65 | /** 66 | * Always returns true. 67 | * 68 | * @return bool Always returns true. 69 | */ 70 | public function get_defaultsetting() { 71 | return true; 72 | } 73 | 74 | /** 75 | * Never write settings 76 | * 77 | * @param mixed $data Gets converted to str for comparison against yes value. 78 | * @return string Always returns an empty string. 79 | */ 80 | public function write_setting($data) { 81 | // Do not write any setting. 82 | return ''; 83 | } 84 | 85 | /** 86 | * Returns an HTML string 87 | * 88 | * @param string $data 89 | * @param string $query 90 | * @return string Returns an HTML string 91 | */ 92 | public function output_html($data, $query = '') { 93 | global $CFG, $OUTPUT; 94 | 95 | $context = new stdClass(); 96 | $context->title = $this->visiblename; 97 | $context->description = $this->description; 98 | 99 | if (file_exists("{$CFG->dirroot}/course/format/grid/{$this->filename}")) { 100 | $filecontents = file_get_contents($CFG->dirroot . '/course/format/grid/' . $this->filename); 101 | } else { 102 | $filecontents = 'Grid format admin_setting_markdown -> file not found: ' . $this->filename; 103 | } 104 | $context->markdown = format_text($filecontents, FORMAT_MARKDOWN); 105 | 106 | return $OUTPUT->render_from_template('format_grid/grid_admin_setting_markdown', $context); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /classes/observer.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2017-onwards G J Barnard based upon work done by Marina Glancy. 22 | * @author G J Barnard - {@link https://about.me/gjbarnard} and 23 | * {@link https://moodle.org/user/profile.php?id=442195} 24 | * @author Based on code originally written by Paul Krix and Julian Ridden. 25 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | namespace format_grid; 29 | 30 | /** 31 | * Event observers supported by this format. 32 | */ 33 | class observer { 34 | /** 35 | * Observer for the event course_content_deleted. 36 | * 37 | * Deletes the settings entry for the given course upon course deletion. 38 | * 39 | * @param \core\event\course_content_deleted $event 40 | */ 41 | public static function course_content_deleted(\core\event\course_content_deleted $event) { 42 | if (class_exists('format_grid', false)) { 43 | // If class format_grid was never loaded, this is definitely not a course in 'Grid' format. 44 | self::delete_images($event->objectid); 45 | } 46 | } 47 | 48 | /** 49 | * Observer for the event course_restored. 50 | * 51 | * Deletes the settings entry for the given course upon course restoration. 52 | * 53 | * @param \core\event\course_restored $event 54 | */ 55 | public static function course_restored(\core\event\course_restored $event) { 56 | global $DB; 57 | $format = $DB->get_field('course', 'format', ['id' => $event->objectid]); 58 | // If not in the grid format, then don't need the images etc. 59 | if ($format != 'grid') { 60 | // Then delete the images. 61 | self::delete_images($event->objectid); 62 | } 63 | } 64 | 65 | /** 66 | * Deletes the images for the given course. 67 | * 68 | * @param int $courseid Course id. 69 | */ 70 | protected static function delete_images($courseid) { 71 | // Delete any images associated with the course. 72 | toolbox::delete_images($courseid); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /classes/output/courseformat/content/delegatedsection.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * Contains the Grid format delegated section course format output class. 20 | * 21 | * @package format_grid 22 | * @copyright 2024 Mikel Martín 23 | * @copyright © 2024-onwards G J Barnard. 24 | * @author G J Barnard - gjbarnard at gmail dot com and {@link http://moodle.org/user/profile.php?id=442195} 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | namespace format_grid\output\courseformat\content; 29 | 30 | use core_courseformat\base as course_format; 31 | use core_courseformat\output\local\content\delegatedsection as delegatedsection_base; 32 | use section_info; 33 | 34 | /** 35 | * Base class to render a delegated section. 36 | * 37 | * @package core_courseformat 38 | * @copyright 2024 Mikel Martín 39 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 | */ 41 | class delegatedsection extends delegatedsection_base { 42 | /** 43 | * Constructor. 44 | * 45 | * @param course_format $format the course format 46 | * @param section_info $section the section info 47 | */ 48 | public function __construct(course_format $format, section_info $section) { 49 | parent::__construct($format, $section); 50 | $this->isstealth = false; 51 | } 52 | 53 | /** 54 | * Get the name of the template to use for this templatable. 55 | * 56 | * @param renderer_base $renderer The renderer requesting the template name. 57 | * @return string. 58 | */ 59 | public function get_template_name(\renderer_base $renderer): string { 60 | return 'format_grid/local/content/delegatedsection'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /classes/output/courseformat/content/section.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Contains the default section controls output class. 19 | * 20 | * @package format_grid 21 | * @copyright 2020 Ferran Recio 22 | * @copyright © 2023-onwards G J Barnard based upon work done by Ferran Recio. 23 | * @author G J Barnard - {@link https://gjbarnard.co.uk} and 24 | * {@link http://moodle.org/user/profile.php?id=442195} 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | namespace format_grid\output\courseformat\content; 29 | 30 | use core_courseformat\base as course_format; 31 | use core_courseformat\output\local\content\section as section_base; 32 | use core\output\renderer_base; 33 | use stdClass; 34 | 35 | /** 36 | * Class to render a course section. 37 | * 38 | * @package format_grid 39 | * @copyright 2020 Ferran Recio 40 | * @copyright © 2023-onwards G J Barnard based upon work done by Ferran Recio. 41 | * @author G J Barnard - {@link https://gjbarnard.co.uk} and 42 | * {@link http://moodle.org/user/profile.php?id=442195} 43 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 44 | */ 45 | class section extends section_base { 46 | /** @var course_format the course format */ 47 | protected $format; 48 | 49 | /** 50 | * Export this data so it can be used as the context for a mustache template. 51 | * 52 | * @param renderer_base $output typically, the renderer that's calling this function. 53 | * @return array data context for a mustache template. 54 | */ 55 | public function export_for_template(renderer_base $output): stdClass { 56 | $format = $this->format; 57 | 58 | $data = parent::export_for_template($output); 59 | 60 | if (!$this->format->get_sectionnum()) { 61 | $addsectionclass = $format->get_output_classname('content\\addsection'); 62 | $addsection = new $addsectionclass($format); 63 | $data->numsections = $addsection->export_for_template($output); 64 | $data->insertafter = true; 65 | } 66 | 67 | return $data; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /classes/output/courseformat/content/section/cmsummary.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * Contains the default section controls output class. 20 | * 21 | * @package format_grid 22 | * @copyright © 2025 G J Barnard in respect to modifications of standard topics format. 23 | * @author G J Barnard - {@link http://about.me/gjbarnard} and 24 | * {@link http://moodle.org/user/profile.php?id=442195} 25 | * @copyright 2020 Ferran Recio 26 | * @author Based on code originally written by Paul Krix and Julian Ridden. 27 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 | */ 29 | 30 | namespace format_grid\output\courseformat\content\section; 31 | 32 | use core_courseformat\output\local\content\section\cmsummary as cmsummary_base; 33 | use completion_info; 34 | use stdClass; 35 | 36 | /** 37 | * Base class to render a course section summary. 38 | * 39 | * @package core_courseformat 40 | * @copyright 2020 Ferran Recio 41 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 | */ 43 | class cmsummary extends cmsummary_base { 44 | /** 45 | * Export this data so it can be used as the context for a mustache template. 46 | * 47 | * @param renderer_base $output typically, the renderer that's calling this function 48 | * @return array data context for a mustache template 49 | */ 50 | public function export_for_template(\renderer_base $output): stdClass { 51 | global $PAGE; 52 | 53 | $notediting = !$PAGE->user_is_editing(); 54 | $singlesectionid = $this->format->get_sectionid(); 55 | $data = new stdClass; 56 | if (($singlesectionid) && ($notediting)) { 57 | $showcompletion = false; 58 | $coursesettings = $this->format->get_settings(); 59 | $sectionformatoptions = $this->format->get_format_options($this->section); 60 | if (((!empty($coursesettings['showcompletion'])) && ($coursesettings['showcompletion'] == 2)) && 61 | ((!empty($sectionformatoptions['showsectioncompletion'])) && ($sectionformatoptions['showsectioncompletion'] == 2))) { 62 | $showcompletion = true; 63 | } 64 | 65 | // Only calculate on a single section page when not editing. Many section page already has alternate code. 66 | list($mods, $complete, $total, $showcompletion) = $this->calculate_section_stats($showcompletion); 67 | 68 | $totalactivities = array_reduce($mods, fn($carry, $item) => $carry + ($item["count"] ?? 0), 0); 69 | $data = (object)[ 70 | 'showcompletion' => $showcompletion, 71 | 'total' => $total, 72 | 'complete' => $complete, 73 | 'mods' => array_values($mods), 74 | 'totalactivities' => $totalactivities, 75 | ]; 76 | 77 | $contentclass = $this->format->get_output_classname('content'); 78 | $widget = new $contentclass($this->format); 79 | $data->modprogress = $widget->render_grid_completion($complete, $total, $output); 80 | } 81 | 82 | return $data; 83 | } 84 | 85 | /** 86 | * Calculate the activities count of the current section. 87 | * 88 | * @param int $showcompletion Do we want to determine if completion is to be shown? 89 | * @return array with [[count by activity type], completed activities, total of activitites] 90 | */ 91 | private function calculate_section_stats($showcompletion): array { 92 | $format = $this->format; 93 | $course = $format->get_course(); 94 | $section = $this->section; 95 | $modinfo = $format->get_modinfo(); 96 | $completioninfo = new completion_info($course); 97 | 98 | $mods = []; 99 | $total = 0; 100 | $complete = 0; 101 | 102 | $cmids = $modinfo->sections[$section->section] ?? []; 103 | 104 | // We determine if completion can be shown if indeed we want it to be so. 105 | $cancomplete = isloggedin() && !isguestuser() && $showcompletion; 106 | $showcompletion = false; 107 | foreach ($cmids as $cmid) { 108 | $thismod = $modinfo->cms[$cmid]; 109 | 110 | if ($thismod->uservisible) { 111 | if (isset($mods[$thismod->modname])) { 112 | $mods[$thismod->modname]['name'] = $thismod->modplural; 113 | $mods[$thismod->modname]['count']++; 114 | } else { 115 | $mods[$thismod->modname]['name'] = $thismod->modfullname; 116 | $mods[$thismod->modname]['count'] = 1; 117 | } 118 | if ($cancomplete && $completioninfo->is_enabled($thismod) != COMPLETION_TRACKING_NONE) { 119 | $showcompletion = true; 120 | $total++; 121 | $completiondata = $completioninfo->get_data($thismod, true); 122 | if ($completiondata->completionstate == COMPLETION_COMPLETE || 123 | $completiondata->completionstate == COMPLETION_COMPLETE_PASS) { 124 | $complete++; 125 | } 126 | } 127 | } 128 | } 129 | 130 | return [$mods, $complete, $total, $showcompletion]; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /classes/output/courseformat/content/section/controlmenu.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * Contains the default section controls output class. 20 | * 21 | * @package format_grid 22 | * @copyright © 2022 G J Barnard in respect to modifications of standard topics format. 23 | * @author G J Barnard - {@link http://about.me/gjbarnard} and 24 | * {@link http://moodle.org/user/profile.php?id=442195} 25 | * @copyright 2020 Ferran Recio 26 | * @author Based on code originally written by Paul Krix and Julian Ridden. 27 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 | */ 29 | 30 | namespace format_grid\output\courseformat\content\section; 31 | 32 | use context_course; 33 | use core_courseformat\output\local\content\section\controlmenu as controlmenu_base; 34 | 35 | /** 36 | * Base class to render a course section menu. 37 | * 38 | * @package format_grid 39 | * @copyright 2020 Ferran Recio 40 | * @copyright © 2022 G J Barnard in respect to modifications of standard topics format. 41 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 | */ 43 | class controlmenu extends controlmenu_base { 44 | /** @var course_format the course format class */ 45 | protected $format; 46 | 47 | /** @var section_info the course section class */ 48 | protected $section; 49 | 50 | /** 51 | * Generate the edit control items of a section. 52 | * 53 | * This method must remain public until the final deprecation of section_edit_control_items. 54 | * 55 | * @return array of edit control items 56 | */ 57 | public function section_control_items() { 58 | 59 | $format = $this->format; 60 | $section = $this->section; 61 | $course = $format->get_course(); 62 | $sectionreturn = $format->get_sectionnum(); 63 | 64 | $coursecontext = context_course::instance($course->id); 65 | 66 | if ($sectionreturn) { 67 | $url = course_get_url($course, $section->section); 68 | } else { 69 | $url = course_get_url($course); 70 | } 71 | $url->param('sesskey', sesskey()); 72 | 73 | $controls = []; 74 | if ($section->section && has_capability('moodle/course:setcurrentsection', $coursecontext)) { 75 | if ($course->marker == $section->section) { // Show the "light globe" on/off. 76 | $url->param('marker', 0); 77 | $highlightoff = get_string('highlightoff'); 78 | $controls['highlight'] = [ 79 | 'url' => $url, 80 | 'icon' => 'i/marked', 81 | 'name' => $highlightoff, 82 | 'pixattr' => ['class' => ''], 83 | 'attr' => [ 84 | 'class' => 'editing_highlight', 85 | 'data-action' => 'removemarker', 86 | ], 87 | ]; 88 | } else { 89 | $url->param('marker', $section->section); 90 | $highlight = get_string('highlight'); 91 | $controls['highlight'] = [ 92 | 'url' => $url, 93 | 'icon' => 'i/marker', 94 | 'name' => $highlight, 95 | 'pixattr' => ['class' => ''], 96 | 'attr' => [ 97 | 'class' => 'editing_highlight', 98 | 'data-action' => 'setmarker', 99 | ], 100 | ]; 101 | } 102 | } 103 | 104 | $parentcontrols = parent::section_control_items(); 105 | 106 | // If the edit key exists, we are going to insert our controls after it. 107 | if (array_key_exists("edit", $parentcontrols)) { 108 | $merged = []; 109 | // We can't use splice because we are using associative arrays. 110 | // Step through the array and merge the arrays. 111 | foreach ($parentcontrols as $key => $action) { 112 | $merged[$key] = $action; 113 | if ($key == "edit") { 114 | // If we have come to the edit key, merge these controls here. 115 | $merged = array_merge($merged, $controls); 116 | } 117 | } 118 | 119 | return $merged; 120 | } else { 121 | return array_merge($controls, $parentcontrols); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /classes/output/courseformat/content/section/summary.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * Contains the default section summary (used for multipage format). 20 | * 21 | * @package format_grid 22 | * @copyright © 2022 G J Barnard in respect to modifications of standard topics format. 23 | * @author G J Barnard - {@link http://about.me/gjbarnard} and 24 | * {@link http://moodle.org/user/profile.php?id=442195} 25 | * @copyright 2020 Ferran Recio 26 | * @author Based on code originally written by Paul Krix and Julian Ridden. 27 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 | */ 29 | 30 | namespace format_grid\output\courseformat\content\section; 31 | 32 | use core_courseformat\output\local\content\section\summary as summary_base; 33 | use core_courseformat\base as course_format; 34 | use context_course; 35 | use core\output\renderer_base; 36 | use format_grid\toolbox; 37 | use section_info; 38 | use stdClass; 39 | 40 | /** 41 | * Base class to render a course section summary. 42 | * 43 | * @package core_courseformat 44 | * @copyright 2020 Ferran Recio 45 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 46 | */ 47 | class summary extends summary_base { 48 | /** @var section_info the course section class - core is 'private' */ 49 | private $thesection; 50 | 51 | /** 52 | * Constructor. 53 | * 54 | * @param course_format $format the course format 55 | * @param section_info $section the section info 56 | */ 57 | public function __construct(course_format $format, section_info $section) { 58 | parent::__construct($format, $section); 59 | $this->thesection = $section; 60 | } 61 | 62 | /** 63 | * Export this data so it can be used as the context for a mustache template. 64 | * 65 | * @param renderer_base $output typically, the renderer that's calling this function. 66 | * @return array data context for a mustache template. 67 | */ 68 | public function export_for_template(renderer_base $output): stdClass { 69 | 70 | $section = $this->thesection; 71 | 72 | $data = new stdClass(); 73 | 74 | if ($section->uservisible || $section->visible) { 75 | $summary = $this->format_summary_text(); 76 | $data->summarytext = $this->singlepagesummaryimage($summary, $output); 77 | } 78 | return $data; 79 | } 80 | 81 | /** 82 | * Generate html for a section summary image 83 | * @param string $summary The summary text if any. 84 | * @param object $output The output renderer. 85 | * 86 | * @return string HTML to output. 87 | */ 88 | protected function singlepagesummaryimage($summary, $output): string { 89 | global $DB; 90 | $o = ''; 91 | 92 | if (!empty($summary)) { 93 | $o = $summary; 94 | $coursesettings = $this->format->get_settings(); 95 | if ($coursesettings['singlepagesummaryimage'] > 1) { // I.e. not 'off'. 96 | $data = new stdClass(); 97 | switch ($coursesettings['singlepagesummaryimage']) { 98 | case 2: 99 | $data->left = true; 100 | break; 101 | case 3: 102 | $data->centre = true; 103 | break; 104 | case 4: 105 | $data->right = true; 106 | break; 107 | default: 108 | $data->left = true; 109 | } 110 | 111 | $courseid = $this->thesection->course; 112 | $sectionid = $this->thesection->id; 113 | $coursesectionimage = $DB->get_record( 114 | 'format_grid_image', 115 | ['courseid' => $courseid, 'sectionid' => $sectionid] 116 | ); 117 | if (!empty($coursesectionimage)) { 118 | $fs = get_file_storage(); 119 | $coursecontext = context_course::instance($courseid); 120 | $toolbox = toolbox::get_instance(); 121 | $replacement = $toolbox->check_displayed_image( 122 | $coursesectionimage, 123 | $courseid, 124 | $coursecontext->id, 125 | $sectionid, 126 | $this->format, 127 | $fs 128 | ); 129 | if (!empty($replacement)) { 130 | $coursesectionimage = $replacement; 131 | } 132 | 133 | if ($coursesectionimage->displayedimagestate >= 1) { 134 | // Yes. 135 | $displayediswebp = (get_config('format_grid', 'defaultdisplayedimagefiletype') == 2); 136 | $data->imageuri = $toolbox->get_displayed_image_uri( 137 | $coursesectionimage, 138 | $coursecontext->id, 139 | $sectionid, 140 | $displayediswebp 141 | ); 142 | $sectionformatoptions = $this->format->get_format_options($this->thesection); 143 | $data->alttext = $sectionformatoptions['sectionimagealttext']; 144 | 145 | $data->summary = $summary; 146 | 147 | $o = $output->render_from_template('format_grid/singlepagesummaryimage', $data); 148 | } 149 | } 150 | } 151 | } 152 | 153 | return $o; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /classes/output/courseformat/content/sectionnavigation.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2023-onwards G J Barnard. 22 | * @author G J Barnard - gjbarnard at gmail dot com and {@link http://moodle.org/user/profile.php?id=442195} 23 | * @copyright 2020 Ferran Recio 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | namespace format_grid\output\courseformat\content; 28 | 29 | use core\output\renderer_base; 30 | use stdClass; 31 | 32 | /** 33 | * Base class to render a course add section navigation. 34 | */ 35 | class sectionnavigation extends \core_courseformat\output\local\content\sectionnavigation { 36 | /** @var stdClass the calculated data to prevent calculations when rendered several times */ 37 | protected $data = null; 38 | 39 | /** 40 | * Export this data so it can be used as the context for a mustache template. 41 | * 42 | * @param renderer_base $output typically, the renderer that's calling this function 43 | * @return stdClass data context for a mustache template 44 | */ 45 | public function export_for_template(renderer_base $output): stdClass { 46 | global $USER; 47 | 48 | if ($this->data !== null) { 49 | return $this->data; 50 | } 51 | 52 | $format = $this->format; 53 | $course = $format->get_course(); 54 | $context = \context_course::instance($course->id); 55 | 56 | $modinfo = $this->format->get_modinfo(); 57 | $sections = $modinfo->get_section_info_all(); 58 | 59 | // FIXME: This is really evil and should by using the navigation API. 60 | $canviewhidden = has_capability('moodle/course:viewhiddensections', $context, $USER); 61 | 62 | $data = (object)[ 63 | 'previousurl' => '', 64 | 'nexturl' => '', 65 | 'larrow' => $output->larrow(), 66 | 'rarrow' => $output->rarrow(), 67 | 'currentsection' => $this->sectionno, 68 | ]; 69 | 70 | $back = $this->sectionno - 1; 71 | while ($back >= 0 && empty($data->previousurl)) { 72 | if ($canviewhidden || $format->is_section_visible($sections[$back])) { 73 | if (!$sections[$back]->visible) { 74 | $data->previoushidden = true; 75 | } 76 | $data->previousname = get_section_name($course, $sections[$back]); 77 | $data->previousurl = course_get_url($course, $back, ['navigation' => true]); 78 | $data->hasprevious = true; 79 | } 80 | $back--; 81 | } 82 | 83 | $forward = $this->sectionno + 1; 84 | $numsections = course_get_format($course)->get_last_section_number_without_deligated(); 85 | while ($forward <= $numsections && empty($data->nexturl)) { 86 | if ($canviewhidden || $format->is_section_visible($sections[$forward])) { 87 | if (!$sections[$forward]->visible) { 88 | $data->nexthidden = true; 89 | } 90 | $data->nextname = get_section_name($course, $sections[$forward]); 91 | $data->nexturl = course_get_url($course, $forward, ['navigation' => true]); 92 | $data->hasnext = true; 93 | } 94 | $forward++; 95 | } 96 | 97 | $data->rtl = right_to_left(); 98 | $this->data = $data; 99 | return $data; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /classes/output/courseformat/content/sectionselector.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2023-onwards G J Barnard. 22 | * @author G J Barnard - gjbarnard at gmail dot com and {@link http://moodle.org/user/profile.php?id=442195} 23 | * @copyright 2020 Ferran Recio 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | namespace format_grid\output\courseformat\content; 28 | 29 | use core\output\renderer_base; 30 | use core\output\url_select; 31 | use stdClass; 32 | 33 | /** 34 | * Represents the section selector. 35 | */ 36 | class sectionselector extends \core_courseformat\output\local\content\sectionselector { 37 | /** 38 | * Get the name of the template to use for this templatable. 39 | * 40 | * @param \renderer_base $renderer The renderer requesting the template name 41 | * @return string 42 | */ 43 | public function get_template_name(renderer_base $renderer): string { 44 | return 'format_grid/local/content/sectionselector'; 45 | } 46 | 47 | /** 48 | * Export this data so it can be used as the context for a mustache template. 49 | * 50 | * @param renderer_base $output typically, the renderer that's calling this function 51 | * @return stdClass data context for a mustache template 52 | */ 53 | public function export_for_template(renderer_base $output): stdClass { 54 | 55 | $format = $this->format; 56 | $course = $format->get_course(); 57 | 58 | $modinfo = $this->format->get_modinfo(); 59 | 60 | $data = $this->navigation->export_for_template($output); 61 | 62 | // Add the section selector. 63 | $sectionmenu = []; 64 | $sectionmenu[course_get_url($course)->out(false)] = get_string('maincoursepage'); 65 | $section = 0; 66 | $numsections = $format->get_last_section_number_without_deligated(); 67 | while ($section <= $numsections) { 68 | if ($section != $data->currentsection) { 69 | $thissection = $modinfo->get_section_info($section); 70 | if ($format->is_section_visible($thissection)) { 71 | $url = course_get_url($course, $section, ['navigation' => true]); 72 | if ($url) { 73 | $sectionmenu[$url->out(false)] = get_section_name($course, $section); 74 | } 75 | } 76 | } 77 | $section++; 78 | } 79 | 80 | $select = new url_select($sectionmenu, '', ['' => get_string('jumpto')]); 81 | $select->class = 'jumpmenu'; 82 | $select->formid = 'sectionmenu'; 83 | 84 | $data->selector = $output->render($select); 85 | return $data; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /classes/output/courseformat/state/course.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright 2021 Ferran Recio 22 | * @copyright © 2024-onwards G J Barnard. 23 | * @author G J Barnard - gjbarnard at gmail dot com and {@link http://moodle.org/user/profile.php?id=442195} 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | namespace format_grid\output\courseformat\state; 28 | 29 | use core_courseformat\output\local\state\course as course_base; 30 | use core_courseformat\base as course_format; 31 | use course_modinfo; 32 | use core\url; 33 | use stdClass; 34 | 35 | /** 36 | * Contains the ajax update course structure. 37 | * 38 | * @package core_course 39 | * @copyright 2021 Ferran Recio 40 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 | */ 42 | class course extends course_base { 43 | 44 | /** 45 | * Export this data so it can be used as state object in the course editor. 46 | * 47 | * @param renderer_base $output typically, the renderer that's calling this function 48 | * @return stdClass data context for a mustache template 49 | */ 50 | public function export_for_template(\renderer_base $output): stdClass { 51 | global $CFG; 52 | 53 | $format = $this->format; 54 | $course = $format->get_course(); 55 | $context = $format->get_context(); 56 | // State must represent always the most updated version of the course. 57 | $modinfo = course_modinfo::instance($course); 58 | 59 | $url = new url('/course/view.php', ['id' => $course->id]); 60 | $maxbytes = get_user_max_upload_file_size($context, $CFG->maxbytes, $course->maxbytes); 61 | 62 | $data = (object)[ 63 | 'id' => $course->id, 64 | 'numsections' => $format->get_last_section_number(), 65 | 'numsectionswithoutdeligated' => $format->get_last_section_number_without_deligated(), 66 | 'sectionlist' => [], 67 | 'editmode' => $format->show_editor(), 68 | 'highlighted' => $format->get_section_highlighted_name(), 69 | 'maxsections' => $format->get_max_sections(), 70 | 'maxsectionswithoutdeligated' => $format->get_max_sections_without_deligated(), 71 | 'baseurl' => $url->out(), 72 | 'statekey' => course_format::session_cache($course), 73 | 'maxbytes' => $maxbytes, 74 | 'maxbytestext' => display_size($maxbytes), 75 | ]; 76 | 77 | $sections = $modinfo->get_section_info_all(); 78 | foreach ($sections as $section) { 79 | if ($format->is_section_visible($section)) { 80 | $data->sectionlist[] = $section->id; 81 | } 82 | } 83 | 84 | return $data; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /classes/output/renderer.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2012 G J Barnard in respect to modifications of standard topics format. 22 | * @author G J Barnard - {@link http://about.me/gjbarnard} and 23 | * {@link http://moodle.org/user/profile.php?id=442195} 24 | * @author Based on code originally written by Paul Krix and Julian Ridden. 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | namespace format_grid\output; 29 | 30 | use core_courseformat\output\section_renderer; 31 | use moodle_page; 32 | 33 | /** 34 | * Basic renderer for grid format. 35 | * 36 | * @copyright 2012 Dan Poltawski 37 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 | */ 39 | class renderer extends section_renderer { 40 | /** 41 | * Constructor method, calls the parent constructor. 42 | * 43 | * @param moodle_page $page 44 | * @param string $target one of rendering target constants 45 | */ 46 | public function __construct(moodle_page $page, $target) { 47 | parent::__construct($page, $target); 48 | 49 | // Since format_topics_renderer::section_edit_control_items() only displays the 'Highlight' control 50 | // when editing mode is on we need to be sure that the link 'Turn editing mode on' is available for a user 51 | // who does not have any other managing capability. 52 | $page->set_other_editing_capability('moodle/course:setcurrentsection'); 53 | } 54 | 55 | /** 56 | * Generate the section title, wraps it in a link to the section page if page is to be displayed on a separate page. 57 | * 58 | * @param section_info|stdClass $section The course_section entry from DB 59 | * @param stdClass $course The course entry from DB 60 | * @return string HTML to output. 61 | */ 62 | public function section_title($section, $course) { 63 | return $this->render(course_get_format($course)->inplace_editable_render_section_name($section)); 64 | } 65 | 66 | /** 67 | * Generate the section title to be displayed on the section page, without a link. 68 | * 69 | * @param section_info|stdClass $section The course_section entry from DB 70 | * @param int|stdClass $course The course entry from DB 71 | * @return string HTML to output. 72 | */ 73 | public function section_title_without_link($section, $course) { 74 | return $this->render(course_get_format($course)->inplace_editable_render_section_name($section, false)); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /classes/privacy/provider.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @category privacy 22 | * @copyright © 2018-onwards G J Barnard based upon work done by Marina Glancy. 23 | * @author G J Barnard - {@link http://about.me/gjbarnard} and 24 | * {@link http://moodle.org/user/profile.php?id=442195} 25 | * @author Based on code originally written by Paul Krix and Julian Ridden. 26 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 | */ 28 | 29 | namespace format_grid\privacy; 30 | 31 | /** 32 | * The Grid format does not store any user data. 33 | */ 34 | class provider implements \core_privacy\local\metadata\null_provider { 35 | /** 36 | * Get the language string identifier with the component's language 37 | * file to explain why this plugin stores no data. 38 | * 39 | * @return string 40 | */ 41 | public static function get_reason(): string { 42 | return 'privacy:nop'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /classes/task/update_displayed_images_adhoc.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid update displayed image adhoc task. 19 | * 20 | * @package format_grid 21 | * @copyright 2024 G J Barnard. 22 | * @author G J Barnard - 23 | * {@link https://moodle.org/user/profile.php?id=442195} 24 | * {@link https://gjbarnard.co.uk} 25 | * @author Based on code originally written by Paul Krix and Julian Ridden. 26 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 27 | */ 28 | namespace format_grid\task; 29 | 30 | use core\output\progress_trace\text_progress_trace; 31 | use format_grid\toolbox; 32 | 33 | /** 34 | * Grid update displayed image adhoc task class. 35 | * 36 | * @package format_grid 37 | * @copyright 2024 G J Barnard. 38 | * @author G J Barnard - 39 | * {@link https://moodle.org/user/profile.php?id=442195} 40 | * {@link https://gjbarnard.co.uk} 41 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 42 | */ 43 | class update_displayed_images_adhoc extends \core\task\adhoc_task { 44 | /** 45 | * Run the task. 46 | */ 47 | public function execute() { 48 | $trace = new text_progress_trace(); 49 | $courseid = $this->get_custom_data(); 50 | self::do_update_displayed_images_task($trace, $courseid); 51 | } 52 | 53 | /** 54 | * Do the task. 55 | * 56 | * @param progress_trace $trace The trace object. 57 | * @param $courseid Course id. 58 | */ 59 | protected static function do_update_displayed_images_task(\progress_trace $trace, $courseid) { 60 | $trace->output('Executing Grid update displayed images adhoc task on course id ' . $courseid . '.'); 61 | toolbox::update_displayed_images($courseid); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /classes/task/update_displayed_images_task.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Update displayed images task. 19 | * 20 | * @package format_grid 21 | * @copyright 2024 G J Barnard. 22 | * @author G J Barnard - 23 | * {@link https://moodle.org/user/profile.php?id=442195} 24 | * {@link https://gjbarnard.co.uk} 25 | * @author Based on code originally written by Paul Krix and Julian Ridden. 26 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 27 | */ 28 | namespace format_grid\task; 29 | 30 | use core_courseformat\base; 31 | use core\task\manager; 32 | 33 | /** 34 | * Grid update displayed images task. 35 | * 36 | * @package format_grid 37 | * @copyright 2024 G J Barnard. 38 | * @author G J Barnard - 39 | * {@link https://moodle.org/user/profile.php?id=442195} 40 | * {@link https://gjbarnard.co.uk} 41 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 42 | */ 43 | class update_displayed_images_task { 44 | /** 45 | * Queue the tasks for each grid format course. 46 | */ 47 | public static function update_displayed_images_imageresizemethod() { 48 | global $DB; 49 | 50 | $gridcourses = $DB->get_records('course', ['format' => 'grid'], '', 'id'); 51 | 52 | foreach ($gridcourses as $gridcourse) { 53 | // Instead of course_get_format() for CLI usage. 54 | $format = base::instance($gridcourse->id); 55 | $imageresizemethod = $format->get_format_options()['imageresizemethod']; 56 | if ($imageresizemethod != 0) { 57 | $task = new update_displayed_images_adhoc(); 58 | $task->set_custom_data($gridcourse->id); 59 | manager::queue_adhoc_task($task); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /db/events.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2017-onwards G J Barnard based upon work done by Marina Glancy. 22 | * @author G J Barnard - {@link http://about.me/gjbarnard} and 23 | * {@link http://moodle.org/user/profile.php?id=442195} 24 | * @author Based on code originally written by Paul Krix and Julian Ridden. 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | // List of observers. 31 | $observers = [ 32 | [ 33 | 'eventname' => '\core\event\course_content_deleted', 34 | 'callback' => '\format_grid\observer::course_content_deleted', 35 | ], 36 | [ 37 | 'eventname' => '\core\event\course_restored', 38 | 'callback' => '\format_grid\observer::course_restored', 39 | ], 40 | ]; 41 | -------------------------------------------------------------------------------- /db/install.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /form/sectionfilemanager.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2022-onwards G J Barnard. 22 | * @author G J Barnard - gjbarnard at gmail dot com and {@link http://moodle.org/user/profile.php?id=442195} 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | // phpcs:disable moodle.NamingConventions.ValidVariableName.VariableNameLowerCase 27 | // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod 28 | 29 | defined('MOODLE_INTERNAL') || die(); 30 | 31 | global $CFG; 32 | 33 | require_once($CFG->dirroot . "/lib/form/filemanager.php"); 34 | 35 | /** 36 | * Section file manager form. 37 | */ 38 | class MoodleQuickForm_sectionfilemanager extends MoodleQuickForm_filemanager implements templatable { 39 | /** @var array $options The options */ 40 | private static $options = [ 41 | 'maxfiles' => 1, 42 | 'subdirs' => 0, 43 | 'accepted_types' => ['gif', 'jpe', 'jpeg', 'jpg', 'png', 'webp'], 44 | 'return_types' => FILE_INTERNAL, 45 | ]; 46 | 47 | /** 48 | * Constructor 49 | * 50 | * @param string $elementName (optional) name of the filemanager 51 | * @param string $elementLabel (optional) filemanager label 52 | * @param array $attributes (optional) Either a typical HTML attribute string 53 | * or an associative array 54 | */ 55 | public function __construct($elementName=null, $elementLabel=null, $attributes=null) { 56 | parent::__construct($elementName, $elementLabel, $attributes, self::$options); 57 | } 58 | 59 | /** 60 | * Check that all files have the allowed type. 61 | * 62 | * @param int $value Draft item id with the uploaded files. 63 | * @return string|null Validation error message or null. 64 | */ 65 | public function validateSubmitValue($value) { 66 | $failure = parent::validateSubmitValue($value); 67 | if (!$failure) { 68 | $course = $this->getAttribute('course'); 69 | $coursecontext = context_course::instance($course->id); 70 | $sectionid = $this->getAttribute('sectionid'); 71 | 72 | // Only allow this code to be executed once at the same time for the given section id (the id is unique). 73 | $lock = true; 74 | if (!defined('BEHAT_SITE_RUNNING')) { 75 | $lockfactory = \core\lock\lock_config::get_lock_factory('format_grid'); 76 | $lock = $lockfactory->get_lock('sectionid' . $sectionid, 5); 77 | } 78 | if ($lock) { 79 | $fs = get_file_storage(); 80 | $indata = new stdClass(); 81 | $indata->sectionimage_filemanager = $value; 82 | // The file manager deals with the files table when the image is deleted. 83 | $outdata = file_postupdate_standard_filemanager( 84 | $indata, 85 | 'sectionimage', 86 | self::$options, 87 | $coursecontext, 88 | 'format_grid', 89 | 'sectionimage', 90 | $sectionid 91 | ); 92 | global $DB; 93 | if ($outdata->sectionimage == '1') { 94 | // We have draft file(s), however they could also be left over ones! 95 | $format = course_get_format($course); 96 | $files = $fs->get_area_files($coursecontext->id, 'format_grid', 'sectionimage', $sectionid); 97 | $sectionimage = $DB->get_record_select( 98 | 'format_grid_image', 99 | 'courseid = ? AND sectionid = ?', 100 | [$course->id, $sectionid] 101 | ); 102 | $havefiles = false; 103 | $havechangedfiles = false; 104 | foreach ($files as $file) { 105 | if (!$file->is_directory()) { 106 | $filename = $file->get_filename(); 107 | $contenthash = $file->get_contenthash(); 108 | $havefiles = true; 109 | if ($sectionimage) { 110 | if (($contenthash !== $sectionimage->contenthash) || ($filename !== $sectionimage->image)) { 111 | // Change of image. 112 | $sectionimage->image = $filename; 113 | $sectionimage->contenthash = $contenthash; 114 | $sectionimage->displayedimagestate = 0; // Not generated. 115 | try { 116 | $DB->update_record('format_grid_image', $sectionimage); 117 | } catch (\Exception $e) { 118 | if (!defined('BEHAT_SITE_RUNNING')) { 119 | $lock->release(); 120 | } 121 | throw $e; 122 | } 123 | $havechangedfiles = true; 124 | } // Else image not changed. 125 | } else { 126 | // New image. 127 | $sectionimage = new \stdClass(); 128 | $sectionimage->sectionid = $sectionid; 129 | $sectionimage->courseid = $course->id; 130 | $sectionimage->image = $filename; 131 | $sectionimage->contenthash = $contenthash; 132 | $sectionimage->displayedimagestate = 0; // Not generated. 133 | try { 134 | $sectionimage->id = $DB->insert_record('format_grid_image', $sectionimage, true); 135 | } catch (\Exception $e) { 136 | if (!defined('BEHAT_SITE_RUNNING')) { 137 | $lock->release(); 138 | } 139 | throw $e; 140 | } 141 | $havechangedfiles = true; 142 | } 143 | if ($havechangedfiles) { 144 | $toolbox = \format_grid\toolbox::get_instance(); 145 | try { 146 | $toolbox->setup_displayed_image($sectionimage, $file, $course->id, $sectionid, $format); 147 | } catch (\moodle_exception $me) { 148 | if (!defined('BEHAT_SITE_RUNNING')) { 149 | $lock->release(); 150 | } 151 | $failure = $me->getMessage(); 152 | } catch (\Exception $e) { 153 | if (!defined('BEHAT_SITE_RUNNING')) { 154 | $lock->release(); 155 | } 156 | throw $e; 157 | } 158 | } 159 | } 160 | } 161 | if (!$havefiles) { 162 | // No section files - possible deletion of existing image. 163 | $this->delete_existing_image($coursecontext->id, $course->id, $sectionid, $fs, $lock); 164 | } 165 | } else { 166 | // No draft files - possible deletion of existing image. 167 | $this->delete_existing_image($coursecontext->id, $course->id, $sectionid, $fs, $lock); 168 | } 169 | if (!defined('BEHAT_SITE_RUNNING')) { 170 | $lock->release(); 171 | } 172 | } else { 173 | throw new \moodle_exception( 174 | 'cannotgetimagelock', 175 | 'format_grid', 176 | '', 177 | get_string('cannotgetmanagesectionimagelock', 'format_grid') 178 | ); 179 | } 180 | } 181 | 182 | return $failure; 183 | } 184 | 185 | /** 186 | * Deletes the existing image if any. 187 | * 188 | * @param int $coursecontextid Course context id. 189 | * @param int $courseid Course id. 190 | * @param int $sectionid Section id. 191 | * @param file_storage $fs An instance of file_storage. 192 | * @param \core\lock\lock|boolean $lock An instance of \core\lock\lock if the lock was obtained, or true. 193 | * 194 | */ 195 | private function delete_existing_image($coursecontextid, $courseid, $sectionid, $fs, $lock) { 196 | global $DB; 197 | 198 | try { 199 | $DB->delete_records('format_grid_image', ['courseid' => $courseid, 'sectionid' => $sectionid]); 200 | } catch (\Exception $e) { 201 | if (!defined('BEHAT_SITE_RUNNING')) { 202 | $lock->release(); 203 | } 204 | throw $e; 205 | } 206 | // Remove existing displayed image. 207 | $existingfiles = $fs->get_area_files($coursecontextid, 'format_grid', 'displayedsectionimage', $sectionid); 208 | foreach ($existingfiles as $existingfile) { 209 | if (!$existingfile->is_directory()) { 210 | $existingfile->delete(); 211 | } 212 | } 213 | } 214 | 215 | /** 216 | * Returns HTML for sectionfilemanager form element. 217 | * 218 | * @return string 219 | */ 220 | public function toHtml() { 221 | $this->init(); 222 | return parent::toHtml(); 223 | } 224 | 225 | /** 226 | * Prepare the file area. 227 | */ 228 | private function init() { 229 | $course = $this->getAttribute('course'); 230 | $sectionid = $this->getAttribute('sectionid'); 231 | 232 | $coursecontext = context_course::instance($course->id); 233 | $fmd = file_prepare_standard_filemanager( 234 | $course, 235 | 'sectionimage', 236 | self::$options, 237 | $coursecontext, 238 | 'format_grid', 239 | 'sectionimage', 240 | $sectionid 241 | ); 242 | $this->setValue($fmd->sectionimage_filemanager); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /format.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2012 G J Barnard in respect to modifications of standard topics format. 22 | * @author G J Barnard - {@link https://about.me/gjbarnard} and 23 | * {@link https://moodle.org/user/profile.php?id=442195} 24 | * @copyright 2006 The Open University 25 | * @author N.D.Freear@open.ac.uk, and others. 26 | * @author Based on code originally written by Paul Krix and Julian Ridden. 27 | * 28 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 29 | */ 30 | defined('MOODLE_INTERNAL') || die(); 31 | 32 | require_once($CFG->libdir . '/filelib.php'); 33 | require_once($CFG->libdir . '/completionlib.php'); 34 | 35 | // Horrible backwards compatible parameter aliasing. 36 | if ($topic = optional_param('topic', 0, PARAM_INT)) { 37 | $url = $PAGE->url; 38 | $url->param('section', $topic); 39 | debugging('Outdated topic param passed to course/view.php', DEBUG_DEVELOPER); 40 | redirect($url); 41 | } 42 | // End backwards-compatible aliasing. 43 | 44 | // Retrieve course format option fields and add them to the $course object. 45 | $format = course_get_format($course); 46 | $course = $format->get_course(); 47 | 48 | $context = context_course::instance($course->id); 49 | 50 | if (($marker >= 0) && has_capability('moodle/course:setcurrentsection', $context) && confirm_sesskey()) { 51 | $course->marker = $marker; 52 | course_set_marker($course->id, $marker); 53 | } 54 | 55 | // Make sure section 0 is created. 56 | course_create_sections_if_missing($course, 0); 57 | 58 | $renderer = $PAGE->get_renderer('format_grid'); 59 | 60 | if (!empty($displaysection)) { 61 | $format->set_sectionnum($displaysection); 62 | } 63 | $outputclass = $format->get_output_classname('content'); 64 | $widget = new $outputclass($format); 65 | echo $renderer->render($widget); 66 | -------------------------------------------------------------------------------- /lang/en/format_grid.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2012 G J Barnard in respect to modifications of standard topics format. 22 | * @author G J Barnard - {@link https://about.me/gjbarnard} and 23 | * {@link https://moodle.org/user/profile.php?id=442195} 24 | * @author Based on code originally written by Paul Krix and Julian Ridden. 25 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | // phpcs:disable moodle.Files.LangFilesOrdering 29 | 30 | $string['topic'] = 'Section'; 31 | $string['topic0'] = 'General'; 32 | 33 | // Moodle 2.0 Enhancement - Moodle Tracker MDL-15252, MDL-21693 & MDL-22056 - http://docs.moodle.org/en/Development:Languages. 34 | $string['sectionname'] = 'Section'; 35 | $string['pluginname'] = 'Grid'; 36 | $string['plugin_description'] = 'The course is divided into sections selectable via a grid.'; 37 | $string['section0name'] = 'General'; 38 | 39 | // MDL-26105. 40 | $string['page-course-view-grid'] = 'Any course main page in the grid format'; 41 | $string['page-course-view-grid-x'] = 'Any course page in the grid format'; 42 | 43 | $string['addsections'] = 'Add section'; 44 | $string['newsection'] = 'New section'; 45 | $string['hidefromothers'] = 'Hide section'; 46 | $string['showfromothers'] = 'Show'; 47 | $string['currentsection'] = 'This section'; 48 | $string['markedthissection'] = 'This section is highlighted as the current section'; 49 | $string['markthissection'] = 'Highlight this section as the current section'; 50 | 51 | // Moodle 3.0 Enhancement. 52 | $string['editsection'] = 'Edit section'; 53 | $string['deletesection'] = 'Delete section'; 54 | 55 | // MDL-51802. 56 | $string['editsectionname'] = 'Edit section name'; 57 | $string['newsectionname'] = 'New name for section {$a}'; 58 | 59 | // Setting general. 60 | $string['default'] = 'Default - {$a}'; 61 | 62 | // Section image. 63 | $string['sectionimage'] = 'Section image'; 64 | $string['sectionimage_help'] = 'The section image'; 65 | $string['sectionimagealttext'] = 'Image alt text'; 66 | $string['sectionimagealttext_help'] = "This text will be set as the image 'alt', being 'alternative' attribute."; 67 | 68 | // Section break. 69 | $string['sectionbreak'] = 'Section break'; 70 | $string['sectionbreak_help'] = 'Break the grid at this section'; 71 | $string['sectionbreakheading'] = 'Section break heading'; 72 | $string['sectionbreakheading_help'] = 'Show this heading at the point this section breaks in the grid. HTML can be used.'; 73 | 74 | // Grid justification. 75 | $string['gridjustification'] = 'Set the justification of the grid'; 76 | $string['gridjustification_help'] = 'Set the justification to one of: Start, Centre, End, Space around, Space between or Space evenly'; 77 | $string['defaultgridjustification'] = 'Default justification of the grid'; 78 | $string['defaultgridjustification_desc'] = 'One of: Start, Centre, End, Space around, Space between or Space evenly.'; 79 | $string['start'] = 'Start'; 80 | $string['centre'] = 'Centre'; 81 | $string['end'] = 'End'; 82 | $string['spacearound'] = 'Space around'; 83 | $string['spacebetween'] = 'Space between'; 84 | $string['spaceevenly'] = 'Space evenly'; 85 | 86 | // Image container width. 87 | $string['imagecontainerwidth'] = 'Set the image container width'; 88 | $string['imagecontainerwidth_help'] = 'One of: 128, 192, 210, 256, 320, 384, 448, 512, 576, 640, 704 or 768'; 89 | $string['defaultimagecontainerwidth'] = 'Default width of the image container'; 90 | $string['defaultimagecontainerwidth_desc'] = 'One of: 128, 192, 210, 256, 320, 384, 448, 512, 576, 640, 704 or 768.'; 91 | 92 | // Image container ratio. 93 | $string['imagecontainerratio'] = 'Set the image container ratio relative to the width'; 94 | $string['imagecontainerratio_help'] = 'One of: 3-2, 3-1, 3-3, 2-3, 1-3, 4-3 or 3-4'; 95 | $string['defaultimagecontainerratio'] = 'Default ratio of the image container relative to the width'; 96 | $string['defaultimagecontainerratio_desc'] = 'One of: 3-2, 3-1, 3-3, 2-3, 1-3, 4-3 or 3-4.'; 97 | 98 | // Image resize method. 99 | $string['scale'] = 'Scale'; 100 | $string['crop'] = 'Crop'; 101 | $string['imageresizemethod'] = 'Set the image resize method'; 102 | $string['imageresizemethod_help'] = "Set to: 'Scale' or 'Crop' when resizing the image to fit the container"; 103 | $string['defaultimageresizemethod'] = 'Default image resize method'; 104 | $string['defaultimageresizemethod_desc'] = "Set to: 'Scale' or 'Crop' when resizing the image to fit the container."; 105 | 106 | // Displayed image type. 107 | $string['original'] = 'Original'; 108 | $string['webp'] = 'WebP'; 109 | $string['defaultdisplayedimagefiletype'] = 'Displayed image type'; 110 | $string['defaultdisplayedimagefiletype_desc'] = "'Original' or 'WebP'."; 111 | 112 | // Single page summary image. 113 | $string['off'] = 'Off'; 114 | $string['centre'] = 'Centre'; 115 | $string['left'] = 'Left'; 116 | $string['right'] = 'Right'; 117 | $string['singlepagesummaryimage'] = 'Show the grid image in the section summary'; 118 | $string['singlepagesummaryimage_help'] = 'When there is a summary in the section'; 119 | $string['defaultsinglepagesummaryimage'] = 'Show the grid image in the section summary'; 120 | $string['defaultsinglepagesummaryimage_desc'] = 'When there is a summary in the section.'; 121 | 122 | // Modal. 123 | $string['popup'] = 'Use a popup'; 124 | $string['popup_help'] = 'Display the section in a popup instead of navigating to a single section page'; 125 | $string['defaultpopup'] = 'Use a popup'; 126 | $string['defaultpopup_desc'] = 'Display the section in a popup instead of navigating to a single section page.'; 127 | 128 | // Section zero. 129 | $string['sectionzeroingrid'] = 'Section zero in grid'; 130 | $string['sectionzeroingrid_help'] = 'Place section zero in the grid'; 131 | $string['defaultsectionzeroingrid'] = 'Section zero in grid'; 132 | $string['defaultsectionzeroingrid_desc'] = 'Place section zero in the grid.'; 133 | 134 | // Grid section title / badges. 135 | $string['sectiontitleingridbox'] = 'Section title in grid box'; 136 | $string['sectiontitleingridbox_help'] = 'Show the section title in the grid box'; 137 | $string['defaultsectiontitleingridbox'] = 'Section title in grid box'; 138 | $string['defaultsectiontitleingridbox_desc'] = 'Show the section title in the grid box.'; 139 | $string['sectionbadgeingridbox'] = 'Section badge in grid box'; 140 | $string['sectionbadgeingridbox_help'] = 'Show the section badge in the grid box'; 141 | $string['defaultsectionbadgeingridbox'] = 'Section badge in grid box'; 142 | $string['defaultsectionbadgeingridbox_desc'] = 'Show the section badge in the grid box.'; 143 | 144 | // Completion. 145 | $string['showcompletion'] = 'Show completion'; 146 | $string['showcompletion_help'] = 'Show the completion percentage of the sections'; 147 | $string['defaultshowcompletion'] = 'Show completion'; 148 | $string['defaultshowcompletion_desc'] = 'Show the completion percentage of the sections.'; 149 | $string['showsectioncompletion'] = 'Show completion for the section'; 150 | $string['showsectioncompletion_help'] = 'This allows you to state if a given section has the completion percentage showing'; 151 | 152 | $string['defaultcompletionlowpercentagevalue'] = 'Completion low percentage value'; 153 | $string['defaultcompletionlowpercentagevalue_desc'] = 'For a completion percentage to be \'low\' then it must be less than this value.'; 154 | $string['defaultcompletionmediumpercentagevalue'] = 'Completion medium percentage value'; 155 | $string['defaultcompletionmediumpercentagevalue_desc'] = 'For a completion percentage to be \'medium\' then it must be less than this value.'; 156 | 157 | // Other. 158 | $string['information'] = 'Information'; 159 | $string['informationsettings'] = 'Information settings'; 160 | $string['informationsettingsdesc'] = 'Grid format information'; 161 | $string['informationchanges'] = 'Changes'; 162 | $string['settings'] = 'Settings'; 163 | $string['settingssettings'] = 'Settings settings'; 164 | $string['settingssettingsdesc'] = 'Grid format settings'; 165 | $string['stealthwarning'] = 'Warning: Course has {$a} orphaned section(s) with content. Resolve as soon as possible. Note: Importing into this course from another will turn orphaned sections into real ones - there is no current solution to this!'; 166 | $string['love'] = 'love'; 167 | $string['versioninfo'] = 'Release {$a->release}, version {$a->version} on Moodle {$a->moodle}. Made with {$a->love} in Great Britain.'; 168 | $string['versionalpha'] = 'Alpha version - Almost certainly contains bugs. This is a development version for developers \'only\'! Don\'t even think of installing on a production server!'; 169 | $string['versionbeta'] = 'Beta version - Likely to contain bugs. Ready for testing by administrators on a test server only.'; 170 | $string['versionrc'] = 'Release candidate version - May contain bugs. Check completely on a test server before considering on a production server.'; 171 | $string['versionstable'] = 'Stable version - Could contain bugs. Check on a test server before installing on your production server.'; 172 | 173 | // Setting class admin_setting_configinteger. 174 | $string['asconfigintlower'] = '{$a->value} is less than the lower range limit of {$a->lower}'; 175 | $string['asconfigintupper'] = '{$a->value} is greater than the upper range limit of {$a->upper}'; 176 | $string['asconfigintnan'] = '{$a->value} is not a number'; 177 | 178 | // Exception messages. 179 | $string['cannotconvertuploadedimagetodisplayedimage'] = 'Cannot convert uploaded image to displayed image - {$a}.'; 180 | $string['cannotgetmanagesectionimagelock'] = 'Cannot get manage section image lock. This can happen if two people are editing the settinsg of the same section on the same course at the same time.'; 181 | $string['formatnotsupported'] = 'Format is not supported at this server, please fix the system configuration to have the GD PHP extension installed - {$a}'; 182 | $string['functionfailed'] = 'Function failed on image - {$a}'; 183 | $string['imagemanagement'] = 'Image management: {$a}.'; 184 | $string['mimetypenotsupported'] = 'Mime type is not supported as an image format in the Grid format - {$a}'; 185 | $string['originalheightempty'] = 'Original height is empty - {$a}'; 186 | $string['originalwidthempty'] = 'Original width is empty - {$a}'; 187 | $string['noimageinformation'] = 'Image information is empty - {$a}'; 188 | $string['reporterror'] = 'Please use the error information to understand the nature of why the uploaded image cannot be used'; 189 | 190 | // Privacy. 191 | $string['privacy:nop'] = 'The Grid format stores lots of settings that pertain to its configuration. None of the settings are related to a specific user. It is your responsibilty to ensure that no user data is entered in any of the free text fields. Setting a setting will result in that action being logged within the core Moodle logging system against the user whom changed it, this is outside of the formats control, please see the core logging system for privacy compliance for this. When uploading images, you should avoid uploading images with embedded location data (EXIF GPS) included or other such personal data. It would be possible to extract any location / personal data from the images. Please examine the code carefully to be sure that it complies with your interpretation of your privacy laws. I am not a lawyer and my analysis is based on my interpretation. If you have any doubt then remove the format forthwith.'; 192 | -------------------------------------------------------------------------------- /pix/grid_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjbarnard/moodle-format_grid/36d6b02fd0f78804457a301bc5bb9a8f6f6b582d/pix/grid_logo.jpg -------------------------------------------------------------------------------- /pix/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjbarnard/moodle-format_grid/36d6b02fd0f78804457a301bc5bb9a8f6f6b582d/pix/icon.png -------------------------------------------------------------------------------- /settings.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright © 2013 G J Barnard in respect to modifications of standard topics format. 22 | * @author G J Barnard - {@link https://about.me/gjbarnard} and 23 | * {@link https://moodle.org/user/profile.php?id=442195} 24 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | defined('MOODLE_INTERNAL') || die; 28 | 29 | use format_grid\admin_setting_configinteger; 30 | use format_grid\admin_setting_information; 31 | use format_grid\admin_setting_markdown; 32 | 33 | require_once($CFG->dirroot . '/course/format/grid/lib.php'); // For format_grid static constants. 34 | 35 | $settings = null; 36 | $ADMIN->add('formatsettings', new admin_category('format_grid', get_string('pluginname', 'format_grid'))); 37 | 38 | // Information. 39 | $page = new admin_settingpage( 40 | 'format_grid_information', 41 | get_string('information', 'format_grid') 42 | ); 43 | 44 | if ($ADMIN->fulltree) { 45 | $page->add(new admin_setting_heading( 46 | 'format_grid_information', 47 | '', 48 | format_text(get_string('informationsettingsdesc', 'format_grid'), FORMAT_MARKDOWN) 49 | )); 50 | 51 | // Information. 52 | $page->add(new admin_setting_information('format_grid/formatinformation', '', '', 403)); 53 | 54 | // Support.md. 55 | $page->add(new admin_setting_markdown('format_grid/formatsupport', '', '', 'Support.md')); 56 | 57 | // Changes.md. 58 | $page->add(new admin_setting_markdown( 59 | 'format_grid/formatchanges', 60 | get_string('informationchanges', 'format_grid'), 61 | '', 62 | 'Changes.md' 63 | )); 64 | } 65 | $ADMIN->add('format_grid', $page); 66 | 67 | // Settings. 68 | $page = new admin_settingpage( 69 | 'format_grid_settings', 70 | get_string('settings', 'format_grid') 71 | ); 72 | if ($ADMIN->fulltree) { 73 | $page->add(new admin_setting_heading( 74 | 'format_grid_settings', 75 | '', 76 | format_text(get_string('settingssettingsdesc', 'format_grid'), FORMAT_MARKDOWN) 77 | )); 78 | 79 | // Popup. 80 | $name = 'format_grid/defaultpopup'; 81 | $title = get_string('defaultpopup', 'format_grid'); 82 | $description = get_string('defaultpopup_desc', 'format_grid'); 83 | $default = 1; 84 | $choices = [ 85 | 1 => new lang_string('no'), 86 | 2 => new lang_string('yes'), 87 | ]; 88 | $page->add(new admin_setting_configselect($name, $title, $description, $default, $choices)); 89 | 90 | // Justification. 91 | $name = 'format_grid/defaultgridjustification'; 92 | $title = get_string('defaultgridjustification', 'format_grid'); 93 | $description = get_string('defaultgridjustification_desc', 'format_grid'); 94 | $default = 'space-between'; 95 | $choices = [ 96 | 'start' => new lang_string('start', 'format_grid'), 97 | 'center' => new lang_string('centre', 'format_grid'), 98 | 'end' => new lang_string('end', 'format_grid'), 99 | 'space-around' => new lang_string('spacearound', 'format_grid'), 100 | 'space-between' => new lang_string('spacebetween', 'format_grid'), 101 | 'space-evenly' => new lang_string('spaceevenly', 'format_grid'), 102 | ]; 103 | $page->add(new admin_setting_configselect($name, $title, $description, $default, $choices)); 104 | 105 | // Icon width. 106 | $name = 'format_grid/defaultimagecontainerwidth'; 107 | $title = get_string('defaultimagecontainerwidth', 'format_grid'); 108 | $description = get_string('defaultimagecontainerwidth_desc', 'format_grid'); 109 | $default = \format_grid\toolbox::get_default_image_container_width(); 110 | $choices = \format_grid\toolbox::get_image_container_widths(); 111 | $setting = new admin_setting_configselect($name, $title, $description, $default, $choices); 112 | $setting->set_updatedcallback('format_grid::update_displayed_images_callback'); 113 | $page->add($setting); 114 | 115 | // Icon ratio. 116 | $name = 'format_grid/defaultimagecontainerratio'; 117 | $title = get_string('defaultimagecontainerratio', 'format_grid'); 118 | $description = get_string('defaultimagecontainerratio_desc', 'format_grid'); 119 | $default = \format_grid\toolbox::get_default_image_container_ratio(); 120 | $choices = \format_grid\toolbox::get_image_container_ratios(); 121 | $setting = new admin_setting_configselect($name, $title, $description, $default, $choices); 122 | $setting->set_updatedcallback('format_grid::update_displayed_images_callback'); 123 | $page->add($setting); 124 | 125 | // Resize method - 1 = scale, 2 = crop. 126 | $name = 'format_grid/defaultimageresizemethod'; 127 | $title = get_string('defaultimageresizemethod', 'format_grid'); 128 | $description = get_string('defaultimageresizemethod_desc', 'format_grid'); 129 | $default = 1; // Scale. 130 | $choices = [ 131 | 1 => new lang_string('scale', 'format_grid'), 132 | 2 => new lang_string('crop', 'format_grid'), 133 | ]; 134 | $setting = new admin_setting_configselect($name, $title, $description, $default, $choices); 135 | $setting->set_updatedcallback('format_grid::update_displayed_images_callback'); 136 | $page->add($setting); 137 | 138 | // Displayed image file type - 1 = original, 2 = webp. 139 | $name = 'format_grid/defaultdisplayedimagefiletype'; 140 | $title = get_string('defaultdisplayedimagefiletype', 'format_grid'); 141 | $description = get_string('defaultdisplayedimagefiletype_desc', 'format_grid'); 142 | $default = 1; // Original. 143 | $choices = [ 144 | 1 => new lang_string('original', 'format_grid'), 145 | 2 => new lang_string('webp', 'format_grid'), 146 | ]; 147 | $setting = new admin_setting_configselect($name, $title, $description, $default, $choices); 148 | $setting->set_updatedcallback('format_grid::update_displayed_images_callback'); 149 | $page->add($setting); 150 | 151 | // Section zero in grid. 152 | $name = 'format_grid/defaultsectionzeroingrid'; 153 | $title = get_string('defaultsectionzeroingrid', 'format_grid'); 154 | $description = get_string('defaultsectionzeroingrid_desc', 'format_grid'); 155 | $default = 1; 156 | $choices = [ 157 | 1 => new lang_string('no'), 158 | 2 => new lang_string('yes'), 159 | ]; 160 | $page->add(new admin_setting_configselect($name, $title, $description, $default, $choices)); 161 | 162 | // Section title in grid box. 163 | $name = 'format_grid/defaultsectiontitleingridbox'; 164 | $title = get_string('defaultsectiontitleingridbox', 'format_grid'); 165 | $description = get_string('defaultsectiontitleingridbox_desc', 'format_grid'); 166 | $default = 2; 167 | $choices = [ 168 | 1 => new lang_string('no'), 169 | 2 => new lang_string('yes'), 170 | ]; 171 | $page->add(new admin_setting_configselect($name, $title, $description, $default, $choices)); 172 | 173 | // Section badge in grid box. 174 | $name = 'format_grid/defaultsectionbadgeingridbox'; 175 | $title = get_string('defaultsectionbadgeingridbox', 'format_grid'); 176 | $description = get_string('defaultsectionbadgeingridbox_desc', 'format_grid'); 177 | $default = 2; 178 | $choices = [ 179 | 1 => new lang_string('no'), 180 | 2 => new lang_string('yes'), 181 | ]; 182 | $page->add(new admin_setting_configselect($name, $title, $description, $default, $choices)); 183 | 184 | // Completion. 185 | $name = 'format_grid/defaultshowcompletion'; 186 | $title = get_string('defaultshowcompletion', 'format_grid'); 187 | $description = get_string('defaultshowcompletion_desc', 'format_grid'); 188 | $default = 1; 189 | $choices = [ 190 | 1 => new lang_string('no'), 191 | 2 => new lang_string('yes'), 192 | ]; 193 | $page->add(new admin_setting_configselect($name, $title, $description, $default, $choices)); 194 | 195 | // Completion percentage low. 196 | $name = 'format_grid/defaultcompletionlowpercentagevalue'; 197 | $title = get_string('defaultcompletionlowpercentagevalue', 'format_grid'); 198 | $description = get_string('defaultcompletionlowpercentagevalue_desc', 'format_grid'); 199 | $default = 50; 200 | $lower = 1; 201 | $upper = 98; 202 | $page->add(new admin_setting_configinteger($name, $title, $description, $default, $lower, $upper)); 203 | 204 | // Completion percentage medium. 205 | $name = 'format_grid/defaultcompletionmediumpercentagevalue'; 206 | $title = get_string('defaultcompletionmediumpercentagevalue', 'format_grid'); 207 | $description = get_string('defaultcompletionmediumpercentagevalue_desc', 'format_grid'); 208 | $default = 80; 209 | $lower = 2; 210 | $upper = 99; 211 | $page->add(new admin_setting_configinteger($name, $title, $description, $default, $lower, $upper)); 212 | 213 | // Show the grid image in the section summary on a single page. 214 | $name = 'format_grid/defaultsinglepagesummaryimage'; 215 | $title = get_string('defaultsinglepagesummaryimage', 'format_grid'); 216 | $description = get_string('defaultsinglepagesummaryimage_desc', 'format_grid'); 217 | $default = 1; 218 | $choices = [ 219 | 1 => new lang_string('off', 'format_grid'), 220 | 2 => new lang_string('left', 'format_grid'), 221 | 3 => new lang_string('centre', 'format_grid'), 222 | 4 => new lang_string('right', 'format_grid'), 223 | ]; 224 | $page->add(new admin_setting_configselect($name, $title, $description, $default, $choices)); 225 | } 226 | $ADMIN->add('format_grid', $page); 227 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 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 | /** 17 | * Grid Format. 18 | * 19 | * @package course/format 20 | * @subpackage grid 21 | * @version See the value of '$plugin->version' in version.php. 22 | * @copyright © 2012 onwards G J Barnard in respect to modifications of standard topics format. 23 | * @author G J Barnard - {@link http://about.me/gjbarnard} and 24 | * {@link http://moodle.org/user/profile.php?id=442195} 25 | * @author Based on code originally written by Paul Krix and Julian Ridden. 26 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 | */ 28 | 29 | .course-content ul.grid { 30 | margin: 0; 31 | padding-left: 0; 32 | padding-right: 0; 33 | } 34 | 35 | .format-grid .thegrid { 36 | gap: 0.75rem; 37 | } 38 | 39 | .format-grid .grid-justify-start { 40 | justify-content: start; 41 | } 42 | 43 | .format-grid .grid-justify-center { 44 | justify-content: center; 45 | } 46 | 47 | .format-grid .grid-justify-end { 48 | justify-content: end; 49 | } 50 | 51 | .format-grid .grid-justify-space-around { 52 | justify-content: space-around; 53 | } 54 | 55 | .format-grid .grid-justify-space-between { 56 | justify-content: space-between; 57 | } 58 | 59 | .format-grid .grid-justify-space-evenly { 60 | justify-content: space-evenly; 61 | } 62 | 63 | .format-grid .thegrid .grid-section { 64 | box-sizing: content-box; 65 | width: 210px; 66 | } 67 | 68 | .format-grid .thegrid .grid-section.card { 69 | border-width: 2px; 70 | } 71 | 72 | .format-grid .thegrid .grid-section.currentgridsection { 73 | border: 2px solid var(--primary); 74 | } 75 | 76 | .format-grid .thegrid .grid-section-inner .card-body { 77 | min-height: 0; 78 | } 79 | 80 | .format-grid .thegrid .grid-image-container { 81 | height: 140px; 82 | } 83 | 84 | .format-grid .thegrid .grid-image { 85 | position: relative; 86 | } 87 | 88 | .format-grid .thegrid .grid-image.grid-scaled { 89 | height: 100%; 90 | } 91 | 92 | /*rtl:begin:ignore*/ 93 | .format-grid .thegrid .grid-image.grid-scaled img { 94 | left: 50%; 95 | position: absolute; 96 | top: 50%; 97 | transform: translate(-50%,-50.25%); 98 | } 99 | /*rtl:end:ignore*/ 100 | 101 | .format-grid .thegrid .grid-image .grid-badge-middle { 102 | bottom: 0; 103 | left: 0; 104 | position: absolute; 105 | right: 0; 106 | top: 0; 107 | } 108 | 109 | .format-grid .thegrid .grid-image .grid-badge-bottom { 110 | bottom: 1.5rem; 111 | left: 0; 112 | position: absolute; 113 | right: 0; 114 | } 115 | 116 | .format-grid .thegrid .grid-generatedimage { 117 | background-size: contain; 118 | height: 100%; 119 | position: relative; 120 | width: 100%; 121 | } 122 | 123 | .format-grid #gridPopup .modal-dialog { 124 | max-width: 100%; 125 | } 126 | 127 | @media (min-width: 576px) { 128 | .format-grid #gridPopup .modal-dialog { 129 | margin-left: 1.25rem; 130 | margin-right: 1.25rem; 131 | } 132 | } 133 | 134 | .format-grid #gridPopup .modal-dialog .modal-body { 135 | min-height: 200px; 136 | } 137 | 138 | .format-grid .grid-completion { 139 | border-color: black; 140 | border-radius: 45px; 141 | border-style: solid; 142 | border-width: 3px; 143 | bottom: 1rem; 144 | display: flex; 145 | flex-direction: column; 146 | height: 42px; 147 | justify-content: center; 148 | padding: 4px; 149 | width: 42px; 150 | } 151 | 152 | .format-grid .thegrid .grid-completion { 153 | position: absolute; 154 | right: 0.5rem; 155 | } 156 | 157 | .format-grid .grid-completion.grid-completion-colour-low { 158 | background-color: magenta; 159 | color: white; 160 | } 161 | 162 | .format-grid .grid-completion.grid-completion-colour-middle { 163 | background-color: yellow; 164 | color: black; 165 | } 166 | 167 | .format-grid .grid-completion.grid-completion-colour-high{ 168 | background-color: green; 169 | color: white; 170 | } 171 | 172 | .format-grid .grid-completion.grid-completion-percentagequarter-1 { 173 | border-right-color: lightgreen; 174 | } 175 | 176 | .format-grid .grid-completion.grid-completion-percentagequarter-2 { 177 | border-bottom-color: lightgreen; 178 | border-right-color: lightgreen; 179 | } 180 | 181 | .format-grid .grid-completion.grid-completion-percentagequarter-3 { 182 | border-bottom-color: lightgreen; 183 | border-left-color: lightgreen; 184 | border-right-color: lightgreen; 185 | } 186 | 187 | .format-grid .grid-completion.grid-completion-percentagequarter-4 { 188 | border-color: lightgreen; 189 | } 190 | 191 | .format-grid .grid-completion .grid-percentage { 192 | font-size: 0.75rem; 193 | } 194 | 195 | .format-grid .grid-completion .grid-percentage.grid-percentagevalue-100 { 196 | font-size: 0.6rem; 197 | } 198 | 199 | .format-grid .grid-image-preview { 200 | max-height: 100%; 201 | max-width: 100%; 202 | } 203 | 204 | .format-grid .grid-image-name { 205 | font-size: 0.75rem; 206 | text-align: center; 207 | } 208 | 209 | /* Single section navigation */ 210 | .course-content .single-section .section-navigation.gd-selection-selector-container { 211 | align-items: center; 212 | display: flex; 213 | } 214 | 215 | .course-content .single-section .section-navigation .gd-selection-selector-item { 216 | display: flex; 217 | flex: 1 0 0%; 218 | } 219 | 220 | .gd-selection-selector-item.prevsection { 221 | justify-content: flex-start; 222 | } 223 | 224 | .gd-selection-selector-item.jumpto { 225 | justify-content: center; 226 | } 227 | 228 | .gd-selection-selector-item.nextsection { 229 | justify-content: flex-end; 230 | } 231 | -------------------------------------------------------------------------------- /styles_boost.css: -------------------------------------------------------------------------------- 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 | /** 17 | * Grid Format. 18 | * 19 | * @package course/format 20 | * @subpackage grid 21 | * @version See the value of '$plugin->version' in version.php. 22 | * @copyright © 2022 onwards G J Barnard in respect to modifications of standard topics format. 23 | * @author G J Barnard - {@link http://about.me/gjbarnard} and 24 | * {@link http://moodle.org/user/profile.php?id=442195} 25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 | */ 27 | 28 | .format-grid .course-content .single-section .section-navigation { 29 | margin-bottom: 0; 30 | } -------------------------------------------------------------------------------- /templates/coursestyles.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/coursestyles 19 | 20 | Grid course styles template. 21 | 22 | Context variables required for this template: 23 | * height - Image height. 24 | * width - Image width. 25 | 26 | Example context (json): 27 | { 28 | "height": "240", 29 | "width": "180" 30 | } 31 | }} 32 | {{#coursestyles}} 33 | 48 | {{/coursestyles}} 49 | -------------------------------------------------------------------------------- /templates/grid_admin_setting_information.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/grid_admin_setting_information 19 | 20 | Context variables required for this template: 21 | * versioninfo - Version information. 22 | * maturity - Maturity information. 23 | * maturityalert - Alert level. 24 | * versioncheck - Version check information. 25 | 26 | Example context (json): 27 | { 28 | "versioninfo": "Release 3.11.1.0, version 2021102700 on Moodle 3.11.4+ (Build: 20220111)", 29 | "maturity": "Maturity info", 30 | "maturityalert": "info", 31 | "versioncheck": "Not supported on this version of Moodle" 32 | } 33 | }} 34 | {{#maturity}} 35 |
36 |

{{{versioninfo}}}

37 | {{{maturity}}} 38 |
39 | {{/maturity}} 40 | {{^maturity}} 41 |

{{versioninfo}}

42 | {{/maturity}} 43 | {{#versioncheck}} 44 |
45 | {{{versioncheck}}} 46 |
47 | {{/versioncheck}} 48 | -------------------------------------------------------------------------------- /templates/grid_admin_setting_markdown.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/grid_admin_setting_markdown 19 | 20 | Admin setting markdown template. 21 | 22 | Context variables required for this template: 23 | * title - Setting title. 24 | * name - Setting name. 25 | * description - Setting description. 26 | * markdown - Setting makrdown. 27 | 28 | Example context (json): 29 | { 30 | "title": "Setting title", 31 | "name": "Name", 32 | "description": "Description goes here", 33 | "markdown": "Markdown goes here" 34 | } 35 | }} 36 | {{! 37 | Setting description. 38 | }} 39 |
40 |
41 |

{{{title}}}

42 |

{{{description}}}

43 |
44 |
45 |
{{{markdown}}}
46 |
47 |
48 | -------------------------------------------------------------------------------- /templates/grid_completion.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/grid_completion 19 | 20 | Single page summary image. 21 | 22 | Context variables required for this template: 23 | * percentagecolour - The colour indicator, low (< 11), medium (between 11 and 89) or high (> 89). 24 | * percentagequarter - The colour border quarter indicator, 0 (< 1), 1 (between 1 and 25), 2 (between 26 and 50), 3 (between 51 and 75) or 4 (> 75). 25 | 26 | Example context (json): 27 | { 28 | "percentagecolour": "medium", 29 | "percentagequarter": 2, 30 | "percentagevalue": 42 31 | } 32 | }} 33 |
34 | {{percentagevalue}}% 35 |
36 | -------------------------------------------------------------------------------- /templates/grid_section.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/grid_section 19 | 20 | Context variables required for this template: 21 | * gridimage - Has a grid image to show. 22 | * currentsection - Current section? 23 | * imageuri - Image URI. 24 | * alttext - Image alternative text. 25 | * generatedimageuri - Generated image URI. 26 | * imageerror - Image error if any. 27 | * image - Name of the image if any. 28 | 29 | Example context (json): 30 | { 31 | "gridimage": true, 32 | "currentsection": true, 33 | "imageuri": "https://mymoodle/pluginfile.php/358/format_grid/displayedsectionimage/42/1/ducking.jpg.webp", 34 | "imagealttext": "Duckling", 35 | "generatedimageuri": false, 36 | "imageerror": "", 37 | "image": "ducking.jpg.webp" 38 | } 39 | }} 40 | {{#gridimage}} 41 |
42 |
43 | {{#imageuri}} 44 |
45 | {{imagealttext}} 46 |
47 | {{/imageuri}} 48 | {{#generatedimageuri}} 49 |
50 |
51 | {{/generatedimageuri}} 52 | {{#imageerror}} 53 |
54 |

{{imageerror}}

55 |
56 | {{/imageerror}} 57 | {{#image}} 58 | 61 | {{/image}} 62 |
63 |
64 | {{/gridimage}} 65 | -------------------------------------------------------------------------------- /templates/local/content.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/local/content 19 | 20 | Displays the complete course format. 21 | 22 | Example context (json): 23 | { 24 | "initialsection": { 25 | "num": 0, 26 | "id": 34, 27 | "cmlist": { 28 | "cms": [ 29 | { 30 | "cmitem": { 31 | "cmformat": { 32 | "cmname": "Forum example", 33 | "hasname": "true" 34 | }, 35 | "id": 3, 36 | "module": "forum", 37 | "extraclasses": "newmessages" 38 | } 39 | } 40 | ], 41 | "hascms": true 42 | }, 43 | "iscurrent": true, 44 | "summary": { 45 | "summarytext": "Summary text!" 46 | } 47 | }, 48 | "sections": [ 49 | { 50 | "num": 1, 51 | "id": 35, 52 | "header": { 53 | "name": "Section title", 54 | "url": "#" 55 | }, 56 | "cmlist": { 57 | "cms": [ 58 | { 59 | "cmitem": { 60 | "cmformat": { 61 | "cmname": "Another forum", 62 | "hasname": "true" 63 | }, 64 | "id": 3, 65 | "module": "forum", 66 | "extraclasses": "newmessages" 67 | } 68 | } 69 | ], 70 | "hascms": true 71 | }, 72 | "iscurrent": true, 73 | "summary": { 74 | "summarytext": "Summary text!" 75 | } 76 | }, 77 | { 78 | "num": 4, 79 | "id": 36, 80 | "header": { 81 | "name": "Section 2 title", 82 | "url": "#" 83 | }, 84 | "cmlist": { 85 | "cms": [ 86 | { 87 | "cmitem": { 88 | "cmformat": { 89 | "cmname": "Forum example", 90 | "hasname": "true" 91 | }, 92 | "id": 5, 93 | "module": "forum", 94 | "extraclasses": "newmessages" 95 | } 96 | } 97 | ], 98 | "hascms": true 99 | }, 100 | "iscurrent": true, 101 | "summary": { 102 | "summarytext": "Summary text!" 103 | } 104 | } 105 | ], 106 | "format": "grid", 107 | "title": "Course title example", 108 | "hasnavigation": true, 109 | "sectionnavigation": { 110 | "hasprevious": true, 111 | "previousurl": "#", 112 | "larrow": "◄", 113 | "previousname": "Section 3", 114 | "hasnext": true, 115 | "rarrow": "►", 116 | "nexturl": "#", 117 | "nextname": "Section 5" 118 | }, 119 | "sectionselector": { 120 | "hasprevious": true, 121 | "previousurl": "#", 122 | "larrow": "◄", 123 | "previousname": "Section 3", 124 | "hasnext": true, 125 | "rarrow": "►", 126 | "nexturl": "#", 127 | "nextname": "Section 5", 128 | "selector": "" 129 | }, 130 | "sectionreturn": 1, 131 | "singlesection": { 132 | "num": 1, 133 | "id": 35, 134 | "header": { 135 | "name": "Single Section Example", 136 | "url": "#" 137 | }, 138 | "cmlist": { 139 | "cms": [ 140 | { 141 | "cmitem": { 142 | "cmformat": { 143 | "cmname": "Assign example", 144 | "hasname": "true" 145 | }, 146 | "id": 4, 147 | "module": "assign", 148 | "extraclasses": "" 149 | } 150 | } 151 | ], 152 | "hascms": true 153 | }, 154 | "iscurrent": true, 155 | "summary": { 156 | "summarytext": "Summary text!" 157 | } 158 | } 159 | } 160 | }} 161 |
162 |

{{{title}}}

163 | {{#hasnavigation}} 164 |

{{#str}}maincoursepage{{/str}}

165 | {{/hasnavigation}} 166 | {{{completionhelp}}} 167 | {{#hassectionheaderimages}} 168 | {{> format_grid/coursestyles }} 169 | {{/hassectionheaderimages}} 170 | {{#stealthwarning}} 171 |
{{stealthwarning}}
172 | {{/stealthwarning}} 173 |
    174 | {{#initialsection}} 175 | {{$ core_courseformat/local/content/section }} 176 | {{> core_courseformat/local/content/section }} 177 | {{/ core_courseformat/local/content/section }} 178 | {{/initialsection}} 179 | {{#sections}} 180 | {{$ core_courseformat/local/content/section }} 181 | {{> format_grid/local/content/section }} 182 | {{/ core_courseformat/local/content/section }} 183 | {{/sections}} 184 |
185 | {{#hasgridsections}} 186 | {{> format_grid/coursestyles }} 187 | {{> format_grid/grid }} 188 | {{/hasgridsections}} 189 | {{#hasnavigation}} 190 |
191 | {{#sectionnavigation}} 192 | {{$ core_courseformat/local/content/sectionnavigation }} 193 | {{> format_grid/local/content/sectionnavigation }} 194 | {{/ core_courseformat/local/content/sectionnavigation }} 195 | {{/sectionnavigation}} 196 |
    197 | {{#singlesection}} 198 | {{$ core_courseformat/local/content/section }} 199 | {{> format_grid/local/content/section }} 200 | {{/ core_courseformat/local/content/section }} 201 | {{/singlesection}} 202 |
203 | {{#sectionselector}} 204 | {{$ core_courseformat/local/content/sectionselector }} 205 | {{> format_grid/local/content/sectionselector }} 206 | {{/ core_courseformat/local/content/sectionselector }} 207 | {{/sectionselector}} 208 |
209 | {{/hasnavigation}} 210 | {{#numsections}} 211 | {{$ core_courseformat/local/content/addsection}} 212 | {{> core_courseformat/local/content/addsection}} 213 | {{/ core_courseformat/local/content/addsection}} 214 | {{/numsections}} 215 | {{#bulkedittools}} 216 | {{$ core_courseformat/local/content/bulkedittools}} 217 | {{> core_courseformat/local/content/bulkedittools}} 218 | {{/ core_courseformat/local/content/bulkedittools}} 219 | {{/bulkedittools}} 220 |
221 | {{#js}} 222 | require(['format_grid/local/content'], function(component) { 223 | component.init('{{uniqid}}-course-format', {}, {{sectionreturn}}); 224 | }); 225 | {{/js}} 226 | -------------------------------------------------------------------------------- /templates/local/content/delegatedsection.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/local/content/delegatedsection 19 | 20 | Displays a course section. 21 | 22 | Note: This template is a wrapper around the section/content template to allow course formats and theme designers to 23 | modify parts of the wrapper without having to copy/paste the entire template. 24 | 25 | Example context (json): 26 | { 27 | "num": 3, 28 | "id": 35, 29 | "sectionname": "Section title", 30 | "controlmenu": "[tools menu]", 31 | "header": { 32 | "name": "Section title", 33 | "title": "Section title", 34 | "url": "#", 35 | "ishidden": true, 36 | "headinglevel": 3 37 | }, 38 | "cmlist": { 39 | "cms": [ 40 | { 41 | "cmitem": { 42 | "cmformat": { 43 | "cmname": "Assign example", 44 | "hasname": "true" 45 | }, 46 | "id": 4, 47 | "anchor": "activity-4", 48 | "module": "assign", 49 | "extraclasses": "" 50 | } 51 | } 52 | ], 53 | "hascms": true 54 | }, 55 | "ishidden": false, 56 | "iscurrent": true, 57 | "currentlink": "This topic", 58 | "availability": { 59 | "info": "Hidden from students", 60 | "hasavailability": true 61 | }, 62 | "summary": { 63 | "summarytext": "Summary text!" 64 | }, 65 | "controlmenu": { 66 | "menu": "Edit", 67 | "hasmenu": true 68 | }, 69 | "cmcontrols": "[Add an activity or resource]", 70 | "iscoursedisplaymultipage": true, 71 | "sectionreturnnum": 0, 72 | "contentcollapsed": false, 73 | "sitehome": false, 74 | "highlightedlabel" : "Highlighted" 75 | } 76 | }} 77 |
    78 | 95 |
96 | -------------------------------------------------------------------------------- /templates/local/content/section.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/local/content/section 19 | 20 | Displays a course section. 21 | 22 | Include the core content/section template. 23 | 24 | Example context (json): 25 | { 26 | "num": 3, 27 | "id": 35, 28 | "controlmenu": "[tools menu]", 29 | "header": { 30 | "name": "Section title", 31 | "title": "Section title", 32 | "url": "#", 33 | "ishidden": true 34 | }, 35 | "cmlist": { 36 | "cms": [ 37 | { 38 | "cmitem": { 39 | "cmformat": { 40 | "cmname": "Forum example", 41 | "hasname": "true" 42 | }, 43 | "id": 3, 44 | "module": "forum", 45 | "anchor": "activity-3", 46 | "extraclasses": "newmessages" 47 | } 48 | }, 49 | { 50 | "cmitem": { 51 | "cmformat": { 52 | "cmname": "Assign example", 53 | "hasname": "true" 54 | }, 55 | "id": 4, 56 | "anchor": "activity-4", 57 | "module": "assign", 58 | "extraclasses": "" 59 | } 60 | } 61 | ], 62 | "hascms": true 63 | }, 64 | "ishidden": false, 65 | "iscurrent": true, 66 | "currentlink": "This topic", 67 | "availability": { 68 | "info": "Hidden from students", 69 | "hasavailability": true 70 | }, 71 | "summary": { 72 | "summarytext": "Summary text!" 73 | }, 74 | "controlmenu": { 75 | "menu": "Edit", 76 | "hasmenu": true 77 | }, 78 | "cmcontrols": "[Add an activity or resource]", 79 | "iscoursedisplaymultipage": true, 80 | "sectionreturnid": 0, 81 | "contentcollapsed": false, 82 | "insertafter": true, 83 | "numsections": 42, 84 | "sitehome": false, 85 | "highlightedlabel" : "Highlighted" 86 | } 87 | }} 88 | 89 | {{< core_courseformat/local/content/section }} 90 | 91 | {{$ core_courseformat/local/content/section/content }} 92 | {{> format_grid/local/content/section/content }} 93 | {{/ core_courseformat/local/content/section/content }} 94 | 95 | {{$ core_courseformat/local/content/addsection}} 96 | {{> core_courseformat/local/content/section/addsectiondivider }} 97 | {{/ core_courseformat/local/content/addsection}} 98 | 99 | {{/ core_courseformat/local/content/section }} 100 | -------------------------------------------------------------------------------- /templates/local/content/section/cmsummary.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/local/content/section/cmsummary 19 | 20 | Displays the activities summary of a section. 21 | 22 | Example context (json): 23 | { 24 | "showcompletion": true, 25 | "mods": [ 26 | { 27 | "name": "Forums", 28 | "count": "3" 29 | }, 30 | { 31 | "name": "Books", 32 | "count": "2" 33 | } 34 | ], 35 | "modprogress": "Markup of icon", 36 | "totalactivities": "5" 37 | } 38 | }} 39 |
40 |
41 | {{#pix}}i/activities, moodle{{/pix}}{{#str}} totalactivities, course, {{totalactivities}} {{/str}} 42 |
43 | {{#showcompletion}} 44 |
45 | {{{modprogress}}} 46 |
47 | {{/showcompletion}} 48 |
49 | -------------------------------------------------------------------------------- /templates/local/content/section/content.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/local/content/section/content 19 | 20 | The internal content of a section. 21 | 22 | Example context (json): 23 | { 24 | "num": 3, 25 | "id": 35, 26 | "controlmenu": "[tools menu]", 27 | "header": { 28 | "name": "Section title", 29 | "title": "Section title", 30 | "url": "#", 31 | "ishidden": true, 32 | "headinglevel": 3 33 | }, 34 | "cmlist": { 35 | "cms": [ 36 | { 37 | "cmitem": { 38 | "cmformat": { 39 | "cmname": "Forum example", 40 | "hasname": "true" 41 | }, 42 | "id": 3, 43 | "cmid": 3, 44 | "module": "forum", 45 | "anchor": "activity-3", 46 | "extraclasses": "newmessages" 47 | } 48 | }, 49 | { 50 | "cmitem": { 51 | "cmformat": { 52 | "cmname": "Assign example", 53 | "hasname": "true" 54 | }, 55 | "id": 4, 56 | "cmid": 4, 57 | "anchor": "activity-4", 58 | "module": "assign", 59 | "extraclasses": "" 60 | } 61 | } 62 | ], 63 | "hascms": true 64 | }, 65 | "ishidden": false, 66 | "iscurrent": true, 67 | "currentlink": "This topic", 68 | "availability": { 69 | "info": "Hidden from students", 70 | "hasavailability": true 71 | }, 72 | "summary": { 73 | "summarytext": "Summary text!" 74 | }, 75 | "controlmenu": { 76 | "menu": "Edit", 77 | "hasmenu": true 78 | }, 79 | "cmcontrols": "[Add an activity or resource]", 80 | "iscoursedisplaymultipage": true, 81 | "sectionreturnnum": 0, 82 | "contentcollapsed": false, 83 | "insertafter": true, 84 | "numsections": 42, 85 | "sitehome": false, 86 | "isstealth": false, 87 | "highlightedlabel" : "Highlighted" 88 | } 89 | }} 90 |
95 | {{#singleheader}} 96 | {{$ core_courseformat/local/content/section/header }} 97 | {{> core_courseformat/local/content/section/header }} 98 | {{/ core_courseformat/local/content/section/header }} 99 | {{/singleheader}} 100 | {{#header}} 101 | {{$ core_courseformat/local/content/section/header }} 102 | {{> core_courseformat/local/content/section/header }} 103 | {{/ core_courseformat/local/content/section/header }} 104 | {{/header}} 105 | {{^singleheader}} 106 | {{#restrictionlock}} 107 |
108 | {{#pix}}t/unlock, core{{/pix}} 109 |
110 | {{/restrictionlock}} 111 | {{/singleheader}} 112 |
113 | {{$ core_courseformat/local/content/section/badges }} 114 | {{> core_courseformat/local/content/section/badges }} 115 | {{/ core_courseformat/local/content/section/badges }} 116 |
117 | {{#collapsemenu}} 118 | 130 | {{/collapsemenu}} 131 | {{#controlmenu}} 132 | {{$ core_courseformat/local/content/section/controlmenu }} 133 | {{> core_courseformat/local/content/section/controlmenu }} 134 | {{/ core_courseformat/local/content/section/controlmenu }} 135 | {{/controlmenu}} 136 | {{#header}} 137 | {{^controlmenu}} 138 | 153 | {{/controlmenu}} 154 | {{/header}} 155 |
156 | {{> format_grid/grid_section }} 157 |
159 |
160 | {{#isstealth}} 161 |
162 | {{#str}} orphansectionwarning, core_courseformat {{/str}} 163 |
164 | {{/isstealth}} 165 | {{#summary}} 166 | {{$ core_courseformat/local/content/section/summary }} 167 | {{> core_courseformat/local/content/section/summary }} 168 | {{/ core_courseformat/local/content/section/summary }} 169 | {{/summary}} 170 | {{#availability}} 171 | {{$ core_courseformat/local/content/section/availability }} 172 | {{> core_courseformat/local/content/section/availability }} 173 | {{/ core_courseformat/local/content/section/availability }} 174 | {{/availability}} 175 |
176 | {{#cmsummary}} 177 | {{$ core_courseformat/local/content/section/cmsummary }} 178 | {{> format_grid/local/content/section/cmsummary }} 179 | {{/ core_courseformat/local/content/section/cmsummary }} 180 | {{/cmsummary}} 181 | {{#cmlist}} 182 | {{$ core_courseformat/local/content/section/cmlist }} 183 | {{> core_courseformat/local/content/section/cmlist }} 184 | {{/ core_courseformat/local/content/section/cmlist }} 185 | {{/cmlist}} 186 | {{{cmcontrols}}} 187 |
188 | -------------------------------------------------------------------------------- /templates/local/content/sectionnavigation.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/local/content/sectionnavigation 19 | 20 | Displays the course section navigation. 21 | 22 | Example context (json): 23 | { 24 | "hasnext": true, 25 | "hasprevious": true, 26 | "previousname": "Section 6", 27 | "previousurl": "#", 28 | "nexturl": "#", 29 | "nextname": "Section 8", 30 | "rtl": false 31 | } 32 | }} 33 | 49 | -------------------------------------------------------------------------------- /templates/local/content/sectionselector.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/local/content/sectionselector 19 | 20 | Displays the course section navigation. 21 | 22 | Example context (json): 23 | { 24 | "hasnext": true, 25 | "hasprevious": true, 26 | "nextname": "Section 8", 27 | "nexturl": "#", 28 | "previousname": "Section 6", 29 | "previousurl": "#", 30 | "rtl": false, 31 | "selector": "" 32 | } 33 | }} 34 |
35 |
36 | {{#hasprevious}} 37 | 38 | {{{previousname}}} 39 | 40 | {{/hasprevious}} 41 |
42 |
43 | {{{selector}}} 44 |
45 |
46 | {{#hasnext}} 47 | 48 | {{{nextname}}} 49 | 50 | {{/hasnext}} 51 |
52 |
53 | -------------------------------------------------------------------------------- /templates/singlepagesummaryimage.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template format_grid/singlepagesummaryimage 19 | 20 | Single page summary image. 21 | 22 | Context variables required for this template: 23 | * centre - If true, centre the content. 24 | * image - Image markup. 25 | * left - If true, align the content to the left. 26 | * right - If true, align the content to the right. 27 | * summary - Summary markup. 28 | 29 | Example context (json): 30 | { 31 | "centre": true, 32 | "alttext": "The image", 33 | "imageuri": "https://mymoodle/pluginfile.php/358/format_grid/displayedsectionimage/301/39/myimage.jpg.webp", 34 | "left": false, 35 | "right": false, 36 | "summary": "Summary markup" 37 | } 38 | }} 39 | {{#left}} 40 |
41 | {{/left}} 42 | {{#centre}} 43 |
44 | {{/centre}} 45 | {{#right}} 46 |
47 | {{/right}} 48 |
{{alttext}}
49 |
{{{summary}}}
50 |
51 | -------------------------------------------------------------------------------- /tests/behat/BEHAT_COMMANDS.txt: -------------------------------------------------------------------------------- 1 | Refs: https://www.elearningworld.org/behat-part-one/ and https://www.elearningworld.org/behat-part-two/ 2 | 3 | sudo -u www-data php admin/tool/behat/cli/run.php --tags="@format_grid" -------------------------------------------------------------------------------- /tests/behat/move_sections.feature: -------------------------------------------------------------------------------- 1 | @format @format_grid 2 | Feature: Sections can be moved - adapted from core test of the same name. 3 | In order to rearrange my course contents 4 | As a teacher 5 | I need to move sections up and down 6 | 7 | Background: 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | daisy | Daisy | Grid | daisy@grid.com | 11 | And the following "courses" exist: 12 | | fullname | shortname | format | coursedisplay | numsections | 13 | | Grid | GD | grid | 0 | 5 | 14 | And the following "course enrolments" exist: 15 | | user | course | role | 16 | | daisy | GD | editingteacher | 17 | And the following "activities" exist: 18 | | activity | name | intro | course | idnumber | section | 19 | | forum | Test forum name | Test forum name description | GD | forum1 | 1 | 20 | And I log in as "daisy" 21 | And I am on "Grid" course homepage with editing mode on 22 | 23 | Scenario: Move up and down a section with Javascript disabled in a single page course 24 | When I move down section "1" 25 | Then I should see "Test forum name" in the "Section 2" "section" 26 | And I move up section "2" 27 | And I should see "Test forum name" in the "Section 1" "section" 28 | 29 | @javascript 30 | Scenario: Move section with javascript 31 | When I open section "1" edit menu 32 | And I click on "Move" "link" in the "Section 1" "section" 33 | And I click on "Section 3" "link" in the ".modal-body" "css_element" 34 | Then I should see "Test forum name" in the "Section 3" "section" 35 | 36 | @_file_upload @javascript 37 | Scenario: Move section with an image - note: The duckling image is copyright 'Gareth J Barnard 2020' use only for this test without permission. 38 | When I edit the section "2" 39 | And I upload "course/format/grid/tests/fixtures/Duckling.jpg" file to "Section image" filemanager 40 | And I set the field "Image alt text" to "Duckling" 41 | And I press "Save changes" 42 | And I open section "2" edit menu 43 | And I click on "Move" "link" in the "Section 2" "section" 44 | And I click on "Section 3" "link" in the ".modal-body" "css_element" 45 | And I turn editing mode off 46 | Then "//img[contains(@src, 'Duckling.jpg')]" "xpath_element" should exist in the "#section-3 .grid-image" "css_element" 47 | And "//img[contains(@alt, 'Duckling')]" "xpath_element" should exist in the "#section-3 .grid-image" "css_element" 48 | -------------------------------------------------------------------------------- /tests/behat/sectionbreak.feature: -------------------------------------------------------------------------------- 1 | @format @format_grid 2 | Feature: Section break 3 | As a teacher I need to break the grid with a section break. 4 | 5 | Background: 6 | Given the following "users" exist: 7 | | username | firstname | lastname | email | 8 | | daisy | Daisy | Grid | daisy@grid.com | 9 | And the following "courses" exist: 10 | | fullname | shortname | format | numsections | 11 | | Grid | GD | grid | 10 | 12 | And the following "course enrolments" exist: 13 | | user | course | role | 14 | | daisy | GD | editingteacher | 15 | And I am on the "GD" "Course" page logged in as "daisy" 16 | 17 | @javascript 18 | Scenario: Create a section 4 break 19 | When I turn editing mode on 20 | And I edit the section "4" 21 | And I select "Yes" from the "sectionbreak" singleselect 22 | And I set the field "Section break heading" to "Section four break" 23 | And I press "Save changes" 24 | And I turn editing mode off 25 | Then I should see "Section four break" in the "#gridsectionbreak-4" "css_element" 26 | -------------------------------------------------------------------------------- /tests/behat/uploadimage.feature: -------------------------------------------------------------------------------- 1 | @format @format_grid 2 | Feature: Image upload 3 | As a teacher I need to upload an image to a section. 4 | 5 | Background: 6 | Given the following "users" exist: 7 | | username | firstname | lastname | email | 8 | | daisy | Daisy | Grid | daisy@grid.com | 9 | And the following "courses" exist: 10 | | fullname | shortname | format | numsections | 11 | | Grid | GD | grid | 5 | 12 | And the following "course enrolments" exist: 13 | | user | course | role | 14 | | daisy | GD | editingteacher | 15 | And I am on the "GD" "Course" page logged in as "daisy" 16 | 17 | @_file_upload @javascript 18 | Scenario: Upload an image to section 2 - note: The duckling image is copyright 'Gareth J Barnard 2020' use only for this test without permission. 19 | When I turn editing mode on 20 | And I edit the section "2" 21 | And I upload "course/format/grid/tests/fixtures/Duckling.jpg" file to "Section image" filemanager 22 | And I set the field "Image alt text" to "Duckling" 23 | And I press "Save changes" 24 | And I turn editing mode off 25 | Then "//img[contains(@src, 'Duckling.jpg')]" "xpath_element" should exist in the "#section-2 .grid-image" "css_element" 26 | And "//img[contains(@alt, 'Duckling')]" "xpath_element" should exist in the "#section-2 .grid-image" "css_element" 27 | -------------------------------------------------------------------------------- /tests/fixtures/Duckling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjbarnard/moodle-format_grid/36d6b02fd0f78804457a301bc5bb9a8f6f6b582d/tests/fixtures/Duckling.jpg -------------------------------------------------------------------------------- /version.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Grid Format. 19 | * 20 | * @package format_grid 21 | * @copyright 2012 G J Barnard in respect to modifications of standard topics format. 22 | * @author G J Barnard - 23 | * {@link https://moodle.org/user/profile.php?id=442195} 24 | * {@link https://gjbarnard.co.uk} 25 | * @author Based on code originally written by Paul Krix and Julian Ridden. 26 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 27 | */ 28 | 29 | defined('MOODLE_INTERNAL') || die(); 30 | 31 | // Plugin version. 32 | $plugin->version = 2024101503; 33 | 34 | // Required Moodle version. 35 | $plugin->requires = 2024100700.00; // 4.5 (Build: 20241007). 36 | 37 | // Supported Moodle version. 38 | $plugin->supported = [405, 405]; 39 | 40 | // Full name of the plugin. 41 | $plugin->component = 'format_grid'; 42 | 43 | // Software maturity level. 44 | $plugin->maturity = MATURITY_STABLE; 45 | 46 | // User-friendly version number. 47 | $plugin->release = '405.1.0'; 48 | --------------------------------------------------------------------------------