diff --git a/admin/tool/behat/tests/behat/i_am_on_page.feature b/admin/tool/behat/tests/behat/i_am_on_page.feature index 9c60a8b3da9..8f4abc23ac4 100644 --- a/admin/tool/behat/tests/behat/i_am_on_page.feature +++ b/admin/tool/behat/tests/behat/i_am_on_page.feature @@ -71,9 +71,9 @@ Feature: Use core page resolvers for the I am on the page steps Then I should see "" Examples: - | description | identifier | shouldsee | - | Admin page | "Admin notifications" | Check for available updates | - | Home page | Homepage | Course overview | + | description | identifier | shouldsee | + | Admin page | "Admin notifications" | Check for available updates | + | Home page | Homepage | Calendar | Scenario Outline: When I am on a named page logged in as When I am on the page logged in as admin @@ -82,4 +82,4 @@ Feature: Use core page resolvers for the I am on the page steps Examples: | description | identifier | shouldsee | | Admin page | "Admin notifications" | Check for available updates | - | Home page | Homepage | Course overview | + | Home page | Homepage | Calendar | diff --git a/admin/tool/policy/tests/behat/acceptances.feature b/admin/tool/policy/tests/behat/acceptances.feature index 85df22dbaa9..998049ca794 100644 --- a/admin/tool/policy/tests/behat/acceptances.feature +++ b/admin/tool/policy/tests/behat/acceptances.feature @@ -36,7 +36,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users And I press "Next" And I set the field "I agree to the This site policy" to "1" And I press "Next" - And I should see "Course overview" + And I should see "Calendar" And I log out And I log in as "manager" And I press "Next" @@ -82,7 +82,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users And I press "Next" And I set the field "I agree to the This site policy" to "1" And I press "Next" - And I should see "Course overview" + And I should see "Calendar" And I navigate to "Users > Privacy and policies > Manage policies" in site administration And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row" And I click on "Accept This site policy" "link" in the "User One" "table_row" @@ -111,7 +111,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users And I set the field "I agree to the This site policy" to "1" And I set the field "I agree to the This privacy policy" to "1" And I press "Next" - And I should see "Course overview" + And I should see "Calendar" And I log out And I log in as "manager" And I press "Next" @@ -300,7 +300,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users And I press "Next" And I set the field "I agree to the This site policy" to "1" And I press "Next" - And I should see "Course overview" + And I should see "Calendar" And I log out And I log in as "admin" And I navigate to "Users > Privacy and policies > Manage policies" in site administration @@ -311,4 +311,4 @@ Feature: Viewing acceptances reports and accepting on behalf of other users And I press "Continue" And I log out When I log in as "user1" - Then I should see "Course overview" + Then I should see "Calendar" diff --git a/admin/tool/usertours/tests/behat/create_tour.feature b/admin/tool/usertours/tests/behat/create_tour.feature index 92b676f3e6e..b1e1b3edaac 100644 --- a/admin/tool/usertours/tests/behat/create_tour.feature +++ b/admin/tool/usertours/tests/behat/create_tour.feature @@ -20,7 +20,7 @@ Feature: Add a new user tour | Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | And I add steps to the "First tour" tour: | targettype | targetvalue_block | Title | Content | - | Block | Course overview | Course overview | This area shows you what's happening in some of your courses | + | Block | Timeline | Timeline | This is the Timeline. All of your upcoming activities can be found here | | Block | Calendar | Calendar | This is the Calendar. All of your assignments and due dates can be found here | And I add steps to the "First tour" tour: | targettype | targetvalue_selector | Title | Content | @@ -28,7 +28,7 @@ Feature: Add a new user tour When I am on homepage Then I should see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful" And I click on "Next" "button" in the "[data-role='flexitour-step']" "css_element" - And I should see "This area shows you what's happening in some of your courses" + And I should see "This is the Timeline. All of your upcoming activities can be found here" And I should not see "This is the Calendar. All of your assignments and due dates can be found here" And I click on "Next" "button" in the "[data-role='flexitour-step']" "css_element" And I should see "This is the Calendar. All of your assignments and due dates can be found here" @@ -95,7 +95,7 @@ Feature: Add a new user tour | Display in middle of page | Welcome | First step of the Tour | And I add steps to the "Steps tour" tour: | targettype | targetvalue_block | Title | Content | - | Block | Course overview | Course overview | Second step of the Tour | + | Block | Timeline | Timeline | Second step of the Tour | | Block | Calendar | Calendar | Third step of the Tour | When I am on homepage Then I should see "First step of the Tour" @@ -129,7 +129,7 @@ Feature: Add a new user tour | Display in middle of page | Welcome | First step of the Tour | And I add steps to the "Steps tour" tour: | targettype | targetvalue_block | Title | Content | - | Block | Course overview | Course overview | Second step of the Tour | + | Block | Timeline | Timeline | Second step of the Tour | | Block | Calendar | Calendar | Third step of the Tour | When I am on homepage Then I should see "First step of the Tour" diff --git a/admin/tool/usertours/tests/behat/tour_navigation.feature b/admin/tool/usertours/tests/behat/tour_navigation.feature index 5e577d10c40..12123d23226 100644 --- a/admin/tool/usertours/tests/behat/tour_navigation.feature +++ b/admin/tool/usertours/tests/behat/tour_navigation.feature @@ -53,14 +53,14 @@ Feature: Steps can be navigated within a tour | Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | And I add steps to the "First tour" tour: | targettype | targetvalue_block | Title | Content | - | Block | Course overview | Course overview | This area shows you what's happening in some of your courses | + | Block | Timeline | Timeline | This is the Timeline. All of your upcoming activities can be found here | | Block | Calendar | Calendar | This is the Calendar. All of your assignments and due dates can be found here | When I am on homepage Then I should see "Skip tour" And I should see "Next (1/3)" And I click on "Next (1/3)" "button" in the "Welcome" "dialogue" And I should see "Skip tour" - And I click on "Next (2/3)" "button" in the "Course overview" "dialogue" + And I click on "Next (2/3)" "button" in the "Timeline" "dialogue" And I should see "End tour" @javascript diff --git a/blocks/moodleblock.class.php b/blocks/moodleblock.class.php index 76de67eef6a..d50478fecb7 100644 --- a/blocks/moodleblock.class.php +++ b/blocks/moodleblock.class.php @@ -249,7 +249,7 @@ class block_base { $this->arialabel = $bc->arialabel; } - if ($this->page->user_is_editing()) { + if ($this->page->user_is_editing() && $this->instance_can_be_edited()) { $bc->controls = $this->page->blocks->edit_controls($this); } else { // we must not use is_empty on hidden blocks @@ -692,6 +692,15 @@ class block_base { return true; } + /** + * If overridden and set to false by the block it will not be editable. + * + * @return bool + */ + public function instance_can_be_edited() { + return true; + } + /** @callback callback functions for comments api */ public static function comment_template($options) { $ret = <<get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true)); // Get the expected default blocks. - $alldefaultblocksordered = $DB->get_records_menu('block_instances', - array('pagetypepattern' => 'my-index'), 'defaultregion, defaultweight ASC', 'id, blockname'); + $alldefaultblocksordered = $DB->get_records_menu( + 'block_instances', + array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id), + 'defaultregion, defaultweight ASC', + 'id, blockname' + ); $this->setUser($user); @@ -368,8 +373,13 @@ class core_block_externallib_testcase extends externallib_advanced_testcase { $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. + $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true)); // Get the expected default blocks. - $alldefaultblocks = $DB->get_records_menu('block_instances', array('pagetypepattern' => 'my-index'), '', 'id, blockname'); + $alldefaultblocks = $DB->get_records_menu( + 'block_instances', array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id), + '', + 'id, blockname' + ); // Now, add a sticky block. $page = new moodle_page(); @@ -411,8 +421,14 @@ class core_block_externallib_testcase extends externallib_advanced_testcase { $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. + $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true)); // Get the expected default blocks. - $alldefaultblocks = $DB->get_records_menu('block_instances', array('pagetypepattern' => 'my-index'), '', 'id, blockname'); + $alldefaultblocks = $DB->get_records_menu( + 'block_instances', + array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id), + '', + 'id, blockname' + ); // Add a custom block. $page = new moodle_page(); diff --git a/grade/report/grader/tests/behat/switch_views.feature b/grade/report/grader/tests/behat/switch_views.feature index fa65ba83ef9..98b764b86c1 100644 --- a/grade/report/grader/tests/behat/switch_views.feature +++ b/grade/report/grader/tests/behat/switch_views.feature @@ -38,7 +38,6 @@ Feature: We can change what we are viewing on the grader report And I give the grade "80.00" to the user "Student 1" for the grade item "Test assignment name 1" And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment name 2" And I press "Save changes" - And I turn editing mode off @javascript Scenario: View and minimise the grader report containing hidden activities diff --git a/lang/en/admin.php b/lang/en/admin.php index 589b2dc2620..460fe06e21f 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -858,6 +858,7 @@ $string['modsettings'] = 'Manage activities'; $string['modulesecurity'] = 'Module security'; $string['multilangforceold'] = 'Force old multilang syntax: <span> without the class="multilang" and <lang>'; $string['mustenablestats'] = 'Statistics have not yet been enabled on this site.'; +$string['mycourses'] = 'My courses'; $string['mycoursesperpage'] = 'Number of courses'; $string['mydashboard'] = 'System default dashboard'; $string['mymoodle'] = 'Dashboard'; diff --git a/lang/en/my.php b/lang/en/my.php index 97a6bfa4635..cc0b8f73567 100644 --- a/lang/en/my.php +++ b/lang/en/my.php @@ -22,6 +22,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +$string['coursemanagementoptions'] = 'Course management options'; $string['mymoodle'] = 'Dashboard'; $string['nocourses'] = 'No course information to show.'; $string['noguest'] = 'The Dashboard page is not available to guest users'; diff --git a/lib/ajax/blocks.php b/lib/ajax/blocks.php index d12eccea684..7d1a7625d90 100644 --- a/lib/ajax/blocks.php +++ b/lib/ajax/blocks.php @@ -60,6 +60,7 @@ $PAGE->blocks->add_custom_regions_for_pagetype($pagetype); $pagetype = explode('-', $pagetype); switch ($pagetype[0]) { case 'my': + case 'mycourses': $PAGE->set_blocks_editing_capability('moodle/my:manageblocks'); break; case 'user': diff --git a/lib/blocklib.php b/lib/blocklib.php index b16fa61bff8..56feb707a4a 100644 --- a/lib/blocklib.php +++ b/lib/blocklib.php @@ -2647,7 +2647,30 @@ function blocks_add_default_system_blocks() { $subpagepattern = null; } - $newblocks = array('timeline', 'private_files', 'badges', 'calendar_month'); - $newcontent = array('myoverview'); - $page->blocks->add_blocks(array(BLOCK_POS_RIGHT => $newblocks, 'content' => $newcontent), 'my-index', $subpagepattern); + if ($defaultmycoursespage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__courses', 'private' => 0))) { + $mycoursesubpagepattern = $defaultmycoursespage->id; + } else { + $mycoursesubpagepattern = null; + } + + $page->blocks->add_blocks([ + BLOCK_POS_RIGHT => [ + 'private_files', + 'badges', + ], + 'content' => [ + 'timeline', + 'calendar_month', + ]], + 'my-index', + $subpagepattern + ); + + $page->blocks->add_blocks([ + 'content' => [ + 'myoverview' + ]], + 'my-index', + $mycoursesubpagepattern + ); } diff --git a/lib/classes/event/mycourses_viewed.php b/lib/classes/event/mycourses_viewed.php new file mode 100644 index 00000000000..d1d435f2bcb --- /dev/null +++ b/lib/classes/event/mycourses_viewed.php @@ -0,0 +1,58 @@ +. + +namespace core\event; + +/** + * My courses viewed event class. + * + * Class for event to be triggered when a user views their My courses page. + * + * @package core + * @copyright 2021 Mathew May + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mycourses_viewed extends base { + + /** + * Init method. + * + * @return void + */ + protected function init(): void { + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description(): string { + return "The user with id '$this->userid' has viewed their my courses page"; + } + + /** + * Return localised event name. + * + * @return string + */ + public static function get_name(): string { + return get_string('eventmycoursesviewed', 'core'); + } + +} diff --git a/lib/classes/navigation/views/primary.php b/lib/classes/navigation/views/primary.php index e7fd0a125ef..d970f337c81 100644 --- a/lib/classes/navigation/views/primary.php +++ b/lib/classes/navigation/views/primary.php @@ -57,8 +57,8 @@ class primary extends view { } } - // Add a dummy mycourse link to a mycourses page. - $this->add(get_string('mycourses'), new \moodle_url('/course/index.php'), self::TYPE_ROOTNODE, null, 'courses'); + // Add the mycourses link. + $this->add(get_string('mycourses'), new \moodle_url('/my/courses.php'), self::TYPE_ROOTNODE, null, 'courses'); // Add the site admin node. We are using the settingsnav so as to avoid rechecking permissions again. $settingsnav = $this->page->settingsnav; diff --git a/lib/db/install.php b/lib/db/install.php index e1ba215072d..db1041e4700 100644 --- a/lib/db/install.php +++ b/lib/db/install.php @@ -310,6 +310,13 @@ function xmldb_main_install() { $mypage->private = 1; $DB->insert_record('my_pages', $mypage); + $mycoursespage = new stdClass(); + $mycoursespage->userid = null; + $mycoursespage->name = '__courses'; + $mycoursespage->private = 0; + $mycoursespage->sortorder = 0; + $DB->insert_record('my_pages', $mycoursespage); + // Set a sensible default sort order for the most-used question types. set_config('multichoice_sortorder', 1, 'question'); set_config('truefalse_sortorder', 2, 'question'); diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index c76b4d959f9..38a3954ccc9 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -3145,5 +3145,17 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2021110800.03); } + if ($oldversion < 2021111200.01) { + + $mycoursespage = new stdClass(); + $mycoursespage->userid = null; + $mycoursespage->name = '__courses'; + $mycoursespage->private = 0; + $mycoursespage->sortorder = 0; + $DB->insert_record('my_pages', $mycoursespage); + + upgrade_main_savepoint(true, 2021111200.01); + } + return true; } diff --git a/lib/navigationlib.php b/lib/navigationlib.php index 45c324eb399..87a7a1409e8 100644 --- a/lib/navigationlib.php +++ b/lib/navigationlib.php @@ -1328,7 +1328,14 @@ class global_navigation extends navigation_node { $this->rootnodes['site'] = $this->add_course($SITE); $this->rootnodes['myprofile'] = $this->add(get_string('profile'), null, self::TYPE_USER, null, 'myprofile'); $this->rootnodes['currentcourse'] = $this->add(get_string('currentcourse'), null, self::TYPE_ROOTNODE, null, 'currentcourse'); - $this->rootnodes['mycourses'] = $this->add(get_string('mycourses'), null, self::TYPE_ROOTNODE, null, 'mycourses', new pix_icon('i/course', '')); + $this->rootnodes['mycourses'] = $this->add( + get_string('mycourses'), + new moodle_url('/my/courses.php'), + self::TYPE_ROOTNODE, + null, + 'mycourses', + new pix_icon('i/course', '') + ); $this->rootnodes['courses'] = $this->add(get_string('courses'), new moodle_url('/course/index.php'), self::TYPE_ROOTNODE, null, 'courses'); if (!core_course_category::user_top()) { $this->rootnodes['courses']->hide(); @@ -1521,7 +1528,7 @@ class global_navigation extends navigation_node { foreach ($this->rootnodes as $node) { // Dont remove the home node /** @var navigation_node $node */ - if (!in_array($node->key, ['home', 'myhome']) && !$node->has_children() && !$node->isactive) { + if (!in_array($node->key, ['home', 'mycourses', 'myhome']) && !$node->has_children() && !$node->isactive) { $node->remove(); } } @@ -2880,6 +2887,9 @@ class global_navigation extends navigation_node { // This required as there are not other guaranteed nodes that may be loaded. $coursenode->add('frontpageloaded', null, self::TYPE_CUSTOM, null, 'frontpageloaded')->display = false; + // Add My courses to the site pages within the navigation structure so the block can read it. + $coursenode->add(get_string('mycourses'), new moodle_url('/my/courses.php'), self::TYPE_CUSTOM, null, 'mycourses'); + // Participants. if ($navoptions->participants) { $coursenode->add(get_string('participants'), new moodle_url('/user/index.php?id='.$course->id), self::TYPE_CUSTOM, get_string('participants'), 'participants'); diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index ba37129b867..16ef03f41ef 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -4372,7 +4372,8 @@ EOD; $pagetype = $this->page->pagetype; $homepage = get_home_page(); $homepagetype = null; - if ($homepage == HOMEPAGE_MY) { + // Add a special case since /my/courses is a part of the /my subsystem. + if ($homepage == HOMEPAGE_MY && $this->page->title !== get_string('mycourses')) { $homepagetype = 'my-index'; } else if ($homepage == HOMEPAGE_SITE) { $homepagetype = 'site-index'; diff --git a/lib/tests/behat/behat_navigation.php b/lib/tests/behat/behat_navigation.php index d48ac3c759d..88f56cb62bb 100644 --- a/lib/tests/behat/behat_navigation.php +++ b/lib/tests/behat/behat_navigation.php @@ -717,6 +717,9 @@ class behat_navigation extends behat_base { case 'Homepage': return new moodle_url('/'); + case 'My courses': + return new moodle_url('/my/courses.php'); + case 'Admin notifications': return new moodle_url('/admin/'); diff --git a/my/courses.php b/my/courses.php new file mode 100644 index 00000000000..0351e413ce4 --- /dev/null +++ b/my/courses.php @@ -0,0 +1,86 @@ +. + +/** + * My Courses. + * + * - each user can currently have their own page (cloned from system and then customised) + * - only the user can see their own dashboard + * - users can add any blocks they want + * + * @package core + * @subpackage my + * @copyright 2021 Mathew May + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(__DIR__ . '/../config.php'); +require_once($CFG->dirroot . '/my/lib.php'); + +redirect_if_major_upgrade_required(); + +require_login(); + +$hassiteconfig = has_capability('moodle/site:config', context_system::instance()); +if ($hassiteconfig && moodle_needs_upgrading()) { + redirect(new moodle_url('/admin/index.php')); +} + +$context = context_system::instance(); + +// Get the My Moodle page info. Should always return something unless the database is broken. +if (!$currentpage = my_get_page(null, MY_PAGE_PUBLIC, MY_PAGE_COURSES)) { + throw new Exception('mymoodlesetup'); +} + +// Start setting up the page. +$PAGE->set_context($context); +$PAGE->set_url('/my/courses.php'); +$PAGE->add_body_classes(['limitedwidth', 'page-mycourses']); +$PAGE->set_pagelayout('mycourses'); +$PAGE->has_secondary_navigation_setter(false); + +$PAGE->set_pagetype('my-index'); +$PAGE->set_subpage($currentpage->id); +$PAGE->set_title(get_string('mycourses')); +$PAGE->set_heading(get_string('mycourses')); +// Force the add block out of the default area. +$PAGE->theme->addblockposition = BLOCK_ADDBLOCK_POSITION_CUSTOM; + +// Add course management if the user has the capabilities for it. +$coursecat = core_course_category::user_top(); +if ($coursecat->can_create_course() || $coursecat->has_manage_capability()) { + $data = [ + 'newcourseurl' => new moodle_url('/course/edit.php', ['category' => $coursecat->id]), + 'manageurl' => new moodle_url('/course/management.php', ['categoryid' => $coursecat->id]), + ]; + $PAGE->add_header_action($OUTPUT->render_from_template('my/dropdown', $data)); +} + +echo $OUTPUT->header(); + +if (core_userfeedback::should_display_reminder()) { + core_userfeedback::print_reminder_block(); +} + +echo $OUTPUT->custom_block_region('content'); + +echo $OUTPUT->footer(); + +// Trigger dashboard has been viewed event. +$eventparams = array('context' => $context); +$event = \core\event\mycourses_viewed::create($eventparams); +$event->trigger(); diff --git a/my/lib.php b/my/lib.php index 590fe95d67c..9002df0b1c6 100644 --- a/my/lib.php +++ b/my/lib.php @@ -29,41 +29,70 @@ define('MY_PAGE_PUBLIC', 0); define('MY_PAGE_PRIVATE', 1); +define('MY_PAGE_DEFAULT', '__default'); +define('MY_PAGE_COURSES', '__courses'); require_once("$CFG->libdir/blocklib.php"); -/* +/** * For a given user, this returns the $page information for their My Moodle page * + * @param int|null $userid the id of the user whose page should be retrieved + * @param int|null $private either MY_PAGE_PRIVATE or MY_PAGE_PUBLIC + * @param string|null $pagename Differentiate between standard /my or /courses pages. */ -function my_get_page($userid, $private=MY_PAGE_PRIVATE) { +function my_get_page(?int $userid, int $private = MY_PAGE_PRIVATE, string $pagename = MY_PAGE_DEFAULT) { global $DB, $CFG; if (empty($CFG->forcedefaultmymoodle) && $userid) { // Ignore custom My Moodle pages if admin has forced them // Does the user have their own page defined? If so, return it. - if ($customised = $DB->get_record('my_pages', array('userid' => $userid, 'private' => $private))) { + if ($customised = $DB->get_record( + 'my_pages', + array('userid' => $userid, 'private' => $private, 'name' => $pagename), + '*', + IGNORE_MULTIPLE + )) { return $customised; } } // Otherwise return the system default page - return $DB->get_record('my_pages', array('userid' => null, 'name' => '__default', 'private' => $private)); + return $DB->get_record('my_pages', array('userid' => null, 'name' => $pagename, 'private' => $private), '*', IGNORE_MULTIPLE); } -/* +/** * This copies a system default page to the current user * + * @param int $userid the id of the user whose page should be reset + * @param int $private either MY_PAGE_PRIVATE or MY_PAGE_PUBLIC + * @param string $pagetype either my-index or user-profile + * @param string $pagename Differentiate between standard /my or /courses pages. */ -function my_copy_page($userid, $private=MY_PAGE_PRIVATE, $pagetype='my-index') { +function my_copy_page( + int $userid, + int $private = MY_PAGE_PRIVATE, + string $pagetype = 'my-index', + string $pagename = MY_PAGE_DEFAULT +) { global $DB; - if ($customised = $DB->get_record('my_pages', array('userid' => $userid, 'private' => $private))) { + if ($customised = $DB->get_record( + 'my_pages', + array('userid' => $userid, 'name' => $pagename, 'private' => $private), + '*', + IGNORE_MULTIPLE + )) { return $customised; // We're done! } // Get the system default page - if (!$systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__default', 'private' => $private))) { + if (!$systempage = $DB->get_record( + 'my_pages', + array('userid' => null, 'name' => $pagename, 'private' => $private), + '*', + IGNORE_MULTIPLE + )) { return false; // error } @@ -117,18 +146,24 @@ function my_copy_page($userid, $private=MY_PAGE_PRIVATE, $pagetype='my-index') { return $page; } -/* +/** * For a given user, this deletes their My Moodle page and returns them to the system default. * * @param int $userid the id of the user whose page should be reset * @param int $private either MY_PAGE_PRIVATE or MY_PAGE_PUBLIC * @param string $pagetype either my-index or user-profile + * @param string $pagename Differentiate between standard /my or /courses pages. * @return mixed system page, or false on error */ -function my_reset_page($userid, $private=MY_PAGE_PRIVATE, $pagetype='my-index') { +function my_reset_page( + int $userid, + int $private = MY_PAGE_PRIVATE, + string $pagetype='my-index', + string $pagename = MY_PAGE_DEFAULT +) { global $DB, $CFG; - $page = my_get_page($userid, $private); + $page = my_get_page($userid, $private, $pagename); if ($page->userid == $userid) { $context = context_user::instance($userid); if ($blocks = $DB->get_records('block_instances', array('parentcontextid' => $context->id, @@ -140,11 +175,16 @@ function my_reset_page($userid, $private=MY_PAGE_PRIVATE, $pagetype='my-index') } } $DB->delete_records('block_positions', ['subpage' => $page->id, 'pagetype' => $pagetype, 'contextid' => $context->id]); - $DB->delete_records('my_pages', array('id' => $page->id)); + $DB->delete_records('my_pages', array('id' => $page->id, 'name' => $pagename)); } // Get the system default page - if (!$systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__default', 'private' => $private))) { + if (!$systempage = $DB->get_record( + 'my_pages', + array('userid' => null, 'name' => $pagename, 'private' => $private), + '*', + IGNORE_MULTIPLE + )) { return false; // error } @@ -166,10 +206,16 @@ function my_reset_page($userid, $private=MY_PAGE_PRIVATE, $pagetype='my-index') * * @param int $private Either MY_PAGE_PRIVATE or MY_PAGE_PUBLIC. * @param string $pagetype Either my-index or user-profile. - * @param progress_bar $progressbar A progress bar to update. + * @param progress_bar|null $progressbar A progress bar to update. + * @param string $pagename Differentiate between standard /my or /courses pages. * @return void */ -function my_reset_page_for_all_users($private = MY_PAGE_PRIVATE, $pagetype = 'my-index', $progressbar = null) { +function my_reset_page_for_all_users( + int $private = MY_PAGE_PRIVATE, + string $pagetype = 'my-index', + ?progress_bar $progressbar = null, + string $pagename = MY_PAGE_DEFAULT +) { global $DB; // This may take a while. Raise the execution time limit. @@ -199,12 +245,14 @@ function my_reset_page_for_all_users($private = MY_PAGE_PRIVATE, $pagetype = 'my AND bi.pagetypepattern = :pagetypepattern AND (bi.subpagepattern IS NULL OR bi.subpagepattern = " . $DB->sql_concat("''", 'p.id') . ") WHERE p.private = :private + AND p.name = :name AND p.userid $infragment"; $params = array_merge([ 'private' => $private, 'usercontextlevel' => CONTEXT_USER, - 'pagetypepattern' => $pagetype + 'pagetypepattern' => $pagetype, + 'name' => $pagename ], $inparams); $blockids = $DB->get_fieldset_sql($sql, $params); diff --git a/my/templates/dropdown.mustache b/my/templates/dropdown.mustache new file mode 100644 index 00000000000..d1ff8f5a83f --- /dev/null +++ b/my/templates/dropdown.mustache @@ -0,0 +1,37 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template my/dropdown + + Simple dropdown for the my/courses page + + Example context (json): + { + "newcourseurl": "https://moodle.test/course/edit.php?category=1", + "manageurl": "https://moodle.test/course/management.php?categoryid=1" + } +}} + diff --git a/my/tests/behat/my_courses.feature b/my/tests/behat/my_courses.feature new file mode 100644 index 00000000000..bddf5f1e542 --- /dev/null +++ b/my/tests/behat/my_courses.feature @@ -0,0 +1,13 @@ +@core @core_my +Feature: Run tests over my courses. + + Scenario: Admin can add new courses or manage them from my courses + Given I am on the "My courses" page logged in as "admin" + And I click on "Course management options" "link" + And I click on "New course" "link" + And I wait to be redirected + Then I should see "Add a new course" + And I am on the "My courses" page + And I click on "Course management options" "link" + And I click on "Manage courses" "link" + And I should see "Course and category management" diff --git a/my/upgrade.txt b/my/upgrade.txt new file mode 100644 index 00000000000..36979069d89 --- /dev/null +++ b/my/upgrade.txt @@ -0,0 +1,4 @@ +This files describes changes in the my system. +=== 4.0 === +* Introduce a new /courses page +* Tighten up the typing within the lib file diff --git a/theme/boost/classes/boostnavbar.php b/theme/boost/classes/boostnavbar.php index 9ef725d49c7..fb922ed8e2e 100644 --- a/theme/boost/classes/boostnavbar.php +++ b/theme/boost/classes/boostnavbar.php @@ -64,10 +64,9 @@ class boostnavbar implements \renderable { // Set the designated one path for courses. $mycoursesnode = $this->get_item('mycourses'); if (!is_null($mycoursesnode)) { - // TODO: Once MDL-70801 lands point this to the new page. - $url = new \moodle_url('/course/'); + $url = new \moodle_url('/my/courses.php'); $mycoursesnode->action = $url; - $mycoursesnode->text = get_string('courses'); + $mycoursesnode->text = get_string('mycourses'); } $this->remove_no_link_items(); diff --git a/theme/boost/config.php b/theme/boost/config.php index 141a07d140d..ee61566f096 100644 --- a/theme/boost/config.php +++ b/theme/boost/config.php @@ -78,6 +78,12 @@ $THEME->layouts = [ 'regions' => array('side-pre'), 'defaultregion' => 'side-pre', ), + // My courses page. + 'mycourses' => array( + 'file' => 'columns2.php', + 'regions' => array('content'), + 'defaultregion' => 'content', + ), // My dashboard page. 'mydashboard' => array( 'file' => 'columns2.php', diff --git a/theme/boost/lang/en/theme_boost.php b/theme/boost/lang/en/theme_boost.php index 16f509b45d9..438b148d33c 100644 --- a/theme/boost/lang/en/theme_boost.php +++ b/theme/boost/lang/en/theme_boost.php @@ -49,6 +49,7 @@ $string['rawscss_desc'] = 'Use this field to provide SCSS or CSS code which will $string['rawscsspre'] = 'Raw initial SCSS'; $string['rawscsspre_desc'] = 'In this field you can provide initialising SCSS code, it will be injected before everything else. Most of the time you will use this setting to define variables.'; $string['region-side-pre'] = 'Right'; +$string['region-content'] = 'Content'; $string['showfooter'] = 'Show footer'; $string['privacy:metadata:preference:draweropennav'] = 'The user\'s preference for hiding or showing the drawer menu navigation.'; $string['privacy:drawernavclosed'] = 'The current preference for the navigation drawer is closed.'; diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss index 71ebbc0ba6b..502a9dbda71 100644 --- a/theme/boost/scss/moodle/core.scss +++ b/theme/boost/scss/moodle/core.scss @@ -2875,3 +2875,13 @@ body.dragging { } } } + +.page-mycourses { + #region-main { + padding: 0; + } + #region-main, + .block_myoverview { + border: 0; + } +} diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 0926fb7ae73..d47c52657ca 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -12211,6 +12211,13 @@ body.dragging .dragging { .collapse-list .collapse-list-item-content .collapse-list-item { padding-left: calc(1rem * 3); } +.page-mycourses #region-main { + padding: 0; } + +.page-mycourses #region-main, +.page-mycourses .block_myoverview { + border: 0; } + .icon { font-size: 16px; width: 16px; diff --git a/theme/classic/config.php b/theme/classic/config.php index da34422bc66..a4d584931bd 100644 --- a/theme/classic/config.php +++ b/theme/classic/config.php @@ -72,6 +72,12 @@ $THEME->layouts = [ 'regions' => array('side-pre'), 'defaultregion' => 'side-pre', ), + // My courses page. + 'mycourses' => array( + 'file' => 'columns.php', + 'regions' => array('content'), + 'defaultregion' => 'content', + ), // My dashboard page. 'mydashboard' => array( 'file' => 'columns.php', diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index c46cd2bdbaf..c5ea2e1b5b2 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -12211,6 +12211,13 @@ body.dragging .dragging { .collapse-list .collapse-list-item-content .collapse-list-item { padding-left: calc(1rem * 3); } +.page-mycourses #region-main { + padding: 0; } + +.page-mycourses #region-main, +.page-mycourses .block_myoverview { + border: 0; } + .icon { font-size: 16px; width: 16px; diff --git a/theme/classic/tests/behat/behat_theme_classic_behat_course.php b/theme/classic/tests/behat/behat_theme_classic_behat_course.php index 0cb1ddbb175..e7c787e9f04 100644 --- a/theme/classic/tests/behat/behat_theme_classic_behat_course.php +++ b/theme/classic/tests/behat/behat_theme_classic_behat_course.php @@ -43,7 +43,8 @@ class behat_theme_classic_behat_course extends behat_course { public function i_navigate_to_course_participants() { $coursestr = behat_context_helper::escape(get_string('courses')); $mycoursestr = behat_context_helper::escape(get_string('mycourses')); - $xpath = "//div[contains(@class,'block')]//li[p/*[string(.)=$coursestr or string(.)=$mycoursestr]]"; + $xpath = "//div[contains(@class,'block')]//li[contains(@class,'contains_branch')]" . + "[p/*[string(.)=$coursestr or string(.)=$mycoursestr]]"; $this->execute('behat_general::i_click_on_in_the', [get_string('participants'), 'link', $xpath, 'xpath_element']); } diff --git a/version.php b/version.php index e30705c2c9c..25ceefd4570 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2021111200.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2021111200.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.0dev+ (Build: 20211112)'; // Human-friendly version name