├── .github └── workflows │ └── ci.yml ├── COPYING.txt ├── README.md ├── backup └── moodle2 │ ├── backup_multiblock_block_task.class.php │ └── restore_multiblock_block_task.class.php ├── block_multiblock.php ├── changelog.php ├── classes ├── adddefaultblock.php ├── form │ ├── addblock.php │ ├── editblock.php │ ├── editblock_base.php │ └── editblock_totara.php ├── helper.php ├── icon_helper.php ├── layout │ ├── abstract_layout.php │ ├── accordion.php │ ├── carousel.php │ ├── columns_2_33_66.php │ ├── columns_2_66_33.php │ ├── columns_2equal.php │ ├── columns_3equal.php │ ├── dropdown.php │ ├── tabbed_list.php │ ├── tabbed_list_columns_2_66_33.php │ ├── vertical_tabbed_list.php │ └── vertical_tabbed_list_right.php ├── navigation.php ├── output │ ├── main.php │ ├── mobile.php │ └── renderer.php └── privacy │ └── provider.php ├── configinstance.php ├── db ├── access.php ├── mobile.php └── uninstall.php ├── edit_form.php ├── lang └── en │ └── block_multiblock.php ├── manage.php ├── pix ├── accordion.png ├── columns-2-33-66.png ├── columns-2-66-33.png ├── columns-2-equal.png ├── columns-3-equal.png ├── dropdown.png ├── tabbed-list.png ├── tabs-columns-2-66-33.png ├── vertical-tabbed-list-right.png └── vertical-tabbed-list.png ├── settings.php ├── styles.css ├── templates ├── accordion-bootstrap3.mustache ├── accordion-bootstrap4.mustache ├── accordion-mobile.mustache ├── carousel-bootstrap3.mustache ├── carousel-bootstrap4.mustache ├── carousel-mobile.mustache ├── columns-2-33-66.mustache ├── columns-2-66-33.mustache ├── columns-2equal.mustache ├── columns-3equal.mustache ├── dropdown-bootstrap3.mustache ├── dropdown-bootstrap4.mustache ├── tabbed-list-bootstrap3.mustache ├── tabbed-list-bootstrap4.mustache ├── tabbed-list-columns-2-66-33-bootstrap3.mustache ├── tabbed-list-columns-2-66-33-bootstrap4.mustache ├── vertical-tabbed-list-bootstrap3.mustache ├── vertical-tabbed-list-bootstrap4.mustache ├── vertical-tabbed-list-right-bootstrap3.mustache └── vertical-tabbed-list-right-bootstrap4.mustache ├── tests └── behat │ ├── accordion.feature │ ├── basic_test.feature │ ├── behat_block_multiblock.php │ ├── defaultsettings.feature │ ├── dropdown.feature │ ├── front_page.feature │ ├── merging_blocks.feature │ ├── tabbed.feature │ ├── tabbed_vertical_left.feature │ └── tabbed_vertical_right.feature └── version.php /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | uses: catalyst/catalyst-moodle-workflows/.github/workflows/ci.yml@main 8 | secrets: 9 | moodle_org_token: ${{ secrets.MOODLE_ORG_TOKEN }} 10 | with: 11 | disable_phpunit: true 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Multiblock 2 | ========== 3 | 4 | [![Build Status](https://api.travis-ci.com/Arantor/moodle-block_multiblock.svg?branch=master)](https://travis-ci.com/Arantor/moodle-block_multiblock/) 5 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 6 | ![GitHub last commit](https://img.shields.io/github/last-commit/Arantor/moodle-block_multiblock/master.svg) 7 | 8 | ![Moodle 3.5 supported](https://img.shields.io/badge/Moodle-3.5-brightgreen) 9 | ![Moodle 3.6 supported for data loss items](https://img.shields.io/badge/Moodle-3.6-green) 10 | ![Moodle 3.7 supported](https://img.shields.io/badge/Moodle-3.7-brightgreen) 11 | ![Moodle 3.8 supported](https://img.shields.io/badge/Moodle-3.8-brightgreen) 12 | ![Moodle 3.9 supported](https://img.shields.io/badge/Moodle-3.9-brightgreen) 13 | ![Moodle master supported](https://img.shields.io/badge/Moodle-master-brightgreen) 14 | ![Totara 12 support](https://img.shields.io/badge/Totara-12-brightgreen) 15 | 16 | * [What is this?](#what-is-this) 17 | * [What styles of presentation are there?](#what-styles-of-presentation-are-there) 18 | * [Support](#support) 19 | 20 | What is this? 21 | ------------- 22 | 23 | This is a sort of magic metablock. Suppose you're using Boost and you have a 24 | whole bunch of blocks making a course page longer than it should be, or 25 | cluttering up your Dashboard. 26 | 27 | Using Multiblock you can collect all those blocks together and use 'one block's 28 | worth' of space and put all those blocks into the one space with one of several 29 | layouts, whether it's tabs or an accordion, or something else. Several preset 30 | layouts are bundled with Multiblock. 31 | 32 | 33 | What styles of presentation are there? 34 | -------------------------------------- 35 | 36 | Show and tell time, I guess. Screenshots from 3.6 with Boost with the accent 37 | colour changed. 38 | 39 | 40 | Tabbed layout: 41 | 42 | ![Tabbed layout](/pix/tabbed-list.png?raw=true) 43 | 44 | Vertical tabbed layout (left) 45 | 46 | ![Vertical tabbed layout](/pix/vertical-tabbed-list.png?raw=true) 47 | 48 | Vertical tabbed layout (right) 49 | 50 | ![Vertical tabbed layout](/pix/vertical-tabbed-list-right.png?raw=true) 51 | 52 | Accordion layout: 53 | 54 | ![Accordion layout](/pix/accordion.png?raw=true) 55 | 56 | Dropdown layout: 57 | 58 | ![Dropdown layout](/pix/dropdown.png?raw=true) 59 | 60 | Tabs with columns inside it: 61 | 62 | ![Tabs with columns](/pix/tabs-columns-2-66-33.png) 63 | 64 | 65 | There's also some 2-column layouts where the 'multiblock' container has no 66 | title so it integrates better into the look and feel. The styling is designed 67 | for Boost so mileage with other themes will absolutely vary. 68 | 69 | 2 column (equal width): 70 | 71 | ![2 column layout (equal width)](/pix/columns-2-equal.png?raw=true) 72 | 73 | 2 column (66% / 33%): 74 | 75 | ![2 column layout (66% / 33%)](/pix/columns-2-66-33.png?raw=true) 76 | 77 | 2 column (33% / 66%): 78 | 79 | ![2 column layout (33% / 66%)](/pix/columns-2-33-66.png?raw=true) 80 | 81 | 82 | And now a 3-column equal width layout: 83 | 84 | ![3 column layout (equal width)](/pix/columns-3-equal.png?raw=true) 85 | 86 | 87 | Support 88 | ------- 89 | 90 | If you find a bug, please use GitHub to file an issue - they should be added to 91 | https://github.com/arantor/moodle-block_multiblock/issues 92 | 93 | Please note that I make no guarantees around resolution times and if you need 94 | something more concrete in terms of resolutions, or development, please 95 | contact Catalyst IT Europe to discuss your needs. 96 | 97 | https://www.catalyst-eu.net/content/contact-us 98 | -------------------------------------------------------------------------------- /backup/moodle2/backup_multiblock_block_task.class.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Backup steps for the multiblock plugin. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | /** 26 | * Specialised backup task for the multiblock block. 27 | * 28 | * This is primarily about backing up the child blocks. 29 | * 30 | * @package block_multiblock 31 | * @copyright 2019 Peter Spicer 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class backup_multiblock_block_task extends backup_block_task { 35 | 36 | /** 37 | * Mandatory function for defining specific settings for this task. 38 | */ 39 | protected function define_my_settings() { 40 | } 41 | 42 | /** 43 | * Mandatory function for defining steps carried out by this backup process. 44 | * 45 | * Specifically when run, queue the sub-blocks to the backup plan and execute them. 46 | */ 47 | protected function define_my_steps() { 48 | global $DB; 49 | 50 | // Find all the sub-blocks so we can add them to the backup plan. 51 | $subblocks = $DB->get_records('block_instances', ['parentcontextid' => $this->get_contextid()]); 52 | 53 | // Before we add anything to the plan, we need to sort out the progress meter. 54 | // The issue is, we're adding tasks to the progress queue but the size of the queue 55 | // for the progress meter was set up before we started the queue, so we have to fit it. 56 | // Unfortunately, it's a protected array inside the progress instance, so better 57 | // pop the lid and get ourselves access to it with Reflection. 58 | $progress = $this->get_progress(); 59 | $progressclass = new ReflectionClass($progress); 60 | $progressproperty = $progressclass->getProperty('maxes'); 61 | $progressproperty->setAccessible(true); 62 | $maxes = $progressproperty->getValue($progress); 63 | $maxes[count($maxes) - 1] += count($subblocks); 64 | $progressproperty->setValue($progress, $maxes); 65 | 66 | foreach (array_keys($subblocks) as $blockid) { 67 | // Only Moodle2 format backups support blocks, not that the backup block task cares anyway. 68 | $task = backup_factory::get_backup_block_task(backup::FORMAT_MOODLE, $blockid); 69 | 70 | // Add it to the plan, then run the task for each sub-block. 71 | $this->plan->add_task($task); 72 | $task->build(); 73 | $task->execute(); 74 | } 75 | } 76 | 77 | /** 78 | * Return fileareas attached to this block. 79 | * Multiblock itself has no fileareas, it leverages those of its children. 80 | * 81 | * @return array List of fileareas. 82 | */ 83 | public function get_fileareas() { 84 | return []; 85 | } 86 | 87 | /** 88 | * Return a list of configuration items that need to be safely encoded to 89 | * successfully be handled during backup/restore. 90 | * 91 | * Multiblock itself has minimal configuration, it leverages its children. 92 | * 93 | * @return array List of attributes that need encoding. 94 | */ 95 | public function get_configdata_encoded_attributes() { 96 | return []; 97 | } 98 | 99 | /** 100 | * Re-encode content links inside the block's content when backing up 101 | * or restoring. 102 | * 103 | * Multiblock has no content itself for this to be processed. 104 | * 105 | * @param string $content The content to be processed. 106 | * @return string The processed content. 107 | */ 108 | public static function encode_content_links($content) { 109 | return $content; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /backup/moodle2/restore_multiblock_block_task.class.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Restoration steps for the multiblock plugin. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | /** 26 | * Specialised restore task for the multiblock block 27 | * 28 | * @package block_multiblock 29 | * @copyright 2019 Peter Spicer 30 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 | */ 32 | class restore_multiblock_block_task extends restore_block_task { 33 | 34 | /** 35 | * Injects the restore plan into this task. 36 | * 37 | * While receiving the dependency of the restore_plan, rearrange 38 | * the queue to make sure multiblock is processed first. 39 | * 40 | * @param restore_plan $plan The restore plan for this restore job. 41 | */ 42 | public function set_plan($plan) { 43 | parent::set_plan($plan); 44 | 45 | // So here we need to reorder the task list. Specifically, we need 46 | // to rearrange all instances of restore_block_task to put 47 | // instances of restore_multiblock_task_block first. And that means 48 | // we first have to get the task list, it's a protected property. 49 | $planclass = new ReflectionClass($plan); 50 | $taskproperty = $planclass->getProperty('tasks'); 51 | $taskproperty->setAccessible(true); 52 | 53 | // So, this task (a multiblock) is the last task added. We need to splice it in. 54 | // First, break it off the list. 55 | $tasks = $taskproperty->getValue($plan); 56 | $thistask = array_pop($tasks); 57 | 58 | // Now we need to find which index contains the first instance of a block task. 59 | $index = null; 60 | foreach ($tasks as $id => $task) { 61 | if ($task instanceof restore_block_task) { 62 | $index = $id; 63 | break; 64 | } 65 | } 66 | 67 | if ($index !== null) { 68 | // Splice the item at the front of the queue. This should be fine in all backup cases 69 | // because in all backup types, the blocks are after the root/course/activity tasks. 70 | $tasks = array_merge(array_slice($tasks, 0, $index), [$thistask], array_slice($tasks, $index)); 71 | } else { 72 | // Slightly bizarrely, there's no other blocks in the tasklist, just put this back. 73 | $tasks[] = $thistask; 74 | } 75 | 76 | // And put the queue back. 77 | $taskproperty->setValue($plan, $tasks); 78 | } 79 | 80 | /** 81 | * Mandatory function for defining processing settings on restore. 82 | */ 83 | protected function define_my_settings() { 84 | } 85 | 86 | /** 87 | * Mandatory function for defining processing steps on restore. 88 | * 89 | * Moodle actually inadvertantly handles all the steps automatically. 90 | */ 91 | protected function define_my_steps() { 92 | } 93 | 94 | /** 95 | * Mandatory function for handling file areas on restoration. 96 | * 97 | * @return array A list of file areas handled by this plugin. 98 | */ 99 | public function get_fileareas() { 100 | return []; 101 | } 102 | 103 | /** 104 | * Return a list of attributes that requires decoding during restore. 105 | * 106 | * @return array A list of attributes ot be fixed. 107 | */ 108 | public function get_configdata_encoded_attributes() { 109 | return []; 110 | } 111 | 112 | /** 113 | * Return a list of contents for the plugin that will need to be decoded during restore. 114 | * 115 | * @return array A list of content items to be decoded. 116 | */ 117 | public static function define_decode_contents() { 118 | return array(); 119 | } 120 | 121 | /** 122 | * Return a list of find/replace rules to decode content during restore. 123 | * 124 | * @return array A list of find/replace rules. 125 | */ 126 | public static function define_decode_rules() { 127 | return array(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /changelog.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Changelog. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | ?> 28 | 29 | Multiblock Changelog. 30 | (File protected as a .php file to avoid leaking details of instance in use.) 31 | 32 | 1.3.4 - 2020060104 33 | * Tabs with 2-column layout thanks to Peter Burnett at Catalyst AU 34 | 35 | 1.3.3 - 2020060103 36 | * Handle the site front page course correctly (#66) 37 | * Switch over to Moodle HQ's fork of moodle-plugin-ci 38 | 39 | 1.3.2 - 2020060102 40 | * Don't try to use invalid blocks as part of 'merge in block' (#62) 41 | 42 | 1.3.1 - 2020060101 43 | * Stop running tests on 3.6. 44 | * Add 3.9 stable to the supported list. 45 | 46 | 1.3.0 - 2020060100 47 | * Add 3-column layout. 48 | * Add carousel layout. (#56) 49 | * Fix breadcrumbs issue. (#59) 50 | 51 | 1.2.6 - 2020022406 52 | * Fix warning for missing language string (#55) 53 | 54 | 1.2.5 - 2020022405 55 | * Fix permission check for users editing blocks. 56 | * Bump Node version for automated tests (excluding MOODLE_36_STABLE) 57 | * First release on Moodle plugin directory. 58 | 59 | 1.2.4 - 2020022404 60 | * Tidy up unnecessary global variables. 61 | * When uninstalling, return all sub-blocks to the parent (e.g. dashboard). (#47) 62 | 63 | 1.2.3 - 2020022403 64 | * Added Behat tests for dropdown layout. (#4) 65 | * Travis builds now use Chrome not Firefox. 66 | 67 | 1.2.2 - 2020022402 68 | * Added Behat tests for accordion/tabbed/vertical layouts. (#4) 69 | 70 | 1.2.1 - 2020022401 71 | * Fixed dropdown markup for Totara 12. (#44) 72 | * Fixed vertical tabbed views for Totara 12. (#48) 73 | 74 | 1.2.0 - 2020022400 75 | * Added privacy support 76 | * Fixed permissions handling relating to block contexts. (Part of #22) 77 | 78 | 1.1.2 - 2020022300 79 | * Add hints in configure-block for recommended layouts. (#34) 80 | 81 | 1.1.1 - 2020021301 82 | * Support for Moodle 3.5 83 | 84 | 1.1.0 - 2020021300 85 | * Support for Totara 12+ (#18) 86 | 87 | 1.0.3 - 2020021102 88 | * Added vertical tabs - right aligned. (#33) 89 | 90 | 1.0.2 - 2020021101 91 | * Added a changelog. 92 | * Added badges to the readme. 93 | * Refactored some internals to allow future development. (#29) 94 | 95 | 1.0.1 - 2020021100 96 | * Added save-and-display option to the editing a subblock instance. (#25) 97 | 98 | 1.0.0 - 2019092600 99 | * Initial code, not initially released under this version except on GitHub. 100 | -------------------------------------------------------------------------------- /classes/adddefaultblock.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Form for adding a block to a multiblock instance. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2022 University of Bath 22 | * @author James Pearce 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace block_multiblock; 27 | use block_multiblock\helper; 28 | 29 | /** 30 | * Library for adding a block to a multiblock instance. 31 | * 32 | * @package block_multiblock 33 | */ 34 | class adddefaultblock { 35 | /** @var array Storage of the block name -> block description of possibly addable sub-blocks. */ 36 | public $blocklist = []; 37 | 38 | /** 39 | * Initialise the adding of a block to the multiblock. 40 | * 41 | * @param int $id is the id of the multiblock 42 | * @param array $arraytoadd is an array of blocks to add 43 | * @param object $multiblockinstance is the parent multiblock instance 44 | */ 45 | public function init($id, $arraytoadd, $multiblockinstance) { 46 | 47 | $this->set_blocks_to_add($id, $arraytoadd, $multiblockinstance); 48 | 49 | } 50 | 51 | /** 52 | * This checks the blocks that can bee added to a multiblock, 53 | * selects the ones defined in the current default blocks setting and creates an instance of that new block 54 | * then moves it to the multiblock. 55 | * 56 | * @param int $blockid is the id of the multiblock 57 | * @param array $arraytoadd is an array of blocks to add 58 | * @param object $multiblockinstance is the parent multiblock instance 59 | */ 60 | public function set_blocks_to_add($blockid, $arraytoadd, $multiblockinstance) { 61 | global $DB, $PAGE; 62 | 63 | // Load all possible blocks for the page. 64 | $blockmanager = $PAGE->blocks; 65 | $blockmanager->load_blocks(); 66 | 67 | // Loop over the $arraytoadd to find the blocks to add, within the addable blocks. 68 | foreach ($arraytoadd as $toadd) { 69 | foreach ($blockmanager->get_addable_blocks() as $block) { 70 | if ($block->name == 'multiblock') { 71 | continue; 72 | } 73 | // Found a block to add. 74 | if ($toadd == $block->name) { 75 | // Add the block to the block list. 76 | $this->blocklist[$block->name] = $block; 77 | 78 | // Create a new instance of the block to add. This will put the new default blocks in the side-pre region. 79 | $blockmanager->add_block($toadd, 'side-pre', 10, false); 80 | 81 | $addedblocks = $DB->get_records('block_instances', ['parentcontextid' => $multiblockinstance->parentcontextid]); 82 | 83 | foreach ($addedblocks as $addedblock) { 84 | if ($addedblock->blockname == $toadd) { 85 | 86 | // Move the newly created blocks into the multiblock. 87 | helper::move_block($addedblock->id, $blockid); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /classes/form/addblock.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Form for adding a block to a multiblock instance. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\form; 26 | use block_multiblock\helper; 27 | use core_plugin_manager; 28 | use moodleform; 29 | 30 | defined('MOODLE_INTERNAL') || die(); 31 | 32 | require_once($CFG->libdir . '/formslib.php'); 33 | 34 | /** 35 | * Form for adding a block to a multiblock instance. 36 | * 37 | * @package block_multiblock 38 | * @copyright 2019 Peter Spicer 39 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 | */ 41 | class addblock extends moodleform { 42 | /** @var array Storage of the block name -> block description of possibly addable sub-blocks. */ 43 | public $blocklist = []; 44 | 45 | /** 46 | * Handles the general setup of the form for adding sub-blocks to a multiblock. 47 | */ 48 | public function definition() { 49 | $mform =& $this->_form; 50 | 51 | if (!empty($this->_customdata['id'])) { 52 | $this->set_block_list(); 53 | } 54 | if (empty($this->blocklist)) { 55 | return; 56 | } 57 | 58 | $mform->addElement('header', 'addnewblock', get_string('addnewblock', 'block_multiblock')); 59 | 60 | $group = []; 61 | $group[] = &$mform->createElement('select', 'addblock', get_string('addblock'), $this->blocklist); 62 | $group[] = &$mform->createElement('submit', 'addsubmit', get_string('add')); 63 | $mform->addGroup($group, 'addblockgroup', '', [' '], false); 64 | 65 | $siblings = $this->get_sibling_blocks($this->_customdata['id']); 66 | if (!empty($siblings)) { 67 | $siblings = ['' => get_string('selectblock', 'block_multiblock')] + $siblings; 68 | $mform->addElement('header', 'moveexistingblock', get_string('moveexistingblock', 'block_multiblock')); 69 | $mform->setExpanded('moveexistingblock', false); 70 | 71 | $siblinggroup = []; 72 | $siblinggroup[] = &$mform->createElement('select', 'moveblock', 73 | get_string('moveexistingblock', 'block_multiblock'), $siblings); 74 | $siblinggroup[] = &$mform->createElement('submit', 'movesubmit', get_string('move')); 75 | $mform->addGroup($siblinggroup, 'siblinggroup', '', [' '], false); 76 | } 77 | } 78 | 79 | /** 80 | * Given the instance id of a multiblock, identify the possible addable blocks. 81 | */ 82 | public function set_block_list() { 83 | global $DB, $PAGE; 84 | 85 | $this->blocklist = [ 86 | '' => get_string('selectblock', 'block_multiblock'), 87 | ]; 88 | 89 | helper::set_page_fake_url(); 90 | $PAGE->blocks->load_blocks(); 91 | foreach ($PAGE->blocks->get_addable_blocks() as $block) { 92 | if ($block->name == 'multiblock') { 93 | continue; 94 | } 95 | 96 | $this->blocklist[$block->name] = trim($block->title) ? trim($block->title) : '[block_' . $block->name . ']'; 97 | } 98 | helper::set_page_real_url(); 99 | } 100 | 101 | /** 102 | * Finds other blocks in the same place to be merged in. 103 | * 104 | * @param int $instanceid The instance of a multiblock to find other blocks in the same context. 105 | */ 106 | public function get_sibling_blocks($instanceid) { 107 | global $DB; 108 | 109 | $available = []; 110 | $invalidstatuses = [ 111 | core_plugin_manager::PLUGIN_STATUS_MISSING, 112 | core_plugin_manager::PLUGIN_STATUS_DOWNGRADE, 113 | core_plugin_manager::PLUGIN_STATUS_DELETE, 114 | ]; 115 | foreach (core_plugin_manager::instance()->get_plugins_of_type('block') as $block) { 116 | if (!in_array($block->get_status(), $invalidstatuses)) { 117 | $available[] = $block->name; 118 | } 119 | } 120 | 121 | // First we have to find the block's parent context, then the blocks in that context. 122 | $record = $DB->get_record('block_instances', ['id' => $instanceid]); 123 | $siblings = $DB->get_records('block_instances', ['parentcontextid' => $record->parentcontextid]); 124 | // And remove the current block, we can't add ourselves to ourself now... 125 | unset ($siblings[$instanceid]); 126 | 127 | $blocks = []; 128 | foreach ($siblings as $instanceid => $sibling) { 129 | // Can't add a multiblock to itself in any universe. 130 | if ($sibling->blockname == 'multiblock') { 131 | continue; 132 | } 133 | 134 | // Make sure that the plugin actually exists. 135 | if (!in_array($sibling->blockname, $available)) { 136 | continue; 137 | } 138 | 139 | // Does it have a title? 140 | if (!empty($sibling->configdata)) { 141 | $config = unserialize(base64_decode($sibling->configdata)); 142 | if (!empty($config->title)) { 143 | $blocks[$instanceid] = $config->title . ' (' . get_string('pluginname', 'block_' . $sibling->blockname) . ')'; 144 | continue; 145 | } 146 | } 147 | 148 | // Add it to the list. 149 | $blocks[$instanceid] = get_string('pluginname', 'block_' . $sibling->blockname); 150 | } 151 | 152 | return $blocks; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /classes/form/editblock.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Form for editing a multiblock subblock. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\form; 26 | use block_multiblock; 27 | use block_multiblock\form\editblock_constructor; 28 | 29 | defined('MOODLE_INTERNAL') || die(); 30 | 31 | require_once($CFG->libdir . '/formslib.php'); 32 | 33 | /** 34 | * Form for editing a multiblock subblock in Moodle. 35 | * 36 | * @package block_multiblock 37 | * @copyright 2019 Peter Spicer 38 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 | */ 40 | class editblock extends editblock_base { 41 | 42 | /** 43 | * Sets up the form definition - this will intentionally override the normal block 44 | * block configuration so we only get the parts specific to the subblock. 45 | */ 46 | public function definition() { 47 | $mform =& $this->_form; 48 | 49 | $this->specific_definition($mform); 50 | 51 | if (!empty($this->multiblock) && $mform->elementExists('config_title')) { 52 | $presentations = block_multiblock::get_valid_presentations(); 53 | $defaultconfig = (object) ['presentation' => block_multiblock::get_default_presentation()]; 54 | $config = !empty($this->multiblock->config) ? $this->multiblock->config : $defaultconfig; 55 | if ($presentations[$config->presentation]->requires_title()) { 56 | $requiredmsg = get_string('requirestitle', 'block_multiblock', $this->multiblock->get_title()); 57 | $mform->addRule('config_title', $requiredmsg, 'required', null, 'client'); 58 | } 59 | } 60 | 61 | $this->add_save_buttons(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /classes/form/editblock_base.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Base class for common support when editing a multiblock subblock. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\form; 26 | 27 | use block_multiblock_proxy_edit_form; 28 | 29 | use moodle_page; 30 | use block_base; 31 | /** 32 | * Base class for common support when editing a multiblock subblock. 33 | * 34 | * Note that this extends block_multiblock_proxy_edit_form - this does 35 | * not actually exist. This is an alias to whichever edit_form that the 36 | * subblock would instantiate, so that we can overlay our settings on 37 | * top and not deal with the full set of block settings which won't be 38 | * relevant. 39 | * 40 | * @package block_multiblock 41 | * @copyright 2019 Peter Spicer 42 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 43 | */ 44 | class editblock_base extends block_multiblock_proxy_edit_form { 45 | 46 | /** @var object The multiblock object being edited */ 47 | public $multiblock; 48 | 49 | /** 50 | * The block instance we are editing. 51 | * @var block_base 52 | */ 53 | private $_block; 54 | /** 55 | * The page we are editing this block in association with. 56 | * @var moodle_page 57 | */ 58 | private $_page; 59 | 60 | /** 61 | * Page where we are adding or editing the block 62 | * 63 | * To access you can also use magic property $this->page 64 | * 65 | * @return moodle_page 66 | */ 67 | protected function get_page(): moodle_page { 68 | if (!$this->_page && !empty($this->_customdata['page'])) { 69 | $this->_page = $this->_customdata['page']; 70 | } 71 | return $this->_page; 72 | } 73 | 74 | /** 75 | * Instance of the block that is being added or edited 76 | * 77 | * To access you can also use magic property $this->block 78 | * 79 | * If {{@see self::display_form_when_adding()}} returns true and the configuration 80 | * form is displayed when adding block, the $this->block->id will be null. 81 | * 82 | * @return block_base 83 | */ 84 | protected function get_block(): block_base { 85 | $this->multiblock = $this->_customdata['multiblock']; 86 | if (!$this->_block && !empty($this->_customdata['block'])) { 87 | $this->_block = $this->_customdata['block']; 88 | } 89 | return $this->_block; 90 | } 91 | 92 | /** 93 | * Provides the save buttons for the edit-block form. 94 | */ 95 | public function add_save_buttons() { 96 | // Now we add the save buttons. 97 | $mform =& $this->_form; 98 | 99 | $buttonarray = []; 100 | $buttonarray[] = &$mform->createElement('submit', 'saveandreturn', 101 | get_string('saveandreturntomanage', 'block_multiblock')); 102 | 103 | // If the page type indicates we're not on a wildcard page, we can probably* go back there. 104 | // Note: * for some definition of probably. 105 | if (isset($this->multiblock->instance->pagetypepattern)) { 106 | if (strpos($this->multiblock->instance->pagetypepattern, '*') === false) { 107 | $buttonarray[] = &$mform->createElement('submit', 'saveanddisplay', get_string('savechangesanddisplay')); 108 | } 109 | } 110 | 111 | $buttonarray[] = &$mform->createElement('cancel'); 112 | $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false); 113 | $mform->closeHeaderBefore('buttonar'); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /classes/form/editblock_totara.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Form for editing a multiblock subblock, specific to Totara. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\form; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | require_once($CFG->libdir . '/formslib.php'); 30 | 31 | /** 32 | * Form for editing a multiblock subblock, specific to Totara. 33 | * 34 | * The Moodle version keeps it tidy by just overriding the definition 35 | * method but this isn't possible in Totara. 36 | * 37 | * @package block_multiblock 38 | * @copyright 2020 Peter Spicer 39 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 | */ 41 | class editblock_totara extends editblock_base { 42 | 43 | /** 44 | * Fix the form definition so we can actually have sub-block configuration. 45 | */ 46 | public function definition_after_data() { 47 | $mform =& $this->_form; 48 | 49 | // Fix up the fields that need to be present but whose content should be hidden. 50 | $thingstohide = [ 51 | 'cs_enable_hiding', 52 | 'cs_enable_docking', 53 | 'cs_show_header', 54 | 'cs_show_border', 55 | 'bui_contexts', 56 | 'bui_pagetypepattern', 57 | 'bui_staticpagetypepattern', 58 | 'bui_subpagepattern', 59 | 'bui_defaultregion', 60 | 'bui_defaultweight', 61 | 'bui_visible', 62 | 'bui_region', 63 | 'bui_weight', 64 | ]; 65 | 66 | foreach ($thingstohide as $element) { 67 | if (!empty($mform->_elementIndex[$element])) { 68 | $value = $mform->getElementValue($element); 69 | $mform->removeElement($element); 70 | $mform->addElement('hidden', $element, $value); 71 | } 72 | } 73 | 74 | // Just remove the things we don't care about. 75 | $thingstoremove = [ 76 | 'displayconfig', 77 | 'bui_homecontext', 78 | 'whereheader', 79 | 'onthispage', 80 | 'pagetypewarning', 81 | 'restrictpagetypes', 82 | 'submitbutton', 83 | 'cancelbutton', 84 | 'buttonar', 85 | ]; 86 | foreach ($thingstoremove as $element) { 87 | if (!empty($mform->_elementIndex[$element])) { 88 | $mform->removeElement($element); 89 | } 90 | } 91 | 92 | $this->add_save_buttons(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /classes/icon_helper.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Icon handling abstraction. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock; 26 | 27 | use core\output\flex_icon; 28 | use pix_icon; 29 | 30 | /** 31 | * Icon handling abstraction. 32 | * 33 | * Intended to abstract away Moodle 3.5+ vs Totara 12+. 34 | * 35 | * @package block_multiblock 36 | * @copyright 2020 Peter Spicer 37 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 | */ 39 | class icon_helper { 40 | 41 | /** 42 | * Returns an icon. 43 | * 44 | * For cases where the icon identifier is the same in both Totara and Moodle, 45 | * this function takes care of the general setup. 46 | * 47 | * @param string $icon The icon identifier, e.g. "t/preferences". 48 | * @param string $str The alt text for this icon. 49 | * @return mixed An icon object for rendering. Type dependent whether using Moodle or Totara. 50 | */ 51 | protected static function general_icon(string $icon, string $str) { 52 | if (class_exists('\\core\\output\\flex_icon')) { 53 | return flex_icon::get_icon($icon, 'moodle', ['class' => 'iconsmall', 'alt' => $str]); 54 | } else { 55 | return new pix_icon($icon, $str, 'moodle', ['class' => 'iconsmall']); 56 | } 57 | } 58 | 59 | /** 60 | * Returns a arrow up icon. 61 | * 62 | * @param string $str The alt text for this icon. 63 | * @return mixed An icon object for rendering. Type dependent whether using Moodle or Totara. 64 | */ 65 | public static function arrow_up(string $str) { 66 | return static::general_icon('t/up', $str); 67 | } 68 | 69 | /** 70 | * Returns a arrow down icon. 71 | * 72 | * @param string $str The alt text for this icon. 73 | * @return mixed An icon object for rendering. Type dependent whether using Moodle or Totara. 74 | */ 75 | public static function arrow_down(string $str) { 76 | return static::general_icon('t/down', $str); 77 | } 78 | 79 | /** 80 | * Returns a settings icon. 81 | * 82 | * @param string $str The alt text for this icon. 83 | * @return mixed An icon object for rendering. Type dependent whether using Moodle or Totara. 84 | */ 85 | public static function settings(string $str) { 86 | return static::general_icon('i/settings', $str); 87 | } 88 | 89 | /** 90 | * Returns a preferences icon. 91 | * 92 | * @param string $str The alt text for this icon. 93 | * @return mixed An icon object for rendering. Type dependent whether using Moodle or Totara. 94 | */ 95 | public static function preferences(string $str) { 96 | return static::general_icon('t/preferences', $str); 97 | } 98 | 99 | /** 100 | * Returns a trashcan icon. 101 | * 102 | * @param string $str The alt text for this icon. 103 | * @return mixed An icon object for rendering. Type dependent whether using Moodle or Totara. 104 | */ 105 | public static function delete(string $str) { 106 | if (class_exists('\\core\\output\\flex_icon')) { 107 | return flex_icon::get_icon('i/delete', 'moodle', ['class' => 'iconsmall', 'alt' => $str]); 108 | } else { 109 | return new pix_icon('t/delete', $str, 'moodle', ['class' => 'iconsmall']); 110 | } 111 | } 112 | 113 | /** 114 | * Returns an icon to split a subblock up a level. 115 | * 116 | * @param string $str The alt text for this icon. 117 | * @return mixed An icon object for rendering. Type dependent whether using Moodle or Totara. 118 | */ 119 | public static function level_up(string $str) { 120 | if (class_exists('\\core\\output\\flex_icon')) { 121 | return flex_icon::get_icon('level-up', 'moodle', ['class' => 'iconsmall', 'alt' => $str]); 122 | } else { 123 | return new pix_icon('i/import', $str, 'moodle', ['class' => 'iconsmall', 'alt' => $str]); 124 | } 125 | } 126 | 127 | /** 128 | * Returns a spacer icon to align action icon sets. 129 | * 130 | * @return string The rendered HTML for a spacer icon. 131 | */ 132 | public static function space() : string { 133 | global $OUTPUT; 134 | 135 | if (class_exists('\\core\\output\\flex_icon')) { 136 | return $OUTPUT->flex_icon('spacer'); 137 | } else { 138 | return $OUTPUT->pix_icon('i/empty', ''); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /classes/layout/abstract_layout.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for a base abstract layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | /** 28 | * Behaviour for a base abstract layout. 29 | * 30 | * @package block_multiblock 31 | * @copyright 2020 Peter Spicer 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | abstract class abstract_layout { 35 | 36 | /** 37 | * Returns the recommended uses for this block. 38 | * 39 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 40 | */ 41 | abstract public function get_suggested_use() : string; 42 | 43 | /** 44 | * Returns the internal ID that this layout would use and be identified by. 45 | * 46 | * Defaults to the class name. 47 | * 48 | * @return string The layout ID. 49 | */ 50 | public function get_layout_id() : string { 51 | return substr(strrchr(get_class($this), '\\'), 1); 52 | } 53 | 54 | /** 55 | * Returns the block layout's name. 56 | * 57 | * @return string The layout's name. 58 | */ 59 | public function get_name() : string { 60 | return get_string('presentation:' . $this->get_layout_id(), 'block_multiblock'); 61 | } 62 | 63 | /** 64 | * Returns whether this block layout requires a title for sub-blocks. 65 | * 66 | * @return bool True if the sub-block title is required. 67 | */ 68 | public function requires_title() : bool { 69 | return true; 70 | } 71 | 72 | /** 73 | * Returns the Mustache template required to render this block. 74 | * 75 | * @return string The Mustache template. 76 | */ 77 | public function get_template() : string { 78 | return 'block_multiblock/' . $this->get_layout_id(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /classes/layout/accordion.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for accordion layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | use block_multiblock\helper; 28 | 29 | /** 30 | * Behaviour for accordion layout. 31 | * 32 | * @package block_multiblock 33 | * @copyright 2020 Peter Spicer 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class accordion extends abstract_layout { 37 | 38 | /** 39 | * Returns the recommended uses for this block. 40 | * 41 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 42 | */ 43 | public function get_suggested_use() : string { 44 | return 'sidebar'; 45 | } 46 | 47 | /** 48 | * Returns the Mustache template required to render this block. 49 | * 50 | * @return string The Mustache template. 51 | */ 52 | public function get_template() : string { 53 | if (!helper::is_totara()) { 54 | return 'block_multiblock/accordion-bootstrap4'; 55 | } else { 56 | return 'block_multiblock/accordion-bootstrap3'; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /classes/layout/carousel.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for carousel layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | use block_multiblock\helper; 28 | 29 | /** 30 | * Behaviour for carousel layout. 31 | * 32 | * @package block_multiblock 33 | * @copyright 2020 Peter Spicer 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class carousel extends abstract_layout { 37 | 38 | /** 39 | * Returns the recommended uses for this block. 40 | * 41 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 42 | */ 43 | public function get_suggested_use() : string { 44 | return 'sidebar'; 45 | } 46 | 47 | /** 48 | * Returns the Mustache template required to render this block. 49 | * 50 | * @return string The Mustache template. 51 | */ 52 | public function get_template() : string { 53 | if (!helper::is_totara()) { 54 | return 'block_multiblock/carousel-bootstrap4'; 55 | } else { 56 | return 'block_multiblock/carousel-bootstrap3'; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /classes/layout/columns_2_33_66.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for columns-2-33-66 layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | /** 28 | * Behaviour for columns-2-33-66 layout. 29 | * 30 | * @package block_multiblock 31 | * @copyright 2020 Peter Spicer 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class columns_2_33_66 extends abstract_layout { 35 | 36 | /** 37 | * Returns the recommended uses for this block. 38 | * 39 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 40 | */ 41 | public function get_suggested_use() : string { 42 | return 'main'; 43 | } 44 | 45 | /** 46 | * Returns the internal ID that this layout would use and be identified by. 47 | * 48 | * Defaults to the class name. 49 | * 50 | * @return string The layout ID. 51 | */ 52 | public function get_layout_id() : string { 53 | return 'columns-2-33-66'; 54 | } 55 | 56 | /** 57 | * Returns whether this block layout requires a title for sub-blocks. 58 | * 59 | * @return bool True if the sub-block title is required. 60 | */ 61 | public function requires_title() : bool { 62 | return false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /classes/layout/columns_2_66_33.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for columns-2-66-33 layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | /** 28 | * Behaviour for columns-2-66-33 layout. 29 | * 30 | * @package block_multiblock 31 | * @copyright 2020 Peter Spicer 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class columns_2_66_33 extends abstract_layout { 35 | 36 | /** 37 | * Returns the recommended uses for this block. 38 | * 39 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 40 | */ 41 | public function get_suggested_use() : string { 42 | return 'main'; 43 | } 44 | 45 | /** 46 | * Returns the internal ID that this layout would use and be identified by. 47 | * 48 | * Defaults to the class name. 49 | * 50 | * @return string The layout ID. 51 | */ 52 | public function get_layout_id() : string { 53 | return 'columns-2-66-33'; 54 | } 55 | 56 | /** 57 | * Returns whether this block layout requires a title for sub-blocks. 58 | * 59 | * @return bool True if the sub-block title is required. 60 | */ 61 | public function requires_title() : bool { 62 | return false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /classes/layout/columns_2equal.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for columns-2equal layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | /** 28 | * Behaviour for columns-2equal layout. 29 | * 30 | * @package block_multiblock 31 | * @copyright 2020 Peter Spicer 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class columns_2equal extends abstract_layout { 35 | 36 | /** 37 | * Returns the recommended uses for this block. 38 | * 39 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 40 | */ 41 | public function get_suggested_use() : string { 42 | return 'main'; 43 | } 44 | 45 | /** 46 | * Returns the internal ID that this layout would use and be identified by. 47 | * 48 | * Defaults to the class name. 49 | * 50 | * @return string The layout ID. 51 | */ 52 | public function get_layout_id() : string { 53 | return 'columns-2equal'; 54 | } 55 | 56 | /** 57 | * Returns whether this block layout requires a title for sub-blocks. 58 | * 59 | * @return bool True if the sub-block title is required. 60 | */ 61 | public function requires_title() : bool { 62 | return false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /classes/layout/columns_3equal.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for columns-2equal layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | /** 28 | * Behaviour for columns-3equal layout. 29 | * 30 | * @package block_multiblock 31 | * @copyright 2020 Peter Spicer 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class columns_3equal extends abstract_layout { 35 | 36 | /** 37 | * Returns the recommended uses for this block. 38 | * 39 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 40 | */ 41 | public function get_suggested_use() : string { 42 | return 'main'; 43 | } 44 | 45 | /** 46 | * Returns the internal ID that this layout would use and be identified by. 47 | * 48 | * Defaults to the class name. 49 | * 50 | * @return string The layout ID. 51 | */ 52 | public function get_layout_id() : string { 53 | return 'columns-3equal'; 54 | } 55 | 56 | /** 57 | * Returns whether this block layout requires a title for sub-blocks. 58 | * 59 | * @return bool True if the sub-block title is required. 60 | */ 61 | public function requires_title() : bool { 62 | return false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /classes/layout/dropdown.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for dropdown layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | use block_multiblock\helper; 28 | 29 | /** 30 | * Behaviour for dropdown layout. 31 | * 32 | * @package block_multiblock 33 | * @copyright 2020 Peter Spicer 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class dropdown extends abstract_layout { 37 | 38 | /** 39 | * Returns the recommended uses for this block. 40 | * 41 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 42 | */ 43 | public function get_suggested_use() : string { 44 | return 'sidebar'; 45 | } 46 | 47 | /** 48 | * Returns the Mustache template required to render this block. 49 | * 50 | * @return string The Mustache template. 51 | */ 52 | public function get_template() : string { 53 | if (!helper::is_totara()) { 54 | return 'block_multiblock/dropdown-bootstrap4'; 55 | } else { 56 | return 'block_multiblock/dropdown-bootstrap3'; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /classes/layout/tabbed_list.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for tabbed-list layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | use block_multiblock\helper; 28 | 29 | /** 30 | * Behaviour for tabbed-list layout. 31 | * 32 | * @package block_multiblock 33 | * @copyright 2020 Peter Spicer 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class tabbed_list extends abstract_layout { 37 | 38 | /** 39 | * Returns the recommended uses for this block. 40 | * 41 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 42 | */ 43 | public function get_suggested_use() : string { 44 | return 'main'; 45 | } 46 | 47 | /** 48 | * Returns the internal ID that this layout would use and be identified by. 49 | * 50 | * Defaults to the class name. 51 | * 52 | * @return string The layout ID. 53 | */ 54 | public function get_layout_id() : string { 55 | return 'tabbed-list'; 56 | } 57 | 58 | /** 59 | * Returns the Mustache template required to render this block. 60 | * 61 | * @return string The Mustache template. 62 | */ 63 | public function get_template() : string { 64 | if (!helper::is_totara()) { 65 | return 'block_multiblock/tabbed-list-bootstrap4'; 66 | } else { 67 | return 'block_multiblock/tabbed-list-bootstrap3'; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /classes/layout/tabbed_list_columns_2_66_33.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for vertical-tabbed-list-columns-2-66-33 layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Burnett 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | use block_multiblock\helper; 28 | 29 | /** 30 | * Behaviour for vertical-tabbed-list-right layout. 31 | * 32 | * @package block_multiblock 33 | * @copyright 2020 Peter Burnett 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class tabbed_list_columns_2_66_33 extends abstract_layout { 37 | 38 | /** 39 | * Returns the recommended uses for this block. 40 | * 41 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 42 | */ 43 | public function get_suggested_use() : string { 44 | return 'main'; 45 | } 46 | 47 | /** 48 | * Returns the internal ID that this layout would use and be identified by. 49 | * 50 | * Defaults to the class name. 51 | * 52 | * @return string The layout ID. 53 | */ 54 | public function get_layout_id() : string { 55 | return 'tabbed-list-columns-2-66-33'; 56 | } 57 | 58 | /** 59 | * Returns the Mustache template required to render this block. 60 | * 61 | * @return string The Mustache template. 62 | */ 63 | public function get_template() : string { 64 | if (!helper::is_totara()) { 65 | return 'block_multiblock/tabbed-list-columns-2-66-33-bootstrap4'; 66 | } else { 67 | return 'block_multiblock/tabbed-list-columns-2-66-33-bootstrap3'; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /classes/layout/vertical_tabbed_list.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for vertical-tabbed-list layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | use block_multiblock\helper; 28 | 29 | /** 30 | * Behaviour for vertical-tabbed-list layout. 31 | * 32 | * @package block_multiblock 33 | * @copyright 2020 Peter Spicer 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class vertical_tabbed_list extends abstract_layout { 37 | 38 | /** 39 | * Returns the recommended uses for this block. 40 | * 41 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 42 | */ 43 | public function get_suggested_use() : string { 44 | return 'main'; 45 | } 46 | 47 | /** 48 | * Returns the internal ID that this layout would use and be identified by. 49 | * 50 | * Defaults to the class name. 51 | * 52 | * @return string The layout ID. 53 | */ 54 | public function get_layout_id() : string { 55 | return 'vertical-tabbed-list'; 56 | } 57 | 58 | /** 59 | * Returns the Mustache template required to render this block. 60 | * 61 | * @return string The Mustache template. 62 | */ 63 | public function get_template() : string { 64 | if (!helper::is_totara()) { 65 | return 'block_multiblock/vertical-tabbed-list-bootstrap4'; 66 | } else { 67 | return 'block_multiblock/vertical-tabbed-list-bootstrap3'; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /classes/layout/vertical_tabbed_list_right.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Behaviour for vertical-tabbed-list-right layout. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\layout; 26 | 27 | use block_multiblock\helper; 28 | 29 | /** 30 | * Behaviour for vertical-tabbed-list-right layout. 31 | * 32 | * @package block_multiblock 33 | * @copyright 2020 Peter Spicer 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class vertical_tabbed_list_right extends abstract_layout { 37 | 38 | /** 39 | * Returns the recommended uses for this block. 40 | * 41 | * @return string 'sidebar' or 'main' to suggest which is recommended for this block. 42 | */ 43 | public function get_suggested_use() : string { 44 | return 'main'; 45 | } 46 | 47 | /** 48 | * Returns the internal ID that this layout would use and be identified by. 49 | * 50 | * Defaults to the class name. 51 | * 52 | * @return string The layout ID. 53 | */ 54 | public function get_layout_id() : string { 55 | return 'vertical-tabbed-list-right'; 56 | } 57 | 58 | /** 59 | * Returns the Mustache template required to render this block. 60 | * 61 | * @return string The Mustache template. 62 | */ 63 | public function get_template() : string { 64 | if (!helper::is_totara()) { 65 | return 'block_multiblock/vertical-tabbed-list-right-bootstrap4'; 66 | } else { 67 | return 'block_multiblock/vertical-tabbed-list-right-bootstrap3'; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /classes/navigation.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Supporting infrastructure for the multiblock editing. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock; 26 | 27 | use block_multiblock\helper; 28 | use admin_category; 29 | use admin_settingpage; 30 | use context_course; 31 | use context_coursecat; 32 | use context_system; 33 | use context_user; 34 | use moodle_url; 35 | use navigation_node; 36 | 37 | /** 38 | * Supporting infrastructure for the multiblock editing. 39 | * 40 | * @package block_multiblock 41 | * @copyright 2019 Peter Spicer 42 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 43 | */ 44 | class navigation { 45 | 46 | /** 47 | * While context_block provides getting a given page's URL, 48 | * it is not always 100% consistent or reliable. So, instead, 49 | * we do it ourselves. 50 | * 51 | * @param int $blockid The block's id from mdl_block_instances. 52 | * @return moodle_url The page URL relating to that block. 53 | */ 54 | public static function get_page_url($blockid): moodle_url { 55 | global $DB, $CFG; 56 | 57 | $parentcontext = helper::find_nearest_nonblock_ancestor($blockid); 58 | $block = $DB->get_record('block_instances', ['id' => $blockid]); 59 | 60 | // If the block is in a user context, it could well be a dashboard. 61 | if ($parentcontext instanceof context_user) { 62 | if ($block->pagetypepattern == 'my-index') { 63 | return new moodle_url('/my/'); 64 | } 65 | 66 | if (strpos($block->pagetypepattern, 'totara-dashboard') !== false) { 67 | 68 | if (preg_match('~^totara-dashboard-(\d+)$~', $block->pagetypepattern, $match)) { 69 | return new moodle_url('/totara/dashboard/', ['id' => $match[1]]); 70 | } 71 | 72 | return new moodle_url('/totara/dashboard/'); 73 | } 74 | } 75 | 76 | // If this is a system context, something really interesting could be happening. 77 | if ($parentcontext instanceof context_system) { 78 | if ($block->pagetypepattern == 'my-index') { 79 | return new moodle_url('/my/indexsys.php'); 80 | } 81 | // Fix for Workplace custom pages 82 | if ($block->pagetypepattern == 'admin-tool-custompage') { 83 | return new moodle_url('/admin/tool/custompage/view.php', ['id' => $block->subpagepattern]); 84 | } 85 | if (strpos($block->pagetypepattern, 'totara-dashboard') !== false) { 86 | 87 | if (preg_match('~^totara-dashboard-(\d+)$~', $block->pagetypepattern, $match)) { 88 | return new moodle_url('/totara/dashboard/layout.php', ['id' => $match[1]]); 89 | } 90 | 91 | return new moodle_url('/totara/dashboard/layout.php', ['id' => 1]); 92 | } 93 | // If local_dboard dashboard is being customised. 94 | if (strpos($block->pagetypepattern, 'dboard') !== false) { 95 | if (preg_match('~^dboard-(\d+)$~', $block->pagetypepattern, $match)) { 96 | return new moodle_url('/local/dboard/index.php', ['id' => $match[1]]); 97 | } 98 | } 99 | return static::map_site_context_url($block->pagetypepattern, $parentcontext); 100 | } 101 | 102 | // If this is a course category, we might have the management page. 103 | if ($parentcontext instanceof context_coursecat) { 104 | if (substr($block->pagetypepattern, 0, 17) == 'course-management') { 105 | return new moodle_url('/course/management.php'); 106 | } 107 | } 108 | 109 | // If this is a course, we might have to switch between course-view and course-info. 110 | if ($parentcontext instanceof context_course) { 111 | // If this is the site course (home page), we have to get a little fancier. 112 | if ($parentcontext->instanceid == SITEID) { 113 | if (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_MY)) { 114 | return new moodle_url('/', ['redirect' => 0]); 115 | } else { 116 | return new moodle_url('/'); 117 | } 118 | } 119 | 120 | if (substr($block->pagetypepattern, 0, 11) == 'course-info') { 121 | return new moodle_url('/course/info.php', ['id' => $parentcontext->instanceid]); 122 | } else if ($block->pagetypepattern == 'course-edit') { 123 | return new moodle_url('/course/edit.php', ['id' => $parentcontext->instanceid]); 124 | } 125 | } 126 | 127 | return $parentcontext->get_url(); 128 | } 129 | 130 | /** 131 | * Maps known page-type patterns to destinations within the site context. 132 | * 133 | * @param string $pagetypepattern The page type pattern the block lists 134 | * @param context $context The parent context 135 | * @return moodle_url The URL to route to 136 | */ 137 | public static function map_site_context_url($pagetypepattern, $context): moodle_url { 138 | global $CFG; 139 | 140 | $map = [ 141 | 'admin-*' => '/admin/search.php', 142 | 'my-index' => '/my/indexsys.php', 143 | 'site-index' => '/', 144 | ]; 145 | 146 | if (isset($map[$pagetypepattern])) { 147 | return new moodle_url($map[$pagetypepattern]); 148 | } 149 | 150 | // Page type admin-setting-x can either be a category or a settings page. 151 | if (preg_match('/^admin-setting-(.*)/i', $pagetypepattern, $match)) { 152 | require_once($CFG->libdir . '/adminlib.php'); 153 | navigation_node::require_admin_tree(); 154 | 155 | $adminroot = admin_get_root(); 156 | $page = $adminroot->locate($match[1], true); 157 | 158 | if ($page instanceof admin_settingpage) { 159 | return new moodle_url('/admin/settings.php', ['section' => $match[1]]); 160 | } else if ($page instanceof admin_category) { 161 | return new moodle_url('/admin/category.php', ['category' => $match[1]]); 162 | } else if ($page instanceof admin_externalpage) { 163 | return new moodle_url($page->url); 164 | } 165 | } 166 | 167 | // Grade editing. 168 | if (strpos($pagetypepattern, 'admin-grade-') === 0) { 169 | // We can convert these from admin-grade-edit-scale-index to grade/edit/scale/index.php. 170 | $parts = explode('-', $pagetypepattern); 171 | array_shift($parts); // Remove the first part, 'admin'. 172 | return new moodle_url('/' . implode('/', $parts) . '.php'); 173 | } 174 | 175 | // Otherwise it's based on the page URL: 176 | // PTP: admin-plugins -> admin/plugins.php. 177 | // PTP: admin-tool-customlang-index -> admin/tool/customlang/index.php. 178 | if (strpos($pagetypepattern, '*') === false) { 179 | $parts = explode('-', $pagetypepattern); 180 | return new moodle_url('/' . implode('/', $parts) . '.php'); 181 | } 182 | 183 | // Just in case, we can always try the context's URL - that will get us *something*. 184 | return $context->get_url(); 185 | } 186 | 187 | /** 188 | * Identifies if the specified URL is a dashboard. 189 | * 190 | * @param moodle_url $url The URL of the page in question. 191 | * @return bool True if the page is a dashboard. 192 | */ 193 | public static function is_dashboard(moodle_url $url): bool { 194 | $local = $url->out_as_local_url(false); 195 | return strpos($local, '/my/') === 0 && strpos($local, '/my/indexsys') === false; 196 | } 197 | 198 | /** 199 | * Identifies if the specified URL is somewhere inside the admin panel. 200 | * 201 | * @param moodle_url $url The URL of the page in question. 202 | * @return bool True if the page is an admin page. 203 | */ 204 | public static function is_admin_url(moodle_url $url): bool { 205 | global $CFG; 206 | 207 | $local = $url->out_as_local_url(false); 208 | return strpos($local, '/' . $CFG->admin . '/') === 0 || strpos($local, '/my/indexsys') === 0; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /classes/output/main.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Renderable component for multiblocks. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | namespace block_multiblock\output; 25 | 26 | use renderable; 27 | use renderer_base; 28 | use templatable; 29 | use block_multiblock; 30 | 31 | /** 32 | * Renderable component for multiblocks. 33 | * 34 | * @package block_multiblock 35 | * @copyright 2019 Peter Spicer 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | */ 38 | class main implements renderable, templatable { 39 | /** @var int The id of the multiblock itself. */ 40 | private $multiblockid; 41 | 42 | /** @var array The instances of subblocks within this block to be rendered. */ 43 | private $multiblock; 44 | 45 | /** @var string The template that we're going to pass to Mustache. */ 46 | private $template; 47 | 48 | /** 49 | * Initialises the multiblock render helper. 50 | * 51 | * @param int $blockid The id of the multiblock itself. 52 | * @param array $multiblock The instances of subblocks within this block to be rendered. 53 | * @param string $template The template that we're going to pass to Mustache. 54 | */ 55 | public function __construct(int $blockid, array $multiblock, string $template) { 56 | $this->multiblockid = $blockid; 57 | $this->multiblock = $multiblock; 58 | $this->template = $template; 59 | } 60 | 61 | /** 62 | * Get the template to be rendered for the given configured presentation of this block. 63 | * 64 | * @return string The template to be rendered. 65 | */ 66 | public function get_template(): string { 67 | $presentations = block_multiblock::get_valid_presentations(); 68 | $presentation = isset($presentations[$this->template]) ? $this->template : block_multiblock::get_default_presentation(); 69 | return $presentations[$presentation]->get_template(); 70 | } 71 | 72 | /** 73 | * Export this data so it can be used as the context for a Mustache template. 74 | * 75 | * @param \renderer_base $output 76 | * @return stdClass 77 | */ 78 | public function export_for_template(renderer_base $output) { 79 | $context = [ 80 | 'multiblockid' => $this->multiblockid, 81 | 'multiblock' => $this->multiblock, 82 | 'template' => $this->template, 83 | ]; 84 | 85 | $first = true; 86 | foreach ($context['multiblock'] as $id => $block) { 87 | $context['multiblock'][$id]['active'] = $first; 88 | $first = false; 89 | } 90 | 91 | return (object) $context; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /classes/output/mobile.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | namespace block_multiblock\output; 18 | 19 | /** 20 | * Class mobile 21 | * 22 | * @package block_multiblock 23 | * @copyright 2025 Sumaiya Javed 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | class mobile { 27 | /** 28 | * Returns the view for the mobile app. 29 | * 30 | * @param array $args Arguments from tool_mobile_get_content WS 31 | * @return array HTML, javascript and otherdata 32 | */ 33 | public static function mobile_multiblock_view($args) { 34 | global $OUTPUT, $CFG, $DB; 35 | require_once($CFG->libdir . '/blocklib.php'); 36 | $multiblock = []; 37 | $blockid = $args['blockid']; 38 | $blockinstance = block_instance_by_id($blockid); 39 | $presentation = $blockinstance->config->presentation; 40 | // Only supports accordion/carousel effect. 41 | if ($presentation == 'carousel') { 42 | $template = "block_multiblock/carousel-mobile"; 43 | } else { 44 | $template = "block_multiblock/accordion-mobile"; 45 | } 46 | $data["multiblockid"] = $blockid; 47 | $context = $DB->get_record('context', ['contextlevel' => CONTEXT_BLOCK, 48 | 'instanceid' => $blockinstance->instance->id]); 49 | $blocks = $DB->get_records('block_instances', [ 50 | 'parentcontextid' => $context->id], 'defaultweight, id'); 51 | $active = array_key_first($blocks); 52 | foreach ($blocks as $id => $block) { 53 | if (block_load_class($block->blockname)) { 54 | // Make the proxy class we'll need. 55 | $subblockinstance = block_instance($block->blockname, $block); 56 | if ($subblockinstance->get_content()->text) { 57 | $content = $subblockinstance->get_content()->text; 58 | } else { 59 | $rows = $subblockinstance->get_content(); 60 | $content = ''; 61 | foreach ($rows as $key => $items) { 62 | if ($key != 'footer') { 63 | if (!empty($items) && is_array($items)) { 64 | $content = implode(" - ", $items); 65 | } 66 | } 67 | } 68 | } 69 | $multiblock[$id]["id"] = $id; 70 | $multiblock[$id]["title"] = $subblockinstance->get_title(); 71 | $multiblock[$id]["content"] = $content; 72 | } 73 | } 74 | $data = [ 75 | 'multiblockid' => $blockid, 76 | 'multiblock' => array_values($multiblock), 77 | 'active' => $active, 78 | ]; 79 | return [ 80 | 'templates' => [ 81 | [ 82 | 'id' => 'main', 83 | 'html' => $OUTPUT->render_from_template($template, $data), 84 | ], 85 | ], 86 | 'javascript' => '', 87 | ]; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /classes/output/renderer.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Multiblock renderer. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | namespace block_multiblock\output; 25 | 26 | use plugin_renderer_base; 27 | use renderable; 28 | 29 | /** 30 | * Multiblock renderer. 31 | * 32 | * @package block_multiblock 33 | * @copyright 2019 Peter Spicer 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class renderer extends plugin_renderer_base { 37 | 38 | /** 39 | * Return the main content for the block multiblock. 40 | * 41 | * @param main $main The main renderable 42 | * @return string HTML string 43 | */ 44 | public function render_main(main $main) { 45 | return $this->render_from_template($main->get_template(), $main->export_for_template($this)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /classes/privacy/provider.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Privacy Subsystem implementation for block_multiblock. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace block_multiblock\privacy; 26 | 27 | use context; 28 | use context_block; 29 | use core_privacy\local\metadata\collection; 30 | use core_privacy\local\metadata\provider as metadata_provider; 31 | use core_privacy\local\request\approved_contextlist; 32 | use core_privacy\local\request\approved_userlist; 33 | use core_privacy\local\request\contextlist; 34 | use core_privacy\local\request\core_userlist_provider as userlist_provider; 35 | use core_privacy\local\request\plugin\provider as plugin_provider; 36 | use core_privacy\local\request\userlist; 37 | 38 | /** 39 | * Privacy Subsystem implementation for block_multiblock. 40 | * 41 | * @package block_multiblock 42 | * @copyright 2020 Peter Spicer 43 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 44 | */ 45 | class provider implements metadata_provider, userlist_provider, plugin_provider { 46 | 47 | /** 48 | * Returns information about how block_multiblock stores its data. 49 | * 50 | * This plugin implements several interfaces: 51 | * - The \core_privacy\local\metadata\provider interface - Multiblock manages blocks that might store user data. 52 | * - The \core_privacy\local\request\core_userlist_provider interface - Multiblock queries dependent blocks for this data. 53 | * - The \core_privacy\local\request\plugin\provider interface - Multiblock interacts directly with core. 54 | * 55 | * @param collection $collection The initialised collection to add items to. 56 | * @return collection A listing of user data stored through this system. 57 | */ 58 | public static function get_metadata(collection $collection) : collection { 59 | $collection->link_subsystem('block', 'privacy:metadata:block'); 60 | 61 | return $collection; 62 | } 63 | 64 | /** 65 | * Get the list of contexts that contain user information for the requested user. 66 | * 67 | * @param int $userid The user to lookup. 68 | * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. 69 | */ 70 | public static function get_contexts_for_userid(int $userid) : contextlist { 71 | // This won't be the full list of contexts, this is the list of contexts of the multiblock parents. 72 | // We will resolve the full list out when fetching or pruning actual data. 73 | // Note that we can only connect blocks to user data when they're in a user context. 74 | $contextlist = new contextlist; 75 | 76 | $sql = "SELECT c.id 77 | FROM {block_instances} b 78 | INNER JOIN {context} c ON c.instanceid = b.id AND c.contextlevel = :contextblock 79 | INNER JOIN {context} bpc ON bpc.id = b.parentcontextid 80 | WHERE b.blockname = :blockname 81 | AND bpc.contextlevel = :contextuser 82 | AND bpc.instanceid = :userid"; 83 | 84 | $params = [ 85 | 'blockname' => 'multiblock', 86 | 'contextblock' => CONTEXT_BLOCK, 87 | 'contextuser' => CONTEXT_USER, 88 | 'userid' => $userid, 89 | ]; 90 | 91 | $contextlist->add_from_sql($sql, $params); 92 | 93 | return $contextlist; 94 | } 95 | 96 | 97 | /** 98 | * Get the list of users who have data within a context. 99 | * 100 | * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 101 | */ 102 | public static function get_users_in_context(userlist $userlist) { 103 | // The users of a given multiblock context are the ones who own it; subblocks just inherit the parent context. 104 | // By extension this means they inherit the parent owner too. 105 | $context = $userlist->get_context(); 106 | 107 | if (!is_a($context, context_block::class)) { 108 | return; 109 | } 110 | 111 | $params = [ 112 | 'blockname' => 'multiblock', 113 | 'contextid' => $context->id, 114 | 'contextuser' => CONTEXT_USER, 115 | ]; 116 | 117 | $sql = "SELECT bpc.instanceid AS userid 118 | FROM {context} c 119 | JOIN {block_instances} bi ON bi.id = c.instanceid AND bi.blockname = :blockname 120 | JOIN {context} bpc ON bpc.id = bi.parentcontextid AND bpc.contextlevel = :contextuser 121 | WHERE c.id = :contextid"; 122 | 123 | $userlist->add_from_sql('userid', $sql, $params); 124 | } 125 | 126 | /** 127 | * Export all user data for the specified user, in the specified contexts. 128 | * 129 | * Unlike other parts of the privacy provider, this time we actually fetch the sub-block data. 130 | * 131 | * @param approved_contextlist $contextlist The approved contexts to export information for. 132 | */ 133 | public static function export_user_data(approved_contextlist $contextlist) { 134 | global $DB; 135 | 136 | $user = $contextlist->get_user(); 137 | 138 | list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); 139 | 140 | $sql = "SELECT c.id AS contextid, bi.* 141 | FROM {context} c 142 | JOIN {block_instances} bi ON bi.id = c.instanceid AND c.contextlevel = :contextlevel 143 | WHERE bi.blockname = :blockname 144 | AND (c.id {$contextsql})"; 145 | 146 | $params = [ 147 | 'blockname' => 'multiblock', 148 | 'contextlevel' => CONTEXT_BLOCK, 149 | ]; 150 | $params += $contextparams; 151 | 152 | $multiblocks = $DB->get_recordset_sql($sql, $params); 153 | $subblockcontexts = []; 154 | 155 | // We need to build contextlists for each of the subblocks, with their aggregate context lists. 156 | foreach ($multiblocks as $multiblock) { 157 | $subblock = "SELECT c.id AS contextid, bi.blockname 158 | FROM {context} c 159 | JOIN {block_instances} bi ON bi.id = c.instanceid AND c.contextlevel = :contextlevel 160 | WHERE bi.parentcontextid = :parentcontext"; 161 | $subblockparams = [ 162 | 'contextlevel' => CONTEXT_BLOCK, 163 | 'parentcontext' => $multiblock->contextid, 164 | ]; 165 | 166 | // Now step through all the subblocks of this particular block and query it. 167 | $subblocks = $DB->get_records_sql($subblock, $subblockparams); 168 | foreach ($subblocks as $subblock) { 169 | $subblockcontexts['block_' . $subblock->blockname][] = $subblock->contextid; 170 | } 171 | } 172 | 173 | foreach ($subblockcontexts as $component => $contexts) { 174 | $componentcontextlist = new approved_contextlist($user, $component, $contexts); 175 | $classname = $component . '\\privacy\\provider'; 176 | if (class_exists($classname) && method_exists($classname, 'export_user_data')) { 177 | $classname::export_user_data($componentcontextlist); 178 | } 179 | } 180 | } 181 | 182 | /** 183 | * Delete all data for all users in the specified context. 184 | * 185 | * @param context $context The specific context to delete data for. 186 | */ 187 | public static function delete_data_for_all_users_in_context(context $context) { 188 | if (!$context instanceof context_block) { 189 | return; 190 | } 191 | 192 | // The only way to delete data for the html block is to delete the block instance itself. 193 | if ($blockinstance = static::get_instance_from_context($context)) { 194 | blocks_delete_instance($blockinstance); 195 | } 196 | } 197 | 198 | /** 199 | * Delete multiple users within a single context. 200 | * 201 | * This will delete the main multiblocks, which will also delete all child blocks. 202 | * 203 | * @param approved_userlist $userlist The approved context and user information to delete information for. 204 | */ 205 | public static function delete_data_for_users(approved_userlist $userlist) { 206 | $context = $userlist->get_context(); 207 | 208 | if (!$context instanceof context_block) { 209 | return; 210 | } 211 | 212 | if ($blockinstance = static::get_instance_from_context($context)) { 213 | blocks_delete_instance($blockinstance); 214 | } 215 | } 216 | 217 | /** 218 | * Delete all user data for the specified user, in the specified contexts. 219 | * 220 | * This will delete the main multiblocks, which will also delete all child blocks. 221 | * 222 | * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. 223 | */ 224 | public static function delete_data_for_user(approved_contextlist $contextlist) { 225 | foreach ($contextlist as $context) { 226 | if (!$context instanceof context_block) { 227 | continue; 228 | } 229 | 230 | if ($blockinstance = static::get_instance_from_context($context)) { 231 | blocks_delete_instance($blockinstance); 232 | } 233 | } 234 | } 235 | 236 | /** 237 | * Get the block instance record for the specified context. 238 | * 239 | * @param context_block $context The context to fetch 240 | * @return stdClass 241 | */ 242 | protected static function get_instance_from_context(context_block $context) { 243 | global $DB; 244 | 245 | return $DB->get_record('block_instances', ['id' => $context->instanceid, 'blockname' => 'multiblock']); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /configinstance.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Manage multiblock instances. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | use block_multiblock\helper; 26 | use block_multiblock\navigation; 27 | 28 | require(__DIR__ . '/../../config.php'); 29 | 30 | require_once($CFG->libdir.'/tablelib.php'); 31 | 32 | $blockid = required_param('id', PARAM_INT); 33 | $actionableinstance = required_param('instance', PARAM_INT); 34 | 35 | require_login(); 36 | list($block, $blockinstance, $blockmanager) = helper::bootstrap_page($blockid); 37 | 38 | $pageurl = new moodle_url('/blocks/multiblock/configinstance.php', ['id' => $blockid, 'instance' => $actionableinstance]); 39 | helper::set_page_real_url($pageurl); 40 | 41 | $blockmanager->show_only_fake_blocks(true); 42 | 43 | $blockctx = context_block::instance($blockid); 44 | $multiblockblocks = $blockinstance->load_multiblocks($blockctx->id); 45 | if (!isset($multiblockblocks[$actionableinstance])) { 46 | redirect(new moodle_url('/blocks/multiblock/manage.php', ['id' => $blockid, 'sesskey' => sesskey()])); 47 | } 48 | 49 | $PAGE->navbar->add($multiblockblocks[$actionableinstance]->blockinstance->get_title()); 50 | 51 | $formfile = $CFG->dirroot . '/blocks/' . $multiblockblocks[$actionableinstance]->blockinstance->name() . '/edit_form.php'; 52 | $classname = ''; 53 | if (is_readable($formfile)) { 54 | require_once($CFG->dirroot . '/blocks/edit_form.php'); 55 | require_once($formfile); 56 | $classname = 'block_' . $multiblockblocks[$actionableinstance]->blockinstance->name() . '_edit_form'; 57 | } 58 | 59 | if (!$classname || !class_exists($classname)) { 60 | throw new \Exception('Could not load block configuration for ' . $classname); 61 | } 62 | 63 | class_alias($classname, 'block_multiblock_proxy_edit_form'); 64 | $editform = helper::get_edit_form($pageurl, $multiblockblocks[$actionableinstance], $PAGE, $blockinstance); 65 | 66 | if ($editform->is_cancelled()) { 67 | redirect(new moodle_url('/blocks/multiblock/manage.php', ['id' => $blockid, 'sesskey' => sesskey()])); 68 | } else if ($data = $editform->get_data()) { 69 | $config = new stdClass; 70 | 71 | // Totara has some common config that it handles separately to everything else. 72 | if (method_exists($editform->block, 'serialize_common_config')) { 73 | 74 | $editform->block->validate_common_config_value($data); 75 | $commonconfig = $editform->block->serialize_common_config($editform->split_common_settings_data($data)); 76 | $multiblockblocks[$actionableinstance]->common_config = $commonconfig; 77 | $DB->update_record('block_instances', $multiblockblocks[$actionableinstance]); 78 | } 79 | 80 | foreach ($data as $configfield => $value) { 81 | if (strpos($configfield, 'config_') !== 0) { 82 | continue; 83 | } 84 | $field = substr($configfield, 7); 85 | $config->$field = $value; 86 | } 87 | $multiblockblocks[$actionableinstance]->blockinstance->instance_config_save($config); 88 | 89 | // If we pressed save and display, go to the page where the block lives. 90 | if (!empty($data->saveanddisplay)) { 91 | redirect(navigation::get_page_url($blockid)); 92 | } 93 | 94 | // Otherwise return to the management page. 95 | redirect(new moodle_url('/blocks/multiblock/manage.php', ['id' => $blockid, 'sesskey' => sesskey()])); 96 | } 97 | 98 | echo $OUTPUT->header(); 99 | 100 | $editform->set_data($multiblockblocks[$actionableinstance]->blockinstance->instance); 101 | $editform->display(); 102 | 103 | echo $OUTPUT->footer(); 104 | -------------------------------------------------------------------------------- /db/access.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Multiblock block capabilities. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | $capabilities = array( 28 | 29 | 'block/multiblock:myaddinstance' => array( 30 | 'captype' => 'write', 31 | 'contextlevel' => CONTEXT_SYSTEM, 32 | 'archetypes' => array( 33 | 'user' => CAP_ALLOW 34 | ), 35 | 36 | 'clonepermissionsfrom' => 'moodle/my:manageblocks' 37 | ), 38 | 39 | 'block/multiblock:addinstance' => array( 40 | 'captype' => 'write', 41 | 'contextlevel' => CONTEXT_BLOCK, 42 | 'archetypes' => array( 43 | 'editingteacher' => CAP_ALLOW, 44 | 'manager' => CAP_ALLOW 45 | ), 46 | 47 | 'clonepermissionsfrom' => 'moodle/site:manageblocks' 48 | ), 49 | ); 50 | -------------------------------------------------------------------------------- /db/mobile.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Mobile app areas for Multiblock 19 | * 20 | * Documentation: {@link https://moodledev.io/general/app/development/plugins-development-guide} 21 | * 22 | * @package block_multiblock 23 | * @copyright 2025 Sumaiya Javed 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | $addons = [ 30 | 'block_multiblock' => [ 31 | 'handlers' => [ // Different places where the add-on will display content. 32 | 'multiblock' => [ // Handler unique name (can be anything). 33 | 'delegate' => 'CoreBlockDelegate', 34 | 'method' => 'mobile_multiblock_view', 35 | ], 36 | ], 37 | 'lang' => [ 38 | ['pluginname', 'block_multiblock'], 39 | ], 40 | ], 41 | ]; 42 | -------------------------------------------------------------------------------- /db/uninstall.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Multiblock uninstallation. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | use block_multiblock\helper; 26 | 27 | /** 28 | * Clean up multiblocks when uninstalling. 29 | * 30 | * Finds every multiblock instance and decomposes any blocks in them back to parent context. 31 | */ 32 | function xmldb_block_multiblock_uninstall() : bool { 33 | global $DB; 34 | 35 | // Set up the queries we're going to be using. 36 | $sql = "SELECT c.id AS contextid, bi.* 37 | FROM {context} c 38 | JOIN {block_instances} bi ON bi.id = c.instanceid AND c.contextlevel = :contextlevel 39 | WHERE bi.blockname = :blockname"; 40 | 41 | $params = [ 42 | 'blockname' => 'multiblock', 43 | 'contextlevel' => CONTEXT_BLOCK, 44 | ]; 45 | 46 | $childsql = "SELECT c.id AS contextid, bi.id AS blockid 47 | FROM {context} c 48 | JOIN {block_instances} bi ON bi.id = c.instanceid AND c.contextlevel = :contextlevel 49 | WHERE bi.parentcontextid = :parentcontext"; 50 | 51 | $multiblocks = $DB->get_records_sql($sql, $params); 52 | foreach ($multiblocks as $multiblock) { 53 | // Now we have the blocks, find each block's children. 54 | $childparams = [ 55 | 'contextlevel' => CONTEXT_BLOCK, 56 | 'parentcontext' => $multiblock->contextid, 57 | ]; 58 | $children = $DB->get_records_sql($childsql, $childparams); 59 | 60 | // And split each child out of the parent. 61 | foreach ($children as $childblock) { 62 | helper::split_block($multiblock, $childblock->blockid); 63 | } 64 | } 65 | 66 | return true; 67 | } 68 | -------------------------------------------------------------------------------- /edit_form.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Form for editing multiblock parent block instances. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | /** 26 | * Form for editing multiblock parent block instances. 27 | * 28 | * @package block_multiblock 29 | * @copyright 2019 Peter Spicer 30 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 | */ 32 | class block_multiblock_edit_form extends block_edit_form { 33 | 34 | /** 35 | * Adds the specific settings from this block to the general block editing form. 36 | * 37 | * @param moodleform $mform The block editing form object. 38 | */ 39 | protected function specific_definition($mform) { 40 | global $CFG; 41 | // Let's have some configuration! 42 | $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); 43 | 44 | // First, the block's title. 45 | $mform->addElement('text', 'config_title', get_string('blocktitle', 'block_multiblock')); 46 | $mform->setType('config_title', PARAM_TEXT); 47 | $mform->setDefault('config_title', get_config('block_multiblock', 'title')); 48 | 49 | // Then which presentation format we want to use. 50 | $presentations = block_multiblock::get_valid_presentations(); 51 | $options = []; 52 | foreach ($presentations as $presentationid => $presentation) { 53 | $suggested = $presentation->get_suggested_use(); 54 | if (!in_array($suggested, ['main', 'sidebar'])) { 55 | continue; 56 | } 57 | 58 | $suggestedstring = get_string('layout:' . $suggested, 'block_multiblock'); 59 | 60 | if (!isset($options[$suggestedstring])) { 61 | $options[$suggestedstring] = []; 62 | } 63 | $options[$suggestedstring][$presentationid] = $presentation->get_name(); 64 | } 65 | $mform->addElement('selectgroups', 'config_presentation', get_string('presentation', 'block_multiblock'), $options); 66 | $mform->setDefault('config_presentation', block_multiblock::get_default_presentation()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lang/en/block_multiblock.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Strings for component 'block_multiblock', language 'en' 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | $string['pluginname'] = 'Multiblock'; 26 | 27 | $string['addnewblock'] = 'Add a new sub-block'; 28 | $string['blocktitle'] = 'Block title'; 29 | $string['editblock'] = 'Edit block'; 30 | $string['manageblocklocation'] = 'Block location'; 31 | $string['managemultiblock'] = 'Manage {$a} contents'; 32 | $string['managemultiblocktitle'] = 'Manage multiblock: {$a}'; 33 | $string['moveexistingblock'] = 'Move existing block'; 34 | $string['movetoparentpage'] = 'Move to parent page'; 35 | $string['multiblockhasnosubblocks'] = 'This multiblock has no blocks inside it.'; 36 | $string['requirestitle'] = 'The {$a} presentation requires a title to work properly.'; 37 | $string['selectblock'] = 'Select block...'; 38 | $string['saveandreturntomanage'] = 'Save and return to manage'; 39 | $string['splitanddelete'] = 'Split and delete {$a} block'; 40 | $string['notitle'] = 'no title'; 41 | 42 | $string['layout:main'] = 'Recommended for full width/dashboard use'; 43 | $string['layout:sidebar'] = 'Recommended for sidebar use'; 44 | 45 | $string['multiblock_title'] = 'Multiblock Title'; 46 | $string['multiblock_title_desc'] = 'This title will be displayed as the heading of multiblock in the course.'; 47 | $string['multiblock_presentation_style'] = 'Multiblock presentation style'; 48 | $string['multiblock_presentation_style_desc'] = 'Select a multiblock presentation style to enhance the personal experience'; 49 | $string['multiblock_subblock'] = 'Multiblock - Subblocks'; 50 | $string['multiblock_subblock_desc'] = 'Select the subblocks to be added by default to a new multiblock instance. '; 51 | $string['multiblock:addinstance'] = 'Add a Multiblock'; 52 | $string['multiblock:myaddinstance'] = 'Add a Multiblock to Dashboard'; 53 | 54 | $string['presentation'] = 'Multiblock presentation style'; 55 | $string['presentation:accordion'] = 'Accordion'; 56 | $string['presentation:carousel'] = 'Carousel'; 57 | $string['presentation:columns-2-33-66'] = 'Columns: 2 (33% / 66%)'; 58 | $string['presentation:columns-2equal'] = 'Columns: 2 equal'; 59 | $string['presentation:columns-2-66-33'] = 'Columns: 2 (66% / 33%)'; 60 | $string['presentation:columns-3equal'] = 'Columns: 3 equal'; 61 | $string['presentation:dropdown'] = 'Dropdown'; 62 | $string['presentation:tabbed-list'] = 'Tabs'; 63 | $string['presentation:tabbed-list-columns-2-66-33'] = 'Tabs: Columns: 2 (66% / 33%)'; 64 | $string['presentation:vertical-tabbed-list'] = 'Vertical Tabs (Left)'; 65 | $string['presentation:vertical-tabbed-list-right'] = 'Vertical Tabs (Right)'; 66 | 67 | $string['privacy:metadata:block'] = 'The Multiblock block stores all of its data within the block subsystem.'; 68 | 69 | $string['table:actions'] = 'Actions'; 70 | $string['table:blocktitle'] = 'Block title'; 71 | $string['table:blocktype'] = 'Block type'; 72 | $string['table:lastupdated'] = 'Updated'; 73 | -------------------------------------------------------------------------------- /manage.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Manage multiblock instances. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | use block_multiblock\helper; 26 | use block_multiblock\icon_helper; 27 | use block_multiblock\navigation; 28 | 29 | require(__DIR__ . '/../../config.php'); 30 | 31 | require_once($CFG->libdir.'/tablelib.php'); 32 | 33 | $blockid = required_param('id', PARAM_INT); 34 | $actionableinstance = optional_param('instance', 0, PARAM_INT); 35 | $performaction = optional_param('action', '', PARAM_TEXT); 36 | 37 | require_login(); 38 | list($block, $blockinstance, $blockmanager) = helper::bootstrap_page($blockid); 39 | 40 | // Now we've done permissions checks, reset the URL to be the real one. 41 | $pageurl = new moodle_url('/blocks/multiblock/manage.php', ['id' => $blockid]); 42 | helper::set_page_real_url($pageurl); 43 | 44 | $blockmanager->show_only_fake_blocks(true); 45 | 46 | $blockctx = context_block::instance($blockid); 47 | $multiblockblocks = $blockinstance->load_multiblocks($blockctx->id); 48 | 49 | // Set up the add block routine. 50 | $forcereload = false; 51 | $addblock = new \block_multiblock\form\addblock($pageurl, ['id' => $blockid]); 52 | if ($newblockdata = $addblock->get_data()) { 53 | if (!empty($newblockdata->addsubmit) && $newblockdata->addblock) { 54 | $position = 1; 55 | foreach ($multiblockblocks as $instance) { 56 | if ((int) $instance->defaultweight > $position) { 57 | $position = (int) $instance->defaultweight; 58 | } 59 | } 60 | 61 | // Add the block to the parent context, then move it in. 62 | $blockmanager->add_block($newblockdata->addblock, $blockmanager->get_default_region(), $position + 1, 63 | $block->showinsubcontexts); 64 | // Helpfully, $blockmanager won't give us back the id it just added, so we have to go find it. 65 | $conditions = [ 66 | 'blockname' => $newblockdata->addblock, 67 | 'parentcontextid' => $PAGE->context->id, 68 | ]; 69 | $lastinserted = $DB->get_records('block_instances', $conditions, 'id DESC', 'id', 0, 1); 70 | if ($lastinserted) { 71 | helper::move_block(current($lastinserted)->id, $blockid); 72 | } 73 | 74 | // Now we need to re-prep the table exist. 75 | $forcereload = true; 76 | } else if (!empty($newblockdata->movesubmit) && !empty($newblockdata->moveblock)) { 77 | // Merge it in and then reprep the table and form. 78 | helper::move_block($newblockdata->moveblock, $blockid); 79 | $forcereload = true; 80 | } 81 | } else if ($performaction) { 82 | switch ($performaction) { 83 | case 'moveup': 84 | $positions = array_keys($multiblockblocks); 85 | if (in_array($actionableinstance, $positions) && $positions[0] != $actionableinstance) { 86 | $current = array_search($actionableinstance, $positions); 87 | $temp = $positions[$current - 1]; 88 | $positions[$current - 1] = $positions[$current]; 89 | $positions[$current] = $temp; 90 | } 91 | foreach ($positions as $position => $actionableinstance) { 92 | $new = (object) [ 93 | 'id' => $actionableinstance, 94 | 'defaultweight' => $position + 1, 95 | ]; 96 | $DB->update_record('block_instances', $new); 97 | } 98 | $forcereload = true; 99 | break; 100 | case 'movedown': 101 | $positions = array_keys($multiblockblocks); 102 | if (in_array($actionableinstance, $positions) && $positions[count($positions) - 1] != $actionableinstance) { 103 | $current = array_search($actionableinstance, $positions); 104 | $temp = $positions[$current + 1]; 105 | $positions[$current + 1] = $positions[$current]; 106 | $positions[$current] = $temp; 107 | } 108 | foreach ($positions as $position => $actionableinstance) { 109 | $new = (object) [ 110 | 'id' => $actionableinstance, 111 | 'defaultweight' => $position + 1, 112 | ]; 113 | $DB->update_record('block_instances', $new); 114 | } 115 | $forcereload = true; 116 | break; 117 | case 'split': 118 | helper::split_block($blockinstance->instance, $actionableinstance); 119 | $forcereload = true; 120 | break; 121 | case 'delete': 122 | blocks_delete_instance($multiblockblocks[$actionableinstance]); 123 | $forcereload = true; 124 | break; 125 | case 'splitdelete': 126 | $parenturl = navigation::get_page_url($blockid); 127 | foreach (array_keys($multiblockblocks) as $childid) { 128 | helper::split_block($blockinstance->instance, $childid); 129 | } 130 | blocks_delete_instance($blockinstance->instance); 131 | redirect($parenturl); 132 | break; 133 | } 134 | } 135 | 136 | // And begin our output. 137 | echo $OUTPUT->header(); 138 | 139 | if ($forcereload) { 140 | $multiblockblocks = $blockinstance->load_multiblocks($blockctx->id); 141 | unset($_POST['addblock'], $_POST['moveblock']); // Reset the form element so it doesn't attempt to reuse values it had before. 142 | $addblock = new \block_multiblock\form\addblock($pageurl, ['id' => $blockid]); 143 | } 144 | 145 | if (empty($multiblockblocks)) { 146 | echo html_writer::tag('p', get_string('multiblockhasnosubblocks', 'block_multiblock')); 147 | } else { 148 | $table = new flexible_table('block_multiblock_admin'); 149 | 150 | $headers = [ 151 | 'title' => get_string('table:blocktitle', 'block_multiblock'), 152 | 'type' => get_string('table:blocktype', 'block_multiblock'), 153 | 'actions' => get_string('table:actions', 'block_multiblock'), 154 | ]; 155 | if (!helper::is_totara()) { 156 | $headers['updated'] = get_string('table:lastupdated', 'block_multiblock'); 157 | } 158 | $table->define_columns(array_keys($headers)); 159 | $table->define_headers(array_values($headers)); 160 | $table->define_baseurl(new moodle_url('/blocks/multiblock/manage.php', ['id' => $blockid])); 161 | $table->set_attribute('class', 'admintable blockstable generaltable'); 162 | $table->set_attribute('id', 'multiblocktable'); 163 | $table->sortable(false); 164 | $table->setup(); 165 | 166 | $first = 0; 167 | $last = 0; 168 | foreach ($multiblockblocks as $instance) { 169 | if (!$first) { 170 | $first = $instance->id; 171 | } 172 | $last = $instance->id; 173 | } 174 | 175 | foreach ($multiblockblocks as $instance) { 176 | $actions = ''; 177 | $baseactionurl = new moodle_url('/blocks/multiblock/manage.php', [ 178 | 'id' => $blockid, 179 | 'instance' => $instance->id, 180 | 'sesskey' => sesskey() 181 | ]); 182 | 183 | // Molve the sub-block up, if it's not the first one. 184 | if ($instance->id != $first) { 185 | $url = $baseactionurl; 186 | $url->params(['action' => 'moveup']); 187 | $actions .= $OUTPUT->action_icon($url, icon_helper::arrow_up(get_string('moveup'))); 188 | } else { 189 | $actions .= icon_helper::space(); 190 | } 191 | 192 | // Move sub-block down, if it's not the last one. 193 | if ($instance->id != $last) { 194 | $url = $baseactionurl; 195 | $url->params(['action' => 'movedown']); 196 | $actions .= $OUTPUT->action_icon($url, icon_helper::arrow_down(get_string('movedown'))); 197 | } else { 198 | $actions .= icon_helper::space(); 199 | } 200 | 201 | // Edit settings button. 202 | if (file_exists($CFG->dirroot . '/blocks/' . $instance->blockinstance->name() . '/edit_form.php')) { 203 | $url = new moodle_url('/blocks/multiblock/configinstance.php', [ 204 | 'id' => $blockid, 205 | 'instance' => $instance->id, 206 | 'sesskey' => sesskey(), 207 | ]); 208 | $actions .= $OUTPUT->action_icon($url, icon_helper::settings(get_string('settings'))); 209 | } else { 210 | $actions .= icon_helper::space(); 211 | } 212 | 213 | // Split out to parent context. 214 | $url = $baseactionurl; 215 | $url->params(['action' => 'split']); 216 | $actions .= $OUTPUT->action_icon($url, icon_helper::level_up(get_string('movetoparentpage', 'block_multiblock'))); 217 | 218 | // Delete button. 219 | $url = $baseactionurl; 220 | $url->params(['action' => 'delete']); 221 | $actions .= $OUTPUT->action_icon($url, icon_helper::delete(get_string('delete'))); 222 | 223 | $notitle = html_writer::tag('em', get_string('notitle', 'block_multiblock'), ['class' => 'text-muted']); 224 | 225 | $row = [ 226 | !empty($instance->blockinstance->get_title()) ? $instance->blockinstance->get_title() : $notitle, 227 | get_string('pluginname', 'block_' . $instance->blockinstance->name()), 228 | $actions, 229 | ]; 230 | if (!helper::is_totara()) { 231 | $row[] = userdate($instance->timemodified, get_string('strftimedatetime', 'core_langconfig')); 232 | } 233 | $table->add_data($row); 234 | } 235 | 236 | $table->print_html(); 237 | } 238 | 239 | echo html_writer::empty_tag('hr'); 240 | $addblock->display(); 241 | 242 | echo $OUTPUT->footer(); 243 | -------------------------------------------------------------------------------- /pix/accordion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalyst/moodle-block_multiblock/9e99079464d931691e0d8a73a4c03220a48e5639/pix/accordion.png -------------------------------------------------------------------------------- /pix/columns-2-33-66.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalyst/moodle-block_multiblock/9e99079464d931691e0d8a73a4c03220a48e5639/pix/columns-2-33-66.png -------------------------------------------------------------------------------- /pix/columns-2-66-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalyst/moodle-block_multiblock/9e99079464d931691e0d8a73a4c03220a48e5639/pix/columns-2-66-33.png -------------------------------------------------------------------------------- /pix/columns-2-equal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalyst/moodle-block_multiblock/9e99079464d931691e0d8a73a4c03220a48e5639/pix/columns-2-equal.png -------------------------------------------------------------------------------- /pix/columns-3-equal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalyst/moodle-block_multiblock/9e99079464d931691e0d8a73a4c03220a48e5639/pix/columns-3-equal.png -------------------------------------------------------------------------------- /pix/dropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalyst/moodle-block_multiblock/9e99079464d931691e0d8a73a4c03220a48e5639/pix/dropdown.png -------------------------------------------------------------------------------- /pix/tabbed-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalyst/moodle-block_multiblock/9e99079464d931691e0d8a73a4c03220a48e5639/pix/tabbed-list.png -------------------------------------------------------------------------------- /pix/tabs-columns-2-66-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalyst/moodle-block_multiblock/9e99079464d931691e0d8a73a4c03220a48e5639/pix/tabs-columns-2-66-33.png -------------------------------------------------------------------------------- /pix/vertical-tabbed-list-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalyst/moodle-block_multiblock/9e99079464d931691e0d8a73a4c03220a48e5639/pix/vertical-tabbed-list-right.png -------------------------------------------------------------------------------- /pix/vertical-tabbed-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalyst/moodle-block_multiblock/9e99079464d931691e0d8a73a4c03220a48e5639/pix/vertical-tabbed-list.png -------------------------------------------------------------------------------- /settings.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Settings for the Moodle Multiblock. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2021 Muhammad Ali 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die; 26 | 27 | if ($ADMIN->fulltree) { 28 | global $DB, $PAGE, $CFG; 29 | 30 | $blocks = $DB->get_records('block', array('visible' => 1), 'name ASC'); 31 | 32 | // Multiblock title (heading). 33 | $settings->add(new admin_setting_configtext('block_multiblock/title', get_string('multiblock_title', 'block_multiblock'), 34 | get_string('multiblock_title_desc', 'block_multiblock'), "", 35 | PARAM_TEXT)); 36 | 37 | // Multiblock presentation style options array. 38 | $multiblockpresentationoptions = array(); 39 | $presentations = block_multiblock::get_valid_presentations(); 40 | foreach ($presentations as $presentationid => $presentation) { 41 | array_push($multiblockpresentationoptions, $presentationid); 42 | } 43 | // Multiblock presentation style. 44 | $settings->add(new admin_setting_configselect('block_multiblock/presentation', 45 | get_string('multiblock_presentation_style', 'block_multiblock'), 46 | get_string('multiblock_presentation_style_desc', 'block_multiblock'), 7, $multiblockpresentationoptions)); 47 | 48 | // Multiblock - available sub-blocks. 49 | $blocklist = []; 50 | foreach ($blocks as $block) { 51 | if ($block->name == 'multiblock') { 52 | continue; 53 | } 54 | 55 | $blocklist[$block->name] = trim($block->name) ? trim($block->name) : '[block_' . $block->name . ']'; 56 | } 57 | // Multiblock manage contents (add subblock). 58 | $settings->add(new admin_setting_configmultiselect('block_multiblock/subblock', 59 | get_string('multiblock_subblock', 'block_multiblock'), 60 | get_string('multiblock_subblock_desc', 'block_multiblock'), [1], $blocklist)); 61 | 62 | } 63 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* Improved styling on tabbed view to add space between tab bar and content. */ 2 | .block_multiblock .multiblock-tabbed-list .tab-pane { 3 | padding-top: 1rem; 4 | } 5 | 6 | /* Reduce excessive spacing on accordion selectors. */ 7 | .block_multiblock .multiblock-accordion .card-header [data-toggle="collapse"] { 8 | padding: 0; 9 | } 10 | 11 | /* But make sure there is some spacing in the inner container. */ 12 | .block-region .block.card .multiblock-accordion [id^="multiblock-content"] .card-body { 13 | padding: 1.25rem; 14 | } 15 | 16 | /* Make dropdown full width */ 17 | .block_multiblock .multiblock-dropdown > .nav { 18 | margin: 0 0 1rem 0; 19 | } 20 | .block_multiblock .multiblock-dropdown .nav-item.dropdown { 21 | width: 100%; 22 | text-align: center; 23 | } 24 | 25 | /* Make 2-column layout not broken, generically. */ 26 | .multiblock-columns-2 .col-md-6:nth-of-type(odd) section { 27 | margin-left: 1rem; 28 | margin-right: 0.5rem; 29 | } 30 | .multiblock-columns-2 .col-md-6:nth-of-type(even) section { 31 | margin-left: 0.5rem; 32 | margin-right: 1rem; 33 | } 34 | 35 | .multiblock-columns-2-66-33 .col-md-8 section { 36 | margin-left: 1rem; 37 | margin-right: 0.5rem; 38 | } 39 | .multiblock-columns-2-66-33 .col-md-4 section { 40 | margin-left: 0.5rem; 41 | margin-right: 1rem; 42 | } 43 | 44 | .multiblock-columns-2-33-66 .col-md-4 section { 45 | margin-left: 1rem; 46 | margin-right: 0.5rem; 47 | } 48 | .multiblock-columns-2-33-66 .col-md-8 section { 49 | margin-left: 0.5rem; 50 | margin-right: 1rem; 51 | } 52 | 53 | /* Fix the multiblock 2/3 column layout to hide the parent container and remove its padding. */ 54 | /* Some of the rules are specified again in more selective versions for different themes. */ 55 | .multiblock-columns-2-parent[id^="inst"], 56 | .multiblock-columns-3-parent[id^="inst"] { 57 | background: transparent; 58 | border: transparent; 59 | } 60 | .multiblock-columns-2-parent[id^="inst"] .multiblock > .container, 61 | .multiblock-columns-3-parent[id^="inst"] .multiblock > .container { 62 | padding: 0; 63 | max-width: 100%; 64 | } 65 | .multiblock-columns-2-parent[id^="inst"] .multiblock > .container > .row > [class*="col-md-"], 66 | .multiblock-columns-3-parent[id^="inst"] .multiblock > .container > .row > [class*="col-md-"] { 67 | padding: 0; 68 | } 69 | .multiblock-columns-2-parent[id^="inst"] > .card-body, 70 | .multiblock-columns-3-parent[id^="inst"] > .card-body { 71 | padding: 0 !important; /* stylelint-disable-line declaration-no-important */ 72 | border: transparent !important; /* stylelint-disable-line declaration-no-important */ 73 | } 74 | .multiblock-columns-2-parent[id^="inst"] > .card-body > .card-text, 75 | .multiblock-columns-3-parent[id^="inst"] > .card-body > .card-text { 76 | margin-top: 0 !important; /* stylelint-disable-line declaration-no-important */ 77 | padding: 0 !important; /* stylelint-disable-line declaration-no-important */ 78 | } 79 | .multiblock-columns-2-parent > .card-body > .card-title + .card-text, 80 | .multiblock-columns-2-parent[id^="inst"] > .card-body > .block-controls + .card-text, 81 | .multiblock-columns-3-parent > .card-body > .card-title + .card-text, 82 | .multiblock-columns-3-parent[id^="inst"] > .card-body > .block-controls + .card-text { 83 | margin-top: 0.75rem !important; /* stylelint-disable-line declaration-no-important */ 84 | } 85 | 86 | /* Additional tweaks for 3-column specifically. */ 87 | .multiblock-columns-3 .col-md-4:nth-of-type(3n+1) section { 88 | margin-left: 1rem; 89 | margin-right: 0.5rem; 90 | } 91 | .multiblock-columns-3 .col-md-4:nth-of-type(3n+2) section { 92 | margin-left: 0.5rem; 93 | margin-right: 0.5rem; 94 | } 95 | .multiblock-columns-3 .col-md-4:nth-of-type(3n+3) section { 96 | margin-left: 0.5rem; 97 | margin-right: 1rem; 98 | } 99 | 100 | /* Style fixes for multiblock. */ 101 | .multiblock-carousel { 102 | padding-bottom: 70px; 103 | } 104 | 105 | .multiblock-carousel a.carousel-control { 106 | top: auto; 107 | bottom: 5px; 108 | height: 40px; 109 | background: #fff; 110 | border: 1px solid #ddd; 111 | margin-bottom: -70px; 112 | opacity: 1; 113 | } 114 | 115 | .multiblock-carousel a.carousel-control span { 116 | color: #000; 117 | } 118 | 119 | .multiblock-carousel-bootstrap3 a.carousel-control > .carousel-control { 120 | position: relative; 121 | } 122 | 123 | .multiblock-carousel .flex-icon::before { 124 | font-size: 1.7em; 125 | } 126 | 127 | .multiblock-carousel-bootstrap3 .carousel-control-prev { 128 | left: 0; 129 | right: auto; 130 | } 131 | 132 | .multiblock-carousel-bootstrap3 .carousel-control-next { 133 | right: 0; 134 | left: auto; 135 | } 136 | -------------------------------------------------------------------------------- /templates/accordion-bootstrap3.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 block_multiblock/accordion-bootstrap3 19 | 20 | This template shows the multiple blocks in an accordion 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 | {{#multiblock}} 45 |
46 | 51 |
52 |
53 | {{{content}}} 54 | 55 | {{{annotation}}} 56 |
57 |
58 |
59 | {{/multiblock}} 60 |
61 | -------------------------------------------------------------------------------- /templates/accordion-bootstrap4.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 block_multiblock/accordion-bootstrap4 19 | 20 | This template shows the multiple blocks in an accordion 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 | {{#multiblock}} 45 |
46 |
47 |
48 | {{{title}}} 49 |
50 |
51 |
52 |
53 | {{{content}}} 54 | 55 | {{{annotation}}} 56 |
57 |
58 |
59 | {{/multiblock}} 60 |
61 | -------------------------------------------------------------------------------- /templates/accordion-mobile.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 block_multiblock/accordion-mobile 19 | 20 | This template shows the multiple blocks in an accordion 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "active": 8, 26 | "multiblock": [ 27 | { 28 | "id": 1, 29 | "title": "Block 1", 30 | "content": "

Block 1

" 31 | }, 32 | { 33 | "id": 2, 34 | "title": "Block 2", 35 | "content": "

Block 2

" 36 | } 37 | ] 38 | } 39 | }} 40 | {{=<% %>=}} 41 |
42 | 43 | <% name %> 44 | <%#multiblock%> 45 | 46 | 47 | 48 | <% title %> 49 | 50 | 51 |
52 | <%{ content }%> 53 |
54 |
55 | <%/multiblock%> 56 |
57 |
58 | -------------------------------------------------------------------------------- /templates/carousel-bootstrap3.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 block_multiblock/carousel-bootstrap3 19 | 20 | This template shows the multiple blocks in a carousel view 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 | 70 | {{#js}} 71 | require(['jquery', 'theme_roots/bootstrap'], function($) { 72 | $('#multiblock-content-{{multiblockid}}').carousel(); 73 | }); 74 | {{/js}} -------------------------------------------------------------------------------- /templates/carousel-bootstrap4.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 block_multiblock/carousel-bootstrap4 19 | 20 | This template shows the multiple blocks in a carousel view 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 | 70 | {{#js}} 71 | require(['jquery', 'theme_boost/carousel'], function($) { 72 | $('#multiblock-content-{{multiblockid}}').carousel(); 73 | }); 74 | {{/js}} -------------------------------------------------------------------------------- /templates/carousel-mobile.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 block_multiblock/carousel-mobile 19 | 20 | This template shows the multiple blocks in an carousel 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "active": 8, 26 | "multiblock": [ 27 | { 28 | "id": 1, 29 | "title": "Block 1", 30 | "content": "

Block 1

" 31 | }, 32 | { 33 | "id": 2, 34 | "title": "Block 2", 35 | "content": "

Block 2

" 36 | } 37 | ] 38 | } 39 | }} 40 | {{=<% %>=}} 41 | 54 | -------------------------------------------------------------------------------- /templates/columns-2-33-66.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 block_multiblock/columns-2-66-33 19 | 20 | This template shows the multiple blocks in a two-column layout (with a 1:2 or 33%/66% ratio) 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 |
45 |
46 | {{#multiblock}} 47 |
48 |
49 |
50 | {{#title}} 51 |
{{{title}}}
52 | {{/title}} 53 |
54 | {{{content}}} 55 | 56 | {{{annotation}}} 57 |
58 |
59 |
60 |
61 | {{/multiblock}} 62 |
63 |
64 |
65 | {{#js}} 66 | require(['jquery'], function($) { 67 | $('#inst{{multiblockid}}').addClass('multiblock-columns-2-parent'); 68 | }); 69 | {{/js}} 70 | -------------------------------------------------------------------------------- /templates/columns-2-66-33.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 block_multiblock/columns-2-66-33 19 | 20 | This template shows the multiple blocks in a two-column layout (with a 2:1 or 66%/33% ratio) 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 |
45 |
46 | {{#multiblock}} 47 |
48 |
49 |
50 | {{#title}} 51 |
{{{title}}}
52 | {{/title}} 53 |
54 | {{{content}}} 55 | 56 | {{{annotation}}} 57 |
58 |
59 |
60 |
61 | {{/multiblock}} 62 |
63 |
64 |
65 | {{#js}} 66 | require(['jquery'], function($) { 67 | $('#inst{{multiblockid}}').addClass('multiblock-columns-2-parent'); 68 | }); 69 | {{/js}} 70 | -------------------------------------------------------------------------------- /templates/columns-2equal.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 block_multiblock/columns-2equal 19 | 20 | This template shows the multiple blocks in a two-column layout (equal width) 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 |
45 |
46 | {{#multiblock}} 47 |
48 |
49 |
50 | {{#title}} 51 |
{{{title}}}
52 | {{/title}} 53 |
54 | {{{content}}} 55 | 56 | {{{annotation}}} 57 |
58 |
59 |
60 |
61 | {{/multiblock}} 62 |
63 |
64 |
65 | {{#js}} 66 | require(['jquery'], function($) { 67 | $('#inst{{multiblockid}}').addClass('multiblock-columns-2-parent'); 68 | }); 69 | {{/js}} 70 | -------------------------------------------------------------------------------- /templates/columns-3equal.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 block_multiblock/columns-3equal 19 | 20 | This template shows the multiple blocks in a two-column layout (equal width) 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | }, 40 | { 41 | "id": 3, 42 | "title": "Block 3", 43 | "content": "

Block 3

", 44 | "footer": "", 45 | "active": false 46 | } 47 | ] 48 | } 49 | }} 50 |
51 |
52 |
53 | {{#multiblock}} 54 |
55 |
56 |
57 | {{#title}} 58 |
{{{title}}}
59 | {{/title}} 60 |
61 | {{{content}}} 62 | 63 | {{{annotation}}} 64 |
65 |
66 |
67 |
68 | {{/multiblock}} 69 |
70 |
71 |
72 | {{#js}} 73 | require(['jquery'], function($) { 74 | $('#inst{{multiblockid}}').addClass('multiblock-columns-3-parent'); 75 | }); 76 | {{/js}} 77 | -------------------------------------------------------------------------------- /templates/dropdown-bootstrap3.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 block_multiblock/dropdown-bootstrap3 19 | 20 | This template shows the multiple blocks in a tabbed view 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 | 59 |
60 | {{#multiblock}} 61 |
62 |
63 | {{{content}}} 64 | 65 | {{{annotation}}} 66 |
67 |
68 | {{/multiblock}} 69 |
70 |
71 | {{#js}} 72 | require(['jquery'], function($) { 73 | $('#multiblock-container-{{multiblockid}} .dropdown-menu a').on('click', function() { 74 | $('#multiblock-container-{{multiblockid}} a.dropdown-toggle').html($(this).html() + ' '); 75 | }); 76 | }); 77 | {{/js}} -------------------------------------------------------------------------------- /templates/dropdown-bootstrap4.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 block_multiblock/dropdown-bootstrap4 19 | 20 | This template shows the multiple blocks in a tabbed view 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 | 54 |
55 | {{#multiblock}} 56 |
57 |
58 | {{{content}}} 59 | 60 | {{{annotation}}} 61 |
62 |
63 | {{/multiblock}} 64 |
65 |
66 | {{#js}} 67 | require(['jquery'], function($) { 68 | $('#multiblock-container-{{multiblockid}} .dropdown-menu a').on('click', function() { 69 | $('#multiblock-container-{{multiblockid}} a.dropdown-toggle').html($(this).html()); 70 | }); 71 | }); 72 | {{/js}} -------------------------------------------------------------------------------- /templates/tabbed-list-bootstrap3.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 block_multiblock/tabbed-list 19 | 20 | This template shows the multiple blocks in a tabbed view 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 | 51 |
52 | {{#multiblock}} 53 |
54 |
55 | {{{content}}} 56 | 57 | {{{annotation}}} 58 |
59 |
60 | {{/multiblock}} 61 |
62 |
63 | -------------------------------------------------------------------------------- /templates/tabbed-list-bootstrap4.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 block_multiblock/tabbed-list 19 | 20 | This template shows the multiple blocks in a tabbed view 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 | 51 |
52 | {{#multiblock}} 53 |
54 |
55 | {{{content}}} 56 | 57 | {{{annotation}}} 58 |
59 |
60 | {{/multiblock}} 61 |
62 |
-------------------------------------------------------------------------------- /templates/tabbed-list-columns-2-66-33-bootstrap3.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 block_multiblock/tabbed-list 19 | 20 | This template shows the multiple blocks in a tabbed view 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 | 53 |
54 |
55 |
56 | {{#multiblock}} 57 | {{#is_odd}} 58 |
59 |
60 |
61 |
62 | {{/is_odd}} 63 |
64 |
65 |
66 |
67 | {{{content}}} 68 | 69 | {{{annotation}}} 70 |
71 |
72 |
73 |
74 | {{/multiblock}} 75 |
76 |
77 |
78 |
79 | -------------------------------------------------------------------------------- /templates/tabbed-list-columns-2-66-33-bootstrap4.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 block_multiblock/tabbed-list-columns-2-66-33 19 | 20 | This template shows the multiple blocks in a tabbed view with 2 columns. 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 | 53 |
54 |
55 |
56 | {{#multiblock}} 57 | {{#is_odd}} 58 |
59 |
60 |
61 |
62 | {{/is_odd}} 63 |
64 |
65 |
66 |
67 | {{{content}}} 68 | 69 | {{{annotation}}} 70 |
71 |
72 |
73 |
74 | {{/multiblock}} 75 |
76 |
77 |
78 |
79 | -------------------------------------------------------------------------------- /templates/vertical-tabbed-list-bootstrap3.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 block_multiblock/vertical-tabbed-list-bootstrap3 19 | 20 | This template shows the multiple blocks in a tabbed view 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 |
45 |
46 | 53 |
54 |
55 |
56 | {{#multiblock}} 57 |
58 |
59 | {{{content}}} 60 | 61 | {{{annotation}}} 62 |
63 |
64 | {{/multiblock}} 65 |
66 |
67 |
68 |
-------------------------------------------------------------------------------- /templates/vertical-tabbed-list-bootstrap4.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 block_multiblock/vertical-tabbed-list-bootstrap4 19 | 20 | This template shows the multiple blocks in a tabbed view 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 |
45 |
46 | 51 |
52 |
53 |
54 | {{#multiblock}} 55 |
56 |
57 | {{{content}}} 58 | 59 | {{{annotation}}} 60 |
61 |
62 | {{/multiblock}} 63 |
64 |
65 |
66 |
-------------------------------------------------------------------------------- /templates/vertical-tabbed-list-right-bootstrap3.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 block_multiblock/vertical-tabbed-list-right-bootstrap3 19 | 20 | This template shows the multiple blocks in a tabbed view 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 |
45 |
46 |
47 | {{#multiblock}} 48 |
49 |
50 | {{{content}}} 51 | 52 | {{{annotation}}} 53 |
54 |
55 | {{/multiblock}} 56 |
57 |
58 |
59 | 66 |
67 |
68 |
-------------------------------------------------------------------------------- /templates/vertical-tabbed-list-right-bootstrap4.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 block_multiblock/vertical-tabbed-list-right-bootstrap4 19 | 20 | This template shows the multiple blocks in a tabbed view 21 | 22 | Example context (json): 23 | { 24 | "multiblockid": 28, 25 | "multiblock": [ 26 | { 27 | "id": 1, 28 | "title": "Block 1", 29 | "content": "

Block 1

", 30 | "footer": "My footer", 31 | "active": true 32 | }, 33 | { 34 | "id": 2, 35 | "title": "Block 2", 36 | "content": "

Block 2

", 37 | "footer": "", 38 | "active": false 39 | } 40 | ] 41 | } 42 | }} 43 |
44 |
45 |
46 |
47 | {{#multiblock}} 48 |
49 |
50 | {{{content}}} 51 | 52 | {{{annotation}}} 53 |
54 |
55 | {{/multiblock}} 56 |
57 |
58 |
59 | 64 |
65 |
66 |
-------------------------------------------------------------------------------- /tests/behat/accordion.feature: -------------------------------------------------------------------------------- 1 | @block @block_multiblock @javascript 2 | Feature: Accordion 3 | In order to streamline course presentation 4 | As a teacher 5 | I need to be able to focus content with an accordion layout 6 | 7 | Scenario: Testing accordion layout 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | teacher1 | Teacher | One | teacher@example.com | 11 | | student1 | Student | User | student1@example.com | 12 | And the following "courses" exist: 13 | | fullname | shortname | 14 | | Course 1 | C1 | 15 | And the following "course enrolments" exist: 16 | | user | course | role | 17 | | teacher1 | C1 | editingteacher | 18 | | student1 | C1 | student | 19 | When I log in as "teacher1" 20 | And I am on "Course 1" course homepage with editing mode on 21 | # The usual 'And I add "Multiblock" block' step can fail in JS with lots of blocks present. 22 | And I add the "Multiblock" block 23 | And I configure the "Multiblock" block 24 | And I set the field "Multiblock presentation style" to "Accordion" 25 | And I press "Save changes" 26 | And I manage the contents of "Multiblock" block 27 | And I expand all fieldsets 28 | And I add the HTML block field 29 | And I set the title of the HTML block to "First Item" 30 | And I set the field "Content" to "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." 31 | And I press "Save and return to manage" 32 | And I expand all fieldsets 33 | And I add the HTML block field 34 | And I set the title of the HTML block to "Second Item" 35 | And I set the field "Content" to "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." 36 | And I press "Save and return to manage" 37 | And I log out 38 | And I log in as "student1" 39 | And I am on "Course 1" course homepage 40 | Then I should see "First Item" in the ".block_multiblock" "css_element" 41 | And I should see "Second Item" in the ".block_multiblock" "css_element" 42 | And I should see "Lorem ipsum dolor sit" in the ".block_multiblock" "css_element" 43 | And I should not see "Ut enim ad minim veniam" in the ".block_multiblock" "css_element" 44 | And I press "Second Item" 45 | And I should not see "Lorem ipsum dolor sit" in the ".block_multiblock" "css_element" 46 | And I should see "Ut enim ad minim veniam" in the ".block_multiblock" "css_element" 47 | -------------------------------------------------------------------------------- /tests/behat/basic_test.feature: -------------------------------------------------------------------------------- 1 | @block @block_multiblock 2 | Feature: Basic tests of multiple blocks 3 | In order to streamline course presentation 4 | As a teacher 5 | I need to be able to put multiple blocks in a single space 6 | 7 | Scenario: Adding a Course Toolkit with two sub-blocks 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | teacher1 | Teacher | One | teacher@example.com | 11 | | student1 | Student | User | student1@example.com | 12 | And the following "courses" exist: 13 | | fullname | shortname | 14 | | Course 1 | C1 | 15 | And the following "course enrolments" exist: 16 | | user | course | role | 17 | | teacher1 | C1 | editingteacher | 18 | | student1 | C1 | student | 19 | When I log in as "teacher1" 20 | And I am on "Course 1" course homepage with editing mode on 21 | And I add the "Multiblock" block 22 | And I manage the contents of "Multiblock" block 23 | And I should see "This multiblock has no blocks inside it." 24 | And I set the field "Add a block" to "Logged in user" 25 | And I click on "input[value=Add]" "css_element" 26 | And I should see "Manage Multiblock contents" 27 | And I should see "Logged in user" 28 | And I set the field "Add a block" to "Recent activity" 29 | And I click on "input[value=Add]" "css_element" 30 | And I log out 31 | And I log in as "student1" 32 | And I am on "Course 1" course homepage 33 | Then I should see "Multiblock" 34 | And I should see "Logged in user" 35 | And I should see "Recent activity" 36 | -------------------------------------------------------------------------------- /tests/behat/behat_block_multiblock.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Steps definitions related with blocks. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2020 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. 26 | 27 | use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; 28 | 29 | require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); 30 | 31 | /** 32 | * Blocks management steps definitions. 33 | * 34 | * @package block_multiblock 35 | * @copyright 2020 Peter Spicer 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | */ 38 | class behat_block_multiblock extends behat_base { 39 | /** @var object|null HTML/Text object based on moodle version. */ 40 | private $htmlblock = null; 41 | 42 | /** 43 | * Return HTML/Text object based on moodle version 44 | * 45 | * @return object 46 | */ 47 | private function htmlblock() { 48 | if (!empty($this->htmlblock)) { 49 | return $this->htmlblock; 50 | } 51 | 52 | global $CFG; 53 | $newblockname = $CFG->branch >= 400; 54 | 55 | $htmlblock = new stdClass; 56 | $htmlblock->name = $newblockname ? 'Text' : 'HTML'; 57 | $htmlblock->defaulttitle = $newblockname ? '(new text block)' : '(new HTML block)'; 58 | 59 | $this->htmlblock = $htmlblock; 60 | 61 | return $htmlblock; 62 | } 63 | 64 | /** 65 | * Clicks on "Manage ... contents" for specified block. Page must be in editing mode. 66 | * 67 | * Argument block_name may be either the name of the block or CSS class of the block. 68 | * 69 | * @Given /^I manage the contents of "(?P(?:[^"]|\\")*)" block$/ 70 | * @param string $blockname 71 | */ 72 | public function i_manage_the_contents_of_block($blockname) { 73 | // Problem 1, the block name might be the name or the CSS. 74 | // Problem 2, the string conceivably could be 'Manage contents' if the block name is empty. 75 | // As such we need to use what we do have of it. 76 | 77 | if (in_array($blockname, ["Text", "HTML"])) { 78 | $htmlblock = $this->htmlblock(); 79 | $blockname = $htmlblock->name; 80 | } 81 | 82 | $this->execute("behat_blocks::i_open_the_blocks_action_menu", $this->escape($blockname)); 83 | 84 | $this->execute('behat_general::i_click_on_in_the', 85 | array("Manage", "link", $this->escape($blockname), "block") 86 | ); 87 | } 88 | 89 | /** 90 | * Adds an Text/HTML subblock in the manage contents page of a multiblock. 91 | * 92 | * @Given /^I add the HTML block field$/ 93 | */ 94 | public function i_add_the_html_block_field() { 95 | $block = $this->htmlblock(); 96 | 97 | $this->execute("behat_forms::i_set_the_field_to", ["Add a block", $this->escape($block->name)]); 98 | $this->execute("behat_general::i_click_on_in_the", ["input[value=Add]", "css_element", "[role=main]", "css_element"]); 99 | } 100 | 101 | /** 102 | * Changes the title of a Text/HTML block in its configuration page. 103 | * 104 | * Argument oldtitle is the current name of the block. 105 | * Argument newtitle is the new name of the block. 106 | * 107 | * @Given /^I set the title of the HTML block to "(?P(?:[^"]|\\")*)"$/ 108 | * @Given /^I set the title of the HTML block to "(?P(?:[^"]|\\")*)" from "(?P(?:[^"]|\\")*)"$/ 109 | * 110 | * @param string $newtitle 111 | * @param string|false $oldtitle 112 | */ 113 | public function i_set_the_title_of_the_html_block_to($newtitle, $oldtitle = false) { 114 | $block = $this->htmlblock(); 115 | $titlefieldlabel = $block->name . ' block title'; 116 | $oldtitle = $oldtitle ?: $block->defaulttitle; 117 | 118 | $this->execute("behat_general::i_click_on_in_the", ["Settings", "link", $oldtitle, "table_row"]); 119 | $this->execute("behat_forms::i_set_the_field_to", [$this->escape($titlefieldlabel), $this->escape($newtitle)]); 120 | } 121 | 122 | /** 123 | * Enables editing mode for when you are on the dashboard. 124 | * 125 | * @Given /^I enable editing mode whilst on the dashboard$/ 126 | */ 127 | public function i_enable_editing_mode_whilst_on_the_dashboard() { 128 | global $CFG; 129 | if ($CFG->branch >= 400) { 130 | $this->execute("behat_navigation::i_turn_editing_mode_on"); 131 | } else { 132 | $this->execute( 133 | 'behat_general::i_click_on_in_the', 134 | [ 135 | 'Customise this page', 136 | 'button', 137 | "[id='page-header']", 138 | 'css_element' 139 | ] 140 | ); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tests/behat/defaultsettings.feature: -------------------------------------------------------------------------------- 1 | @block @block_multiblock 2 | Feature: The block can have administrator set defaults 3 | In order to be customize the multiblock 4 | As an admin 5 | I need to be able to assign some site wide defaults 6 | Background: 7 | Given the following "users" exist: 8 | | username | firstname | lastname | email | 9 | | teacher1 | Teacher | One | teacher@example.com | 10 | | student1 | Student | User | student1@example.com | 11 | And the following "courses" exist: 12 | | fullname | shortname | 13 | | Course 1 | C1 | 14 | And the following "course enrolments" exist: 15 | | user | course | role | 16 | | teacher1 | C1 | editingteacher | 17 | | student1 | C1 | student | 18 | 19 | Scenario: Test setting the multiblock title. 20 | Given the following config values are set as admin: 21 | | title | Course Toolkit | block_multiblock | 22 | When I log in as "teacher1" 23 | And I am on "Course 1" course homepage with editing mode on 24 | And I add the "Multiblock" block 25 | Then I should see "Course Toolkit" 26 | 27 | Scenario: Test setting the multiblock presnetation style. 28 | Given the following config values are set as admin: 29 | | presentation | 0 | block_multiblock | 30 | | subblock | calendar_month | block_multiblock | 31 | When I log in as "teacher1" 32 | And I am on "Course 1" course homepage with editing mode on 33 | And I add the "Multiblock" block 34 | Then ".multiblock.multiblock-accordion" "css_element" should exist 35 | 36 | Scenario: Test setting the default blocks. 37 | Given the following config values are set as admin: 38 | | subblock | calendar_month | block_multiblock | 39 | When I log in as "teacher1" 40 | And I am on "Course 1" course homepage with editing mode on 41 | And I add the "Multiblock" block 42 | Then "Calendar" "text" should exist in the "Multiblock" "block" 43 | -------------------------------------------------------------------------------- /tests/behat/dropdown.feature: -------------------------------------------------------------------------------- 1 | @block @block_multiblock @javascript 2 | Feature: Dropdown presentation 3 | In order to streamline course presentation 4 | As a teacher 5 | I need to be able to focus content with a dropdown 6 | 7 | Scenario: Dropdown 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | teacher1 | Teacher | One | teacher@example.com | 11 | When I log in as "teacher1" 12 | And I enable editing mode whilst on the dashboard 13 | # The usual 'And I add "Multiblock" block' step can fail in JS with lots of blocks present. 14 | And I add the "Multiblock" block 15 | And I configure the "Multiblock" block 16 | And I expand all fieldsets 17 | And I set the field "Multiblock presentation style" to "Dropdown" 18 | And I set the field "Region" to "content" 19 | And I press "Save changes" 20 | And I manage the contents of "Multiblock" block 21 | And I expand all fieldsets 22 | And I add the HTML block field 23 | And I set the title of the HTML block to "First Item" 24 | And I set the field "Content" to "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." 25 | And I press "Save and return to manage" 26 | And I expand all fieldsets 27 | And I add the HTML block field 28 | And I set the title of the HTML block to "Second Item" 29 | And I set the field "Content" to "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." 30 | And I press "Save and display" 31 | Then I should see "First Item" in the ".block_multiblock" "css_element" 32 | And I should not see "Second Item" in the ".block_multiblock" "css_element" 33 | And I should see "Lorem ipsum dolor sit" in the ".block_multiblock" "css_element" 34 | And I should not see "Ut enim ad minim veniam" in the ".block_multiblock" "css_element" 35 | # First, open the dropdown. 36 | And I click on "First Item" "link" 37 | # Second, pick the new thing. 38 | And I click on "Second Item" "link" 39 | And I should not see "Lorem ipsum dolor sit" in the ".block_multiblock" "css_element" 40 | And I should see "Ut enim ad minim veniam" in the ".block_multiblock" "css_element" 41 | -------------------------------------------------------------------------------- /tests/behat/front_page.feature: -------------------------------------------------------------------------------- 1 | @block @block_multiblock @javascript 2 | Feature: Front page test 3 | In order to customise the front page 4 | As a teacher 5 | I need to be able to put multiple blocks in a single space 6 | 7 | Scenario: Adding a Multiblock on the front page 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | student1 | Student | User | student1@example.com | 11 | And the following "courses" exist: 12 | | fullname | shortname | 13 | | Course 1 | C1 | 14 | And the following "course enrolments" exist: 15 | | user | course | role | 16 | | student1 | C1 | student | 17 | When I log in as "admin" 18 | And I am on site homepage 19 | And I turn editing mode on 20 | And I add the "Multiblock" block 21 | And I manage the contents of "Multiblock" block 22 | And I should see "This multiblock has no blocks inside it." 23 | And I expand all fieldsets 24 | And I set the field "Add a block" to "Logged in user" 25 | And I click on "input[value=Add]" "css_element" 26 | And I should see "Manage Multiblock contents" 27 | And I should see "Logged in user" 28 | And I expand all fieldsets 29 | And I set the field "Add a block" to "Recent activity" 30 | And I click on "input[value=Add]" "css_element" 31 | And I log out 32 | And I log in as "student1" 33 | And I am on site homepage 34 | Then I should see "Multiblock" 35 | And I should see "Logged in user" 36 | And I should see "Recent activity" 37 | -------------------------------------------------------------------------------- /tests/behat/merging_blocks.feature: -------------------------------------------------------------------------------- 1 | @block @block_multiblock 2 | Feature: Accordion layout 3 | In order to improve existing content 4 | As a teacher 5 | I need to be able to merge existing blocks into a newly made multiblock 6 | 7 | Scenario: Creating a block and then moving it into a multiblock 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | teacher1 | Teacher | One | teacher@example.com | 11 | | student1 | Student | User | student1@example.com | 12 | And the following "courses" exist: 13 | | fullname | shortname | 14 | | Course 1 | C1 | 15 | And the following "course enrolments" exist: 16 | | user | course | role | 17 | | teacher1 | C1 | editingteacher | 18 | | student1 | C1 | student | 19 | When I log in as "teacher1" 20 | And I am on "Course 1" course homepage with editing mode on 21 | And I add the "Logged in user" block 22 | And I add the "Multiblock" block 23 | And I should see "Teacher One" 24 | And I should not see "Teacher One" in the ".block_multiblock" "css_element" 25 | And I manage the contents of "Multiblock" block 26 | And I expand all fieldsets 27 | And I set the field "Move existing block" to "Logged in user" 28 | And I click on "input[value=Move]" "css_element" 29 | And I log out 30 | And I log in as "student1" 31 | And I am on "Course 1" course homepage 32 | Then I should see "Multiblock" 33 | And I should see "Student User" in the ".block_multiblock" "css_element" 34 | -------------------------------------------------------------------------------- /tests/behat/tabbed.feature: -------------------------------------------------------------------------------- 1 | @block @block_multiblock @javascript 2 | Feature: Tabbed layout 3 | In order to streamline course presentation 4 | As a teacher 5 | I need to be able to focus content with a tabbed layout 6 | 7 | Scenario: Testing tabbed layout 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | teacher1 | Teacher | One | teacher@example.com | 11 | | student1 | Student | User | student1@example.com | 12 | And the following "courses" exist: 13 | | fullname | shortname | 14 | | Course 1 | C1 | 15 | And the following "course enrolments" exist: 16 | | user | course | role | 17 | | teacher1 | C1 | editingteacher | 18 | | student1 | C1 | student | 19 | When I log in as "teacher1" 20 | And I am on "Course 1" course homepage with editing mode on 21 | # The usual 'And I add "Multiblock" block' step can fail in JS with lots of blocks present. 22 | And I add the "Multiblock" block 23 | And I configure the "Multiblock" block 24 | And I set the field "Multiblock presentation style" to "Tabs" 25 | And I press "Save changes" 26 | And I manage the contents of "Multiblock" block 27 | And I expand all fieldsets 28 | And I add the HTML block field 29 | And I set the title of the HTML block to "First Item" 30 | And I set the field "Content" to "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." 31 | And I press "Save and return to manage" 32 | And I expand all fieldsets 33 | And I add the HTML block field 34 | And I set the title of the HTML block to "Second Item" 35 | And I set the field "Content" to "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." 36 | And I press "Save and return to manage" 37 | And I log out 38 | And I log in as "student1" 39 | And I am on "Course 1" course homepage 40 | Then I should see "First Item" in the ".block_multiblock" "css_element" 41 | And I should see "Second Item" in the ".block_multiblock" "css_element" 42 | And I should see "Lorem ipsum dolor sit" in the ".block_multiblock" "css_element" 43 | And I should not see "Ut enim ad minim veniam" in the ".block_multiblock" "css_element" 44 | And I click on "Second Item" "link" 45 | And I should not see "Lorem ipsum dolor sit" in the ".block_multiblock" "css_element" 46 | And I should see "Ut enim ad minim veniam" in the ".block_multiblock" "css_element" 47 | -------------------------------------------------------------------------------- /tests/behat/tabbed_vertical_left.feature: -------------------------------------------------------------------------------- 1 | @block @block_multiblock @javascript 2 | Feature: Vertical tabbed layout (left) 3 | In order to streamline course presentation 4 | As a teacher 5 | I need to be able to focus content with a vertical tabbed layout 6 | 7 | Scenario: Testing vertical tabbed layout (left) 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | teacher1 | Teacher | One | teacher@example.com | 11 | When I log in as "teacher1" 12 | And I enable editing mode whilst on the dashboard 13 | # The usual 'And I add "Multiblock" block' step can fail in JS with lots of blocks present. 14 | And I add the "Multiblock" block 15 | And I configure the "Multiblock" block 16 | And I expand all fieldsets 17 | And I set the field "Multiblock presentation style" to "Vertical Tabs (Left)" 18 | And I set the field "Region" to "content" 19 | And I press "Save changes" 20 | And I manage the contents of "Multiblock" block 21 | And I expand all fieldsets 22 | And I add the HTML block field 23 | And I set the title of the HTML block to "First Item" 24 | And I set the field "Content" to "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." 25 | And I press "Save and return to manage" 26 | And I expand all fieldsets 27 | And I add the HTML block field 28 | And I set the title of the HTML block to "Second Item" 29 | And I set the field "Content" to "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." 30 | And I press "Save and display" 31 | Then I should see "First Item" in the ".block_multiblock" "css_element" 32 | And I should see "Second Item" in the ".block_multiblock" "css_element" 33 | And I should see "Lorem ipsum dolor sit" in the ".block_multiblock" "css_element" 34 | And I should not see "Ut enim ad minim veniam" in the ".block_multiblock" "css_element" 35 | And "Second Item" "link" should appear before "Lorem ipsum" "text" 36 | And I click on "Second Item" "link" 37 | And I should not see "Lorem ipsum dolor sit" in the ".block_multiblock" "css_element" 38 | And I should see "Ut enim ad minim veniam" in the ".block_multiblock" "css_element" 39 | -------------------------------------------------------------------------------- /tests/behat/tabbed_vertical_right.feature: -------------------------------------------------------------------------------- 1 | @block @block_multiblock @javascript 2 | Feature: Vertical tabbed layout (right) 3 | In order to streamline course presentation 4 | As a teacher 5 | I need to be able to focus content with a vertical tabbed layout 6 | 7 | Scenario: Testing vertical tabbed layout (right) 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | teacher1 | Teacher | One | teacher@example.com | 11 | When I log in as "teacher1" 12 | And I enable editing mode whilst on the dashboard 13 | # The usual 'And I add "Multiblock" block' step can fail in JS with lots of blocks present. 14 | And I add the "Multiblock" block 15 | And I configure the "Multiblock" block 16 | And I expand all fieldsets 17 | And I set the field "Multiblock presentation style" to "Vertical Tabs (Right)" 18 | And I set the field "Region" to "content" 19 | And I press "Save changes" 20 | And I manage the contents of "Multiblock" block 21 | And I expand all fieldsets 22 | And I add the HTML block field 23 | And I set the title of the HTML block to "First Item" 24 | And I set the field "Content" to "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." 25 | And I press "Save and return to manage" 26 | And I expand all fieldsets 27 | And I add the HTML block field 28 | And I set the title of the HTML block to "Second Item" 29 | And I set the field "Content" to "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." 30 | And I press "Save and display" 31 | Then I should see "First Item" in the ".block_multiblock" "css_element" 32 | And I should see "Second Item" in the ".block_multiblock" "css_element" 33 | And I should see "Lorem ipsum dolor sit" in the ".block_multiblock" "css_element" 34 | And I should not see "Ut enim ad minim veniam" in the ".block_multiblock" "css_element" 35 | And "Lorem ipsum" "text" should appear before "First Item" "link" 36 | And I click on "Second Item" "link" 37 | And I should not see "Lorem ipsum dolor sit" in the ".block_multiblock" "css_element" 38 | And I should see "Ut enim ad minim veniam" in the ".block_multiblock" "css_element" 39 | -------------------------------------------------------------------------------- /version.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Plugin version and other meta-data are defined here. 19 | * 20 | * @package block_multiblock 21 | * @copyright 2019 Peter Spicer 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | 28 | $plugin->version = 2025030700; 29 | $plugin->release = 2025030700; 30 | $plugin->requires = 2022041902; // Moodle 4.2. 31 | $plugin->component = 'block_multiblock'; 32 | $plugin->maturity = MATURITY_STABLE; 33 | $plugin->supported = [402, 405]; // A range of branch numbers of supported moodle versions. 34 | --------------------------------------------------------------------------------