diff --git a/blocks/classes/external.php b/blocks/classes/external.php index 95de3b808e9..a72d5ef8d74 100644 --- a/blocks/classes/external.php +++ b/blocks/classes/external.php @@ -39,6 +39,68 @@ require_once("$CFG->libdir/externallib.php"); */ class core_block_external extends external_api { + + /** + * Returns a block structure. + * + * @return external_single_structure a block single structure. + * @since Moodle 3.6 + */ + private static function get_block_structure() { + return new external_single_structure( + array( + 'instanceid' => new external_value(PARAM_INT, 'Block instance id.'), + 'name' => new external_value(PARAM_PLUGIN, 'Block name.'), + 'region' => new external_value(PARAM_ALPHANUMEXT, 'Block region.'), + 'positionid' => new external_value(PARAM_INT, 'Position id.'), + 'collapsible' => new external_value(PARAM_BOOL, 'Whether the block is collapsible.'), + 'dockable' => new external_value(PARAM_BOOL, 'Whether the block is dockable.'), + 'weight' => new external_value(PARAM_INT, 'Used to order blocks within a region.', VALUE_OPTIONAL), + 'visible' => new external_value(PARAM_BOOL, 'Whether the block is visible.', VALUE_OPTIONAL), + ), 'Block information.' + ); + } + + /** + * Convenience function for getting all the blocks of the current $PAGE. + * + * @param bool $includeinvisible Whether to include not visible blocks or not + * @return array Block information + * @since Moodle 3.6 + */ + private static function get_all_current_page_blocks($includeinvisible = false) { + global $PAGE, $OUTPUT; + + // Load the block instances for all the regions. + $PAGE->blocks->load_blocks($includeinvisible); + $PAGE->blocks->create_all_block_instances(); + + $allblocks = array(); + $blocks = $PAGE->blocks->get_content_for_all_regions($OUTPUT); + foreach ($blocks as $region => $regionblocks) { + $regioninstances = $PAGE->blocks->get_blocks_for_region($region); + // Index block instances to retrieve required info. + $blockinstances = array(); + foreach ($regioninstances as $ri) { + $blockinstances[$ri->instance->id] = $ri->instance; + } + + foreach ($regionblocks as $bc) { + $allblocks[] = [ + 'instanceid' => $bc->blockinstanceid, + 'name' => $blockinstances[$bc->blockinstanceid]->blockname, + 'region' => $region, + 'positionid' => $bc->blockpositionid, + 'collapsible' => (bool) $bc->collapsible, + 'dockable' => (bool) $bc->dockable, + 'weight' => $blockinstances[$bc->blockinstanceid]->weight, + 'visible' => $blockinstances[$bc->blockinstanceid]->visible, + ]; + } + } + return $allblocks; + } + /** * Returns description of get_course_blocks parameters. * @@ -62,7 +124,7 @@ class core_block_external extends external_api { * @since Moodle 3.3 */ public static function get_course_blocks($courseid) { - global $OUTPUT, $PAGE; + global $PAGE; $warnings = array(); $params = self::validate_parameters(self::get_course_blocks_parameters(), ['courseid' => $courseid]); @@ -82,27 +144,10 @@ class core_block_external extends external_api { $PAGE->set_pagetype('course-view-' . $course->format); } - // Load the block instances for all the regions. - $PAGE->blocks->load_blocks(); - $PAGE->blocks->create_all_block_instances(); - - $finalblocks = array(); - $blocks = $PAGE->blocks->get_content_for_all_regions($OUTPUT); - foreach ($blocks as $region => $regionblocks) { - foreach ($regionblocks as $bc) { - $finalblocks[] = [ - 'instanceid' => $bc->blockinstanceid, - 'name' => $bc->attributes['data-block'], - 'region' => $region, - 'positionid' => $bc->blockpositionid, - 'collapsible' => (bool) $bc->collapsible, - 'dockable' => (bool) $bc->dockable, - ]; - } - } + $allblocks = self::get_all_current_page_blocks(); return array( - 'blocks' => $finalblocks, + 'blocks' => $allblocks, 'warnings' => $warnings ); } @@ -117,21 +162,91 @@ class core_block_external extends external_api { return new external_single_structure( array( - 'blocks' => new external_multiple_structure( - new external_single_structure( - array( - 'instanceid' => new external_value(PARAM_INT, 'Block instance id.'), - 'name' => new external_value(PARAM_PLUGIN, 'Block name.'), - 'region' => new external_value(PARAM_ALPHANUMEXT, 'Block region.'), - 'positionid' => new external_value(PARAM_INT, 'Position id.'), - 'collapsible' => new external_value(PARAM_BOOL, 'Whether the block is collapsible.'), - 'dockable' => new external_value(PARAM_BOOL, 'hether the block is dockable.'), - ), 'Block information.' - ), 'List of blocks in the course.' - ), + 'blocks' => new external_multiple_structure(self::get_block_structure(), 'List of blocks in the course.'), 'warnings' => new external_warnings(), ) ); } + /** + * Returns description of get_dashboard_blocks parameters. + * + * @return external_function_parameters + * @since Moodle 3.6 + */ + public static function get_dashboard_blocks_parameters() { + return new external_function_parameters( + array( + 'userid' => new external_value(PARAM_INT, 'User id (optional), default is current user.', VALUE_DEFAULT, 0) + ) + ); + } + + /** + * Returns blocks information for the given user dashboard. + * + * @param int $userid The user id to retrive the blocks from, optional, default is to current user. + * @return array Blocks list and possible warnings + * @throws moodle_exception + * @since Moodle 3.6 + */ + public static function get_dashboard_blocks($userid = 0) { + global $CFG, $USER, $PAGE; + + require_once($CFG->dirroot . '/my/lib.php'); + + $warnings = array(); + $params = self::validate_parameters(self::get_dashboard_blocks_parameters(), ['userid' => $userid]); + + $userid = $params['userid']; + if (empty($userid)) { + $userid = $USER->id; + } + + if ($USER->id != $userid) { + // We must check if the current user can view other users dashboard. + require_capability('moodle/site:config', context_system::instance()); + $user = core_user::get_user($userid, '*', MUST_EXIST); + core_user::require_active_user($user); + } + + $context = context_user::instance($userid);; + self::validate_context($context); + + // Get the My Moodle page info. Should always return something unless the database is broken. + if (!$currentpage = my_get_page($userid, MY_PAGE_PRIVATE)) { + throw new moodle_exception('mymoodlesetup'); + } + + $PAGE->set_context($context); + $PAGE->set_pagelayout('mydashboard'); + $PAGE->set_pagetype('my-index'); + $PAGE->blocks->add_region('content'); // Need to add this special regition to retrieve the central blocks. + $PAGE->set_subpage($currentpage->id); + + // Load the block instances in the current $PAGE for all the regions. + $returninvisible = has_capability('moodle/my:manageblocks', $context) ? true : false; + $allblocks = self::get_all_current_page_blocks($returninvisible); + + return array( + 'blocks' => $allblocks, + 'warnings' => $warnings + ); + } + + /** + * Returns description of get_dashboard_blocks result values. + * + * @return external_single_structure + * @since Moodle 3.6 + */ + public static function get_dashboard_blocks_returns() { + + return new external_single_structure( + array( + 'blocks' => new external_multiple_structure(self::get_block_structure(), 'List of blocks in the dashboard.'), + 'warnings' => new external_warnings(), + ) + ); + } } diff --git a/blocks/tests/externallib_test.php b/blocks/tests/externallib_test.php index fdf3bea560b..566ce532583 100644 --- a/blocks/tests/externallib_test.php +++ b/blocks/tests/externallib_test.php @@ -29,6 +29,7 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); +require_once($CFG->dirroot . '/my/lib.php'); /** * External block functions unit tests @@ -138,4 +139,140 @@ class core_block_externallib_testcase extends externallib_advanced_testcase { } + /** + * Test user get default dashboard blocks. + */ + public function test_get_dashboard_blocks_default_dashboard() { + global $PAGE, $DB; + $this->resetAfterTest(true); + + $user = $this->getDataGenerator()->create_user(); + $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. + + // Get the expected default blocks. + $alldefaultblocksordered = $DB->get_records_menu('block_instances', + array('pagetypepattern' => 'my-index'), 'defaultregion, defaultweight ASC', 'id, blockname'); + + $this->setUser($user); + + // Check for the default blocks. + $result = core_block_external::get_dashboard_blocks($user->id); + // We need to execute the return values cleaning process to simulate the web service server. + $result = external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result); + // Expect all blogs except learning plans one (no learning plans to show). + $this->assertCount(count($alldefaultblocksordered) - 1, $result['blocks']); + $returnedblocks = array(); + foreach ($result['blocks'] as $block) { + // Check all the returned blocks are in the expected blocks array. + $this->assertContains($block['name'], $alldefaultblocksordered); + $returnedblocks[] = $block['name']; + } + // Remove lp block. + array_shift($alldefaultblocksordered); + // Check that we received the blocks in the expected order. + $this->assertEquals(array_values($alldefaultblocksordered), $returnedblocks); + } + + /** + * Test user get default dashboard blocks including a sticky block. + */ + public function test_get_dashboard_blocks_default_dashboard_including_sticky_block() { + global $PAGE, $DB; + $this->resetAfterTest(true); + + $user = $this->getDataGenerator()->create_user(); + $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. + + // Get the expected default blocks. + $alldefaultblocks = $DB->get_records_menu('block_instances', array('pagetypepattern' => 'my-index'), '', 'id, blockname'); + + // Now, add a sticky block. + $page = new moodle_page(); + $page->set_context(context_system::instance()); + $page->set_pagetype('my-index'); + $page->set_url(new moodle_url('/')); + $page->blocks->add_region('side-pre'); + $page->blocks->load_blocks(); + $page->blocks->add_block('myprofile', 'side-pre', 0, true, '*'); + + $this->setUser($user); + + // Check for the default blocks plus the sticky. + $result = core_block_external::get_dashboard_blocks($user->id); + // We need to execute the return values cleaning process to simulate the web service server. + $result = external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result); + // Expect all blogs plus sticky one except learning plans one (no learning plans to show). + $this->assertCount(count($alldefaultblocks), $result['blocks']); + $found = false; + foreach ($result['blocks'] as $block) { + if ($block['name'] == 'myprofile') { + $this->assertEquals('side-pre', $block['region']); + $found = true; + continue; + } + // Check that the block is in the expected blocks array. + $this->assertContains($block['name'], $alldefaultblocks); + } + $this->assertTrue($found); + } + + /** + * Test admin get user's custom dashboard blocks. + */ + public function test_get_dashboard_blocks_custom_user_dashboard() { + global $PAGE, $DB; + $this->resetAfterTest(true); + + $user = $this->getDataGenerator()->create_user(); + $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. + + // Get the expected default blocks. + $alldefaultblocks = $DB->get_records_menu('block_instances', array('pagetypepattern' => 'my-index'), '', 'id, blockname'); + + // Add a custom block. + $page = new moodle_page(); + $page->set_context(context_user::instance($user->id)); + $page->set_pagelayout('mydashboard'); + $page->set_pagetype('my-index'); + $page->blocks->add_region('content'); + $currentpage = my_get_page($user->id, MY_PAGE_PRIVATE); + $page->set_subpage($currentpage->id); + $page->blocks->load_blocks(); + $page->blocks->add_block('myprofile', 'content', 0, false); + + $this->setAdminUser(); + + // Check for the new block as admin for a user. + $result = core_block_external::get_dashboard_blocks($user->id); + // We need to execute the return values cleaning process to simulate the web service server. + $result = external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result); + // Expect all default blogs plys the one we added except learning plans one (no learning plans to show). + $this->assertCount(count($alldefaultblocks), $result['blocks']); + $found = false; + foreach ($result['blocks'] as $block) { + if ($block['name'] == 'myprofile') { + $this->assertEquals('content', $block['region']); + $found = true; + continue; + } + // Check that the block is in the expected blocks array. + $this->assertContains($block['name'], $alldefaultblocks); + } + $this->assertTrue($found); + } + + /** + * Test user tries to get other user blocks not having permission. + */ + public function test_get_dashboard_blocks_other_user_missing_permissions() { + $this->resetAfterTest(true); + + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + + $this->setUser($user1); + + $this->expectException('moodle_exception'); + core_block_external::get_dashboard_blocks($user2->id); + } } diff --git a/blocks/upgrade.txt b/blocks/upgrade.txt index 1234de4f591..c42d1ece63e 100644 --- a/blocks/upgrade.txt +++ b/blocks/upgrade.txt @@ -4,6 +4,7 @@ information provided here is intended especially for developers. === 3.6 === * The timeline view from block_myoverview has been split out into block_timeline. +* External function core_blocks::get_course_blocks now returns the block visible status and weight for ordering. === 3.4 === diff --git a/lib/db/services.php b/lib/db/services.php index f187293dfdd..5cec94e4616 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -2225,6 +2225,15 @@ $functions = array( 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), + 'core_block_get_dashboard_blocks' => array( + 'classname' => 'core_block_external', + 'methodname' => 'get_dashboard_blocks', + 'description' => 'Returns blocks information for the given user dashboard.', + 'type' => 'read', + 'capabilities' => '', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + ), + // Filters functions. 'core_filters_get_available_in_context' => array( 'classname' => 'core_filters\external', diff --git a/version.php b/version.php index d6b3bc5ee30..08e47b9477a 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2018101800.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2018101800.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.