diff --git a/blocks/calendar_month/db/upgrade.php b/blocks/calendar_month/db/upgrade.php index fbb1189309a80..86b4ab3f4382e 100644 --- a/blocks/calendar_month/db/upgrade.php +++ b/blocks/calendar_month/db/upgrade.php @@ -37,6 +37,10 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +defined('MOODLE_INTERNAL') || die(); + +require_once("{$CFG->libdir}/db/upgradelib.php"); + /** * Upgrade the calendar_month block * @param int $oldversion @@ -58,36 +62,8 @@ function xmldb_block_calendar_month_upgrade($oldversion, $block) { // Put any upgrade step following this. if ($oldversion < 2022030200) { - $context = context_system::instance(); - - // Begin looking for any and all customised /my pages. - $pageselect = 'name = :name and private = :private'; - $pageparams['name'] = '__default'; - $pageparams['private'] = 1; - $pages = $DB->get_recordset_select('my_pages', $pageselect, $pageparams); - foreach ($pages as $subpage) { - $blockinstance = $DB->get_record('block_instances', ['blockname' => 'calendar_month', - 'pagetypepattern' => 'my-index', 'subpagepattern' => $subpage->id]); - - if (!$blockinstance) { - // Insert the calendar month into the default index page. - $blockinstance = new stdClass; - $blockinstance->blockname = 'calendar_month'; - $blockinstance->parentcontextid = $context->id; - $blockinstance->showinsubcontexts = false; - $blockinstance->pagetypepattern = 'my-index'; - $blockinstance->subpagepattern = $subpage->id; - $blockinstance->defaultregion = 'content'; - $blockinstance->defaultweight = 0; - $blockinstance->timecreated = time(); - $blockinstance->timemodified = time(); - $DB->insert_record('block_instances', $blockinstance); - } else if ($blockinstance->defaultregion !== 'content') { - $blockinstance->defaultregion = 'content'; - $DB->update_record('block_instances', $blockinstance); - } - } - $pages->close(); + // Update all calendar_month blocks in the my-index to be in the main content region. + upgrade_block_set_defaultregion('calendar_month', '__default', 'my-index', 'content'); upgrade_block_savepoint(true, 2022030200, 'calendar_month', false); } diff --git a/blocks/myoverview/db/upgrade.php b/blocks/myoverview/db/upgrade.php index f141f032a2e97..45d8da9b0284b 100644 --- a/blocks/myoverview/db/upgrade.php +++ b/blocks/myoverview/db/upgrade.php @@ -25,7 +25,8 @@ defined('MOODLE_INTERNAL') || die(); -require_once($CFG->dirroot . '/my/lib.php'); +require_once("{$CFG->dirroot}/my/lib.php"); +require_once("{$CFG->libdir}/db/upgradelib.php"); /** * Upgrade code for the MyOverview block. @@ -84,104 +85,31 @@ function xmldb_block_myoverview_upgrade($oldversion) { // Put any upgrade step following this. if ($oldversion < 2021052504) { - /** - * Small helper function for this version upgrade to delete instances of this block. - * - * @param stdClass $instance DB record of a block that we need to delete within Moodle. - */ - function delete_block_instance(stdClass $instance) { - global $DB; - if ($instance) { - list($sql, $params) = $DB->get_in_or_equal($instance->id, SQL_PARAMS_NAMED); - $params['contextlevel'] = CONTEXT_BLOCK; - $DB->delete_records_select('context', "contextlevel=:contextlevel AND instanceid " . $sql, $params); - $DB->delete_records('block_positions', ['blockinstanceid' => $instance->id]); - $DB->delete_records('block_instances', ['id' => $instance->id]); - $DB->delete_records_list('user_preferences', 'name', - ['block' . $instance->id . 'hidden', 'docked_block_instance_' . $instance->id]); - } - } + upgrade_block_delete_instances('myoverview', '__default', 'my-index'); - // Delete the default indexsys version of the block. - $mysubpagepattern = $DB->get_record( - 'my_pages', - ['userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => MY_PAGE_PRIVATE], - 'id', - IGNORE_MULTIPLE - )->id; - - $instanceselect = 'blockname = :blockname and pagetypepattern = :pagetypepattern and subpagepattern = :subpagepattern'; - $instanceparams['blockname'] = 'myoverview'; - $instanceparams['pagetypepattern'] = 'my-index'; - $instanceparams['subpagepattern'] = $mysubpagepattern; - - $total = $DB->count_records_select('block_instances', $instanceselect, $instanceparams); - // Check if where have blocks to delete. - if ($total > 0) { - $instances = $DB->get_recordset_select('block_instances', $instanceselect, $instanceparams); - // Show a progress bar. - $pbar = new progress_bar('deleteblockinstances', 500, true); - $i = 0; - $pbar->update($i, $total, "Deleting block instance - $i/$total."); - foreach ($instances as $instance) { - delete_block_instance($instance); - // Update progress. - $pbar->update($i, $total, "Deleting block instance - $i/$total."); - $i++; - } - $instances->close(); - // Update progress. - $pbar->update($total, $total, "Deleting block instance - $total/$total."); - } + // Add new instance to the /my/courses.php page. + $subpagepattern = $DB->get_record('my_pages', [ + 'userid' => null, + 'name' => MY_PAGE_COURSES, + 'private' => MY_PAGE_PUBLIC, + ], 'id', IGNORE_MULTIPLE)->id; - // Begin looking for any and all instances of course overview in customised /my pages. - $pageselect = 'name = :name and private = :private and userid IS NOT NULL'; - $pageparams['name'] = MY_PAGE_DEFAULT; - $pageparams['private'] = MY_PAGE_PRIVATE; - - $total = $DB->count_records_select('my_pages', $pageselect, $pageparams); - // Check if where have pages to check for blocks. - if ($total > 0) { - $pages = $DB->get_recordset_select('my_pages', $pageselect, $pageparams); - // Show a progress bar. - $pagepbar = new progress_bar('deletepageblockinstances', 500, true); - $i = 0; - $pagepbar->update($i, $total, "Deleting user page block instance - $i/$total."); - foreach ($pages as $page) { - $blocksql = 'blockname = :blockname and pagetypepattern = :pagetypepattern and subpagepattern = :subpagepattern'; - $blockparams['blockname'] = 'myoverview'; - $blockparams['pagetypepattern'] = 'my-index'; - $blockparams['subpagepattern'] = $page->id; - $instances = $DB->get_records_select('block_instances', $blocksql, $blockparams); - foreach ($instances as $instance) { - delete_block_instance($instance); - } - // Update progress. - $pagepbar->update($i, $total, "Deleting user page block instance - $i/$total."); - $i++; - } - $pages->close(); - // Update progress. - $pagepbar->update($total, $total, "Deleting user page block instance - $total/$total."); - } + $blockname = 'myoverview'; + $pagetypepattern = 'my-index'; - // Add new instance to the /my/courses.php page. - $subpagepattern = $DB->get_record( - 'my_pages', - ['userid' => null, 'name' => MY_PAGE_COURSES, 'private' => MY_PAGE_PUBLIC], - 'id', - IGNORE_MULTIPLE - )->id; + $blockparams = [ + 'blockname' => $blockname, + 'pagetypepattern' => $pagetypepattern, + 'subpagepattern' => $subpagepattern, + ]; // See if this block already somehow exists, it should not but who knows. - if (!$DB->record_exists('block_instances', ['blockname' => 'myoverview', - 'pagetypepattern' => 'my-index', 'subpagepattern' => $subpagepattern])) { + if (!$DB->record_exists('block_instances', $blockparams)) { $page = new moodle_page(); - $systemcontext = context_system::instance(); - $page->set_context($systemcontext); + $page->set_context(context_system::instance()); // Add the block to the default /my/courses. $page->blocks->add_region('content'); - $page->blocks->add_block('myoverview', 'content', 0, false, 'my-index', $subpagepattern); + $page->blocks->add_block($blockname, 'content', 0, false, $pagetypepattern, $subpagepattern); } upgrade_block_savepoint(true, 2021052504, 'myoverview', false); diff --git a/blocks/recentlyaccesseditems/db/upgrade.php b/blocks/recentlyaccesseditems/db/upgrade.php index 3c36d0852467d..060319e878f27 100644 --- a/blocks/recentlyaccesseditems/db/upgrade.php +++ b/blocks/recentlyaccesseditems/db/upgrade.php @@ -38,6 +38,8 @@ defined('MOODLE_INTERNAL') || die(); +require_once("{$CFG->libdir}/db/upgradelib.php"); + /** * Upgrade the recentlyaccesseditems db table. * @@ -76,36 +78,8 @@ function xmldb_block_recentlyaccesseditems_upgrade($oldversion, $block) { // Put any upgrade step following this. if ($oldversion < 2022030200) { - $context = context_system::instance(); - - // Begin looking for any and all customised /my pages. - $pageselect = 'name = :name and private = :private'; - $pageparams['name'] = '__default'; - $pageparams['private'] = 1; - $pages = $DB->get_recordset_select('my_pages', $pageselect, $pageparams); - foreach ($pages as $subpage) { - $blockinstance = $DB->get_record('block_instances', ['blockname' => 'recentlyaccesseditems', - 'pagetypepattern' => 'my-index', 'subpagepattern' => $subpage->id]); - - if (!$blockinstance) { - // Insert the recentlyaccesseditems into the default index page. - $blockinstance = new stdClass; - $blockinstance->blockname = 'recentlyaccesseditems'; - $blockinstance->parentcontextid = $context->id; - $blockinstance->showinsubcontexts = false; - $blockinstance->pagetypepattern = 'my-index'; - $blockinstance->subpagepattern = $subpage->id; - $blockinstance->defaultregion = 'side-post'; - $blockinstance->defaultweight = -10; - $blockinstance->timecreated = time(); - $blockinstance->timemodified = time(); - $DB->insert_record('block_instances', $blockinstance); - } else if ($blockinstance->defaultregion !== 'side-post') { - $blockinstance->defaultregion = 'side-post'; - $DB->update_record('block_instances', $blockinstance); - } - } - $pages->close(); + // Update all recentlyaccesseditems blocks in the my-index to be in the main side-post region. + upgrade_block_set_defaultregion('recentlyaccesseditems', '__default', 'my-index', 'side-post'); upgrade_block_savepoint(true, 2022030200, 'recentlyaccesseditems', false); } diff --git a/blocks/timeline/db/upgrade.php b/blocks/timeline/db/upgrade.php index ac1512ef7ef36..a019622a21c7f 100644 --- a/blocks/timeline/db/upgrade.php +++ b/blocks/timeline/db/upgrade.php @@ -36,6 +36,10 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +defined('MOODLE_INTERNAL') || die(); + +require_once("{$CFG->libdir}/db/upgradelib.php"); + /** * Upgrade the timeline block * @param int $oldversion @@ -57,36 +61,8 @@ function xmldb_block_timeline_upgrade($oldversion, $block) { // Put any upgrade step following this. if ($oldversion < 2022030200) { - $context = context_system::instance(); - - // Begin looking for any and all customised /my pages. - $pageselect = 'name = :name and private = :private'; - $pageparams['name'] = '__default'; - $pageparams['private'] = 1; - $pages = $DB->get_recordset_select('my_pages', $pageselect, $pageparams); - foreach ($pages as $subpage) { - $blockinstance = $DB->get_record('block_instances', ['blockname' => 'timeline', - 'pagetypepattern' => 'my-index', 'subpagepattern' => $subpage->id]); - - if (!$blockinstance) { - // Insert the timeline into the default index page. - $blockinstance = new stdClass; - $blockinstance->blockname = 'timeline'; - $blockinstance->parentcontextid = $context->id; - $blockinstance->showinsubcontexts = false; - $blockinstance->pagetypepattern = 'my-index'; - $blockinstance->subpagepattern = $subpage->id; - $blockinstance->defaultregion = 'content'; - $blockinstance->defaultweight = -10; - $blockinstance->timecreated = time(); - $blockinstance->timemodified = time(); - $DB->insert_record('block_instances', $blockinstance); - } else if ($blockinstance->defaultregion !== 'content') { - $blockinstance->defaultregion = 'content'; - $DB->update_record('block_instances', $blockinstance); - } - } - $pages->close(); + // Update all timeline blocks in the my-index to be in the main content region. + upgrade_block_set_defaultregion('timeline', '__default', 'my-index', 'content'); upgrade_block_savepoint(true, 2022030200, 'timeline', false); } diff --git a/lib/db/upgradelib.php b/lib/db/upgradelib.php index 2ac6b7553935f..4607164c8e19a 100644 --- a/lib/db/upgradelib.php +++ b/lib/db/upgradelib.php @@ -1294,3 +1294,220 @@ function upgrade_add_item_to_usermenu(string $menuitem): void { set_config('customusermenuitems', implode("\n", $lines)); } } + +/** + * Update all instances of a block shown on a pagetype to a new default region, adding missing block instances where + * none is found. + * + * Note: This is intended as a helper to add blocks to all instances of the standard my-page. It will only work where + * the subpagepattern is a string representation of an integer. If there are any string values this will not work. + * + * @param string $blockname The block name, without the block_ frankenstyle component + * @param string $pagename The type of my-page to match + * @param string $pagetypepattern The page type pattern to match for the block + * @param string $newdefaultregion The new region to set + */ +function upgrade_block_set_defaultregion( + string $blockname, + string $pagename, + string $pagetypepattern, + string $newdefaultregion +): void { + global $DB; + + // The subpagepattern is a string. + // In all core blocks it contains a string represnetation of an integer, but it is theoretically possible for a + // community block to do something different. + // This function is not suited to those cases. + $subpagepattern = $DB->sql_cast_char2int('bi.subpagepattern'); + $subpageempty = $DB->sql_isnotempty('block_instances', 'bi.subpagepattern', true, false); + + // If a subquery returns any NULL then the NOT IN returns no results at all. + // By adding a join in the inner select on my_pages we remove any possible nulls and prevent any need for + // additional casting to filter out the nulls. + $sql = <<execute($sql, [ + 'selectblockname' => $blockname, + 'selectparentcontext' => $context->id, + 'selectpagetypepattern' => $pagetypepattern, + 'selectdefaultregion' => $newdefaultregion, + 'selecttimecreated' => time(), + 'selecttimemodified' => time(), + 'pagetypepattern' => $pagetypepattern, + 'blockname' => $blockname, + 'pagename' => $pagename, + ]); + + // Update the existing instances. + $sql = << :existingnewdefaultregion + ) bid + ) + EOF; + + $DB->execute($sql, [ + 'newdefaultregion' => $newdefaultregion, + 'pagetypepattern' => $pagetypepattern, + 'blockname' => $blockname, + 'existingnewdefaultregion' => $newdefaultregion, + 'pagename' => $pagename, + ]); + + // Note: This can be time consuming! + \context_helper::create_instances(CONTEXT_BLOCK); +} + +/** + * Remove all instances of a block on pages of the specified pagetypepattern. + * + * Note: This is intended as a helper to add blocks to all instances of the standard my-page. It will only work where + * the subpagepattern is a string representation of an integer. If there are any string values this will not work. + * + * @param string $blockname The block name, without the block_ frankenstyle component + * @param string $pagename The type of my-page to match + * @param string $pagetypepattern This is typically used on the 'my-index' + */ +function upgrade_block_delete_instances( + string $blockname, + string $pagename, + string $pagetypepattern +): void { + global $DB; + + $deleteblockinstances = function (string $instanceselect, array $instanceparams) use ($DB) { + $deletesql = <<delete_records_subquery('context', 'id', 'cid', $deletesql, array_merge($instanceparams, [ + 'contextlevel' => CONTEXT_BLOCK, + ])); + + $deletesql = <<delete_records_subquery('block_positions', 'id', 'bpid', $deletesql, $instanceparams); + + $blockhidden = $DB->sql_concat("'block'", 'bi.id', "'hidden'"); + $blockdocked = $DB->sql_concat("'docked_block_instance_'", 'bi.id'); + $deletesql = <<delete_records_subquery('user_preferences', 'id', 'pid', $deletesql, $instanceparams); + + $deletesql = <<delete_records_subquery('block_instances', 'id', 'bid', $deletesql, $instanceparams); + }; + + // Delete the default indexsys version of the block. + $subpagepattern = $DB->get_record('my_pages', [ + 'userid' => null, + 'name' => $pagename, + 'private' => MY_PAGE_PRIVATE, + ], 'id', IGNORE_MULTIPLE)->id; + + $instanceselect = << $blockname, + 'pagetypepattern' => $pagetypepattern, + 'subpagepattern' => $subpagepattern, + ]; + $deleteblockinstances($instanceselect, $params); + + // The subpagepattern is a string. + // In all core blocks it contains a string represnetation of an integer, but it is theoretically possible for a + // community block to do something different. + // This function is not suited to those cases. + $subpagepattern = $DB->sql_cast_char2int('bi.subpagepattern'); + + // Look for any and all instances of the block in customised /my pages. + $subpageempty = $DB->sql_isnotempty('block_instances', 'bi.subpagepattern', true, false); + $instanceselect = << $blockname, + 'pagetypepattern' => $pagetypepattern, + 'pagename' => $pagename, + 'private' => MY_PAGE_PRIVATE, + ]; + + $deleteblockinstances($instanceselect, $params); +} diff --git a/lib/tests/db/upgradelib_test.php b/lib/tests/db/upgradelib_test.php new file mode 100644 index 0000000000000..742f3959c5086 --- /dev/null +++ b/lib/tests/db/upgradelib_test.php @@ -0,0 +1,442 @@ +. + +// Note: This namespace is not technically correct, but we have to make it different to the tests for lib/upgradelib.php +// and this is more correct than alternatives. +namespace core\db; + +/** + * Unit tests for the lib/db/upgradelib.php library. + * + * @package core + * @category phpunit + * @copyright 2022 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class upgradelib_test extends \advanced_testcase { + + /** + * Shared setup for the testcase. + */ + public function setUp(): void { + global $CFG; + + require_once("{$CFG->libdir}/db/upgradelib.php"); + require_once("{$CFG->dirroot}/my/lib.php"); + } + + /** + * Ensure that the upgrade_block_set_defaultregion function performs as expected. + * + * Only targetted blocks and pages should be affected. + * + * @covers ::upgrade_block_set_defaultregion + */ + public function test_upgrade_block_set_defaultregion(): void { + global $DB; + + $this->resetAfterTest(); + + // Ensure that only the targetted blocks are affected. + + // Create a my-index entry for the Dashboard. + $dashboardid = $DB->insert_record('my_pages', (object) [ + 'name' => '__default', + 'private' => MY_PAGE_PRIVATE, + ]); + + // Create a page for the my-courses page. + $mycoursesid = $DB->insert_record('my_pages', (object) [ + 'name' => '__courses', + 'private' => MY_PAGE_PRIVATE, + ]); + + $unchanged = []; + $changed = []; + + // Create several blocks of different types. + // These are not linked to the my-index page above, so should not be modified. + $unchanged[] = $this->getDataGenerator()->create_block('online_users', [ + 'defaultregion' => 'left-side', + ]); + $unchanged[] = $this->getDataGenerator()->create_block('myoverview', [ + 'defaultregion' => 'left-side', + ]); + $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [ + 'defaultregion' => 'left-side', + ]); + + // These are on the my-index above, but are not the block being updated. + $unchanged[] = $this->getDataGenerator()->create_block('online_users', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $dashboardid, + 'defaultregion' => 'left-side', + ]); + $unchanged[] = $this->getDataGenerator()->create_block('myoverview', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $dashboardid, + 'defaultregion' => 'left-side', + ]); + + // This is on a my-index page, and is the affected block, but is on the mycourses page, not the dashboard. + $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $mycoursesid, + 'defaultregion' => 'left-side', + ]); + + // This is on the default dashboard, and is the affected block, but not a my-index page. + $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [ + 'pagetypepattern' => 'not-my-index', + 'subpagepattern' => $dashboardid, + 'defaultregion' => 'left-side', + ]); + + // This is the match which should be changed. + $changed[] = $this->getDataGenerator()->create_block('calendar_month', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $dashboardid, + 'defaultregion' => 'left-side', + ]); + + // Perform the operation. + // Target all calendar_month blocks matching 'my-index' and update them to the 'content' region where they + // belong to the user dashboard ('pagename' == '__default'). + upgrade_block_set_defaultregion('calendar_month', '__default', 'my-index', 'content'); + + // Ensure that the relevant blocks remain unchanged. + foreach ($unchanged as $original) { + $block = $DB->get_record('block_instances', ['id' => $original->id]); + $this->assertEquals($original, $block); + } + + // Ensure that only the expected blocks were changed. + foreach ($changed as $original) { + $block = $DB->get_record('block_instances', ['id' => $original->id]); + $this->assertNotEquals($original, $block); + + // Only the defaultregion should be updated to content. No other changes are expected. + $expected = (object) $original; + $expected->defaultregion = 'content'; + $this->assertEquals($expected, $block); + } + } + + /** + * Ensure that the upgrade_block_set_defaultregion function performs as expected. + * + * Missing block entries will be created. + * + * @covers ::upgrade_block_set_defaultregion + */ + public function test_upgrade_block_set_defaultregion_create_missing(): void { + global $DB; + + $this->resetAfterTest(); + + // Ensure that only the targetted blocks are affected. + + $dashboards = []; + $mycourses = []; + // Create dashboard pages for a number of users. + while (count($dashboards) < 10) { + $user = $this->getDataGenerator()->create_user(); + $dashboards[] = $DB->insert_record('my_pages', (object) [ + 'userid' => $user->id, + 'name' => '__default', + 'private' => MY_PAGE_PRIVATE, + ]); + + $mycourses[] = $DB->insert_record('my_pages', (object) [ + 'userid' => $user->id, + 'name' => '__courses', + 'private' => MY_PAGE_PRIVATE, + ]); + } + + // Enusre that there are no blocks initially. + foreach ($dashboards as $dashboardid) { + $this->assertEquals(0, $DB->count_records('block_instances', [ + 'subpagepattern' => $dashboardid, + ])); + } + + // Perform the operation. + // Target all calendar_month blocks matching 'my-index' and update them to the 'content' region where they + // belong to the user dashboard ('pagename' == '__default'). + // Any dashboards which are missing the block will have it created by the operation. + upgrade_block_set_defaultregion('calendar_month', '__default', 'my-index', 'content'); + + // Each of the dashboards should not have a block instance of the calendar_month block in the 'content' region + // on 'my-index' only. + foreach ($dashboards as $dashboardid) { + // Only one block should have been created. + $blocks = $DB->get_records('block_instances', [ + 'subpagepattern' => $dashboardid, + ]); + $this->assertCount(1, $blocks); + + $theblock = reset($blocks); + $this->assertEquals('calendar_month', $theblock->blockname); + $this->assertEquals('content', $theblock->defaultregion); + $this->assertEquals('my-index', $theblock->pagetypepattern); + } + + // Enusre that there are no blocks on the mycourses page. + foreach ($mycourses as $pageid) { + $this->assertEquals(0, $DB->count_records('block_instances', [ + 'subpagepattern' => $pageid, + ])); + } + } + + /** + * Ensure that the upgrade_block_delete_instances function performs as expected. + * + * Missing block entries will be created. + * + * @covers ::upgrade_block_delete_instances + */ + public function test_upgrade_block_delete_instances(): void { + global $DB; + + $this->resetAfterTest(); + + $DB->delete_records('block_instances'); + + // Ensure that only the targetted blocks are affected. + + // Get the my-index entry for the Dashboard. + $dashboardid = $DB->get_record('my_pages', [ + 'userid' => null, + 'name' => '__default', + 'private' => MY_PAGE_PRIVATE, + ], 'id')->id; + + // Get the page for the my-courses page. + $mycoursesid = $DB->get_record('my_pages', [ + 'name' => MY_PAGE_COURSES, + ], 'id')->id; + + $dashboards = []; + $mycourses = []; + $unchanged = []; + $unchangedcontexts = []; + $unchangedpreferences = []; + $deleted = []; + $deletedcontexts = []; + $deletedpreferences = []; + + // Create several blocks of different types. + // These are not linked to the my page above, so should not be modified. + $unchanged[] = $this->getDataGenerator()->create_block('online_users', [ + 'defaultregion' => 'left-side', + ]); + $unchanged[] = $this->getDataGenerator()->create_block('myoverview', [ + 'defaultregion' => 'left-side', + ]); + $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [ + 'defaultregion' => 'left-side', + ]); + + // These are on the my-index above, but are not the block being updated. + $unchanged[] = $this->getDataGenerator()->create_block('online_users', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $dashboardid, + 'defaultregion' => 'left-side', + ]); + $unchanged[] = $this->getDataGenerator()->create_block('myoverview', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $dashboardid, + 'defaultregion' => 'left-side', + ]); + + // This is on a my-index page, and is the affected block, but is on the mycourses page, not the dashboard. + $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $mycoursesid, + 'defaultregion' => 'left-side', + ]); + + // This is on the default dashboard, and is the affected block, but not a my-index page. + $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [ + 'pagetypepattern' => 'not-my-index', + 'subpagepattern' => $dashboardid, + 'defaultregion' => 'left-side', + ]); + + // This is the match which should be changed. + $deleted[] = $this->getDataGenerator()->create_block('calendar_month', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $dashboardid, + 'defaultregion' => 'left-side', + ]); + + // Create blocks for users with preferences now. + while (count($dashboards) < 10) { + $userunchangedblocks = []; + $userdeletedblocks = []; + + $user = $this->getDataGenerator()->create_user(); + $userdashboardid = $DB->insert_record('my_pages', (object) [ + 'userid' => $user->id, + 'name' => '__default', + 'private' => MY_PAGE_PRIVATE, + ]); + $dashboards[] = $userdashboardid; + + $usermycoursesid = $DB->insert_record('my_pages', (object) [ + 'userid' => $user->id, + 'name' => '__courses', + 'private' => MY_PAGE_PRIVATE, + ]); + $mycourses[] = $usermycoursesid; + + // These are on the my-index above, but are not the block being updated. + $userunchangedblocks[] = $this->getDataGenerator()->create_block('online_users', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $userdashboardid, + 'defaultregion' => 'left-side', + ]); + $userunchangedblocks[] = $this->getDataGenerator()->create_block('myoverview', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $userdashboardid, + 'defaultregion' => 'left-side', + ]); + + // This is on a my-index page, and is the affected block, but is on the mycourses page, not the dashboard. + $userunchangedblocks[] = $this->getDataGenerator()->create_block('calendar_month', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $usermycoursesid, + 'defaultregion' => 'left-side', + ]); + + // This is on the default dashboard, and is the affected block, but not a my-index page. + $userunchangedblocks[] = $this->getDataGenerator()->create_block('calendar_month', [ + 'pagetypepattern' => 'not-my-index', + 'subpagepattern' => $userdashboardid, + 'defaultregion' => 'left-side', + ]); + + // This is the match which should be changed. + $userdeletedblocks[] = $this->getDataGenerator()->create_block('calendar_month', [ + 'pagetypepattern' => 'my-index', + 'subpagepattern' => $userdashboardid, + 'defaultregion' => 'left-side', + ]); + + $unchanged += $userunchangedblocks; + $deleted += $userdeletedblocks; + + foreach ($userunchangedblocks as $block) { + // Create user preferences for these blocks. + set_user_preference("block{$block->id}hidden", 1, $user); + set_user_preference("docked_block_instance_{$block->id}", 1, $user); + $unchangedpreferences[] = $block->id; + } + + foreach ($userdeletedblocks as $block) { + // Create user preferences for these blocks. + set_user_preference("block{$block->id}hidden", 1, $user); + set_user_preference("docked_block_instance_{$block->id}", 1, $user); + $deletedpreferences[] = $block->id; + } + } + + // Create missing contexts. + \context_helper::create_instances(CONTEXT_BLOCK); + + // Ensure that other related test data is present. + $systemcontext = \context_system::instance(); + foreach ($unchanged as $block) { + // Get contexts. + $unchangedcontexts[] = \context_block::instance($block->id); + + // Create a block position. + $DB->insert_record('block_positions', [ + 'blockinstanceid' => $block->id, + 'contextid' => $systemcontext->id, + 'pagetype' => 'course-view-topics', + 'region' => 'site-post', + 'weight' => 1, + 'visible' => 1, + ]); + } + + foreach ($deleted as $block) { + // Get contexts. + $deletedcontexts[] = \context_block::instance($block->id); + + // Create a block position. + $DB->insert_record('block_positions', [ + 'blockinstanceid' => $block->id, + 'contextid' => $systemcontext->id, + 'pagetype' => 'course-view-topics', + 'region' => 'site-post', + 'weight' => 1, + 'visible' => 1, + ]); + } + + // Perform the operation. + // Target all calendar_month blocks matching 'my-index' and update them to the 'content' region where they + // belong to the user dashboard ('pagename' == '__default'). + upgrade_block_delete_instances('calendar_month', '__default', 'my-index'); + + // Ensure that the relevant blocks remain unchanged. + foreach ($unchanged as $original) { + $block = $DB->get_record('block_instances', ['id' => $original->id]); + $this->assertEquals($original, $block); + + // Ensure that the block positions remain. + $this->assertEquals(1, $DB->count_records('block_positions', ['blockinstanceid' => $original->id])); + } + + foreach ($unchangedcontexts as $context) { + // Ensure that the context still exists. + $this->assertEquals(1, $DB->count_records('context', ['id' => $context->id])); + } + + foreach ($unchangedpreferences as $blockid) { + // Ensure that the context still exists. + $this->assertEquals(1, $DB->count_records('user_preferences', ['name' => "block{$blockid}hidden"])); + $this->assertEquals(1, $DB->count_records('user_preferences', [ + 'name' => "docked_block_instance_{$blockid}", + ])); + } + + // Ensure that only the expected blocks were changed. + foreach ($deleted as $original) { + $this->assertCount(0, $DB->get_records('block_instances', ['id' => $original->id])); + + // Ensure that the block positions was removed. + $this->assertEquals(0, $DB->count_records('block_positions', ['blockinstanceid' => $original->id])); + } + + foreach ($deletedcontexts as $context) { + // Ensure that the context still exists. + $this->assertEquals(0, $DB->count_records('context', ['id' => $context->id])); + } + + foreach ($deletedpreferences as $blockid) { + // Ensure that the context still exists. + $this->assertEquals(0, $DB->count_records('user_preferences', ['name' => "block{$blockid}hidden"])); + $this->assertEquals(0, $DB->count_records('user_preferences', [ + 'name' => "docked_block_instance_{$blockid}", + ])); + } + } +}