ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç lib.php000064400000030502152265255660006041 0ustar00. /** * My Moodle -- a user's personal dashboard * * This file contains common functions for the dashboard and profile pages. * * @package moodlecore * @subpackage my * @copyright 2010 Remote-Learner.net * @author Hubert Chathi * @author Olav Jordan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ 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(?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, 'name' => $pagename), '*', IGNORE_MULTIPLE )) { return $customised; } } // Otherwise return the system default page 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( 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, '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' => $pagename, 'private' => $private), '*', IGNORE_MULTIPLE )) { return false; // error } // Clone the basic system page record $page = clone($systempage); unset($page->id); $page->userid = $userid; $page->id = $DB->insert_record('my_pages', $page); // Clone ALL the associated blocks as well $systemcontext = context_system::instance(); $usercontext = context_user::instance($userid); $blockinstances = $DB->get_records('block_instances', array('parentcontextid' => $systemcontext->id, 'pagetypepattern' => $pagetype, 'subpagepattern' => $systempage->id)); $roles = get_all_roles(); $newblockinstanceids = []; foreach ($blockinstances as $instance) { $originalid = $instance->id; $originalcontext = context_block::instance($originalid); unset($instance->id); $instance->parentcontextid = $usercontext->id; $instance->subpagepattern = $page->id; $instance->timecreated = time(); $instance->timemodified = $instance->timecreated; $instance->id = $DB->insert_record('block_instances', $instance); $newblockinstanceids[$originalid] = $instance->id; $blockcontext = context_block::instance($instance->id); // Just creates the context record $block = block_instance($instance->blockname, $instance); if (empty($block) || !$block->instance_copy($originalid)) { debugging("Unable to copy block-specific data for original block instance: $originalid to new block instance: $instance->id for block: $instance->blockname", DEBUG_DEVELOPER); } // Check if there are any overrides on this block instance. // We check against all roles, not just roles assigned to the user. // This is so any overrides that are applied to the system default page // will be applied to the user's page as well, even if their role assignment changes in the future. foreach ($roles as $role) { $rolecapabilities = get_capabilities_from_role_on_context($role, $originalcontext); // If there are overrides, then apply them to the new block instance. foreach ($rolecapabilities as $rolecapability) { role_change_permission( $rolecapability->roleid, $blockcontext, $rolecapability->capability, $rolecapability->permission ); } } } // Clone block position overrides. if ($blockpositions = $DB->get_records('block_positions', ['subpage' => $systempage->id, 'pagetype' => $pagetype, 'contextid' => $systemcontext->id])) { foreach ($blockpositions as &$positions) { $positions->subpage = $page->id; $positions->contextid = $usercontext->id; if (array_key_exists($positions->blockinstanceid, $newblockinstanceids)) { // For block instances that were defined on the default dashboard and copied to the user dashboard // use the new blockinstanceid. $positions->blockinstanceid = $newblockinstanceids[$positions->blockinstanceid]; } unset($positions->id); } $DB->insert_records('block_positions', $blockpositions); } 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( 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, $pagename); if ($page->userid == $userid) { $context = context_user::instance($userid); if ($blocks = $DB->get_records('block_instances', array('parentcontextid' => $context->id, 'pagetypepattern' => $pagetype))) { foreach ($blocks as $block) { if (is_null($block->subpagepattern) || $block->subpagepattern == $page->id) { blocks_delete_instance($block); } } } $DB->delete_records('block_positions', ['subpage' => $page->id, 'pagetype' => $pagetype, 'contextid' => $context->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' => $pagename, 'private' => $private), '*', IGNORE_MULTIPLE )) { return false; // error } // Trigger dashboard has been reset event. $eventparams = array( 'context' => context_user::instance($userid), 'other' => array( 'private' => $private, 'pagetype' => $pagetype, ), ); $event = \core\event\dashboard_reset::create($eventparams); $event->trigger(); return $systempage; } /** * Resets the page customisations for all users. * * @param int $private Either MY_PAGE_PRIVATE or MY_PAGE_PUBLIC. * @param string $pagetype Either my-index or user-profile. * @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( 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. core_php_time_limit::raise(); $users = $DB->get_fieldset_select( 'my_pages', 'DISTINCT(userid)', 'userid IS NOT NULL AND private = :private', ['private' => $private] ); $chunks = array_chunk($users, 20); if (!empty($progressbar) && count($chunks) > 0) { $count = count($chunks); $message = get_string('inprogress'); $progressbar->update(0, $count, $message); } foreach ($chunks as $key => $userchunk) { list($infragment, $inparams) = $DB->get_in_or_equal($userchunk, SQL_PARAMS_NAMED); // Find all the user pages and all block instances in them. $sql = "SELECT bi.id FROM {my_pages} p JOIN {context} ctx ON ctx.instanceid = p.userid AND ctx.contextlevel = :usercontextlevel JOIN {block_instances} bi ON bi.parentcontextid = ctx.id AND bi.pagetypepattern = :pagetypepattern AND (bi.subpagepattern IS NULL OR bi.subpagepattern = " . $DB->sql_cast_to_char('p.id') . ") WHERE p.private = :private AND p.name = :name AND p.userid $infragment"; $params = array_merge([ 'private' => $private, 'usercontextlevel' => CONTEXT_USER, 'pagetypepattern' => $pagetype, 'name' => $pagename ], $inparams); $blockids = $DB->get_fieldset_sql($sql, $params); // Wrap the SQL queries in a transaction. $transaction = $DB->start_delegated_transaction(); // Delete the block instances. if (!empty($blockids)) { blocks_delete_instances($blockids); } // Finally delete the pages. $DB->delete_records_select( 'my_pages', "userid $infragment AND private = :private", array_merge(['private' => $private], $inparams) ); // We should be good to go now. $transaction->allow_commit(); if (!empty($progressbar)) { $progressbar->update(((int) $key + 1), $count, $message); } } // Trigger dashboard has been reset event. $eventparams = array( 'context' => context_system::instance(), 'other' => array( 'private' => $private, 'pagetype' => $pagetype, ), ); $event = \core\event\dashboards_reset::create($eventparams); $event->trigger(); if (!empty($progressbar)) { $progressbar->update(1, 1, get_string('completed')); } } class my_syspage_block_manager extends block_manager { // HACK WARNING! // TODO: figure out a better way to do this /** * Load blocks using the system context, rather than the user's context. * * This is needed because the My Moodle pages set the page context to the * user's context for access control, etc. But the blocks for the system * pages are stored in the system context. */ public function load_blocks($includeinvisible = null) { $origcontext = $this->page->context; $this->page->context = context_system::instance(); parent::load_blocks($includeinvisible); $this->page->context = $origcontext; } } courses.php000064400000010433152265255660006757 0ustar00. /** * 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'); require_once($CFG->dirroot . '/course/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->set_docs_path('mycourses'); $PAGE->set_pagetype('my-index'); $PAGE->blocks->add_region('content'); $PAGE->set_subpage($currentpage->id); $PAGE->set_title(get_string('mycourses')); $PAGE->set_heading(get_string('mycourses')); // No blocks can be edited on this page (including by managers/admins) because: // - Course overview is a fixed item on the page and cannot be moved/removed. // - We do not want new blocks on the page. // - Only global blocks (if any) should be visible on the site panel, and cannot be moved int othe centre pane. $PAGE->force_lock_all_blocks(); // 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(); $coursemanagemenu = []; // Only display the action menu if the user has courses (otherwise, the buttons will be displayed in the zero state). if (count(enrol_get_all_users_courses($USER->id, true)) > 0) { if ($coursecat && ($category = core_course_category::get_nearest_editable_subcategory($coursecat, ['create']))) { // The user has the capability to create course. $coursemanagemenu['newcourseurl'] = new moodle_url('/course/edit.php', ['category' => $category->id]); } if ($coursecat && ($category = core_course_category::get_nearest_editable_subcategory($coursecat, ['manage']))) { // The user has the capability to manage the course category. $coursemanagemenu['manageurl'] = new moodle_url('/course/management.php', ['categoryid' => $category->id]); } if ($coursecat) { $category = core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/course:request']); if ($category && $category->can_request_course()) { $coursemanagemenu['courserequesturl'] = new moodle_url('/course/request.php', ['categoryid' => $category->id]); } } } if (!empty($coursemanagemenu)) { // Render the course management menu. $PAGE->add_header_action($OUTPUT->render_from_template('my/dropdown', $coursemanagemenu)); } 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(); indexsys.php000064400000006572152265255660007153 0ustar00. /** * My Moodle -- a user's personal dashboard * * - 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 * - the administrators can define a default site dashboard for users who have * not created their own dashboard * * This script implements the user's view of the dashboard, and allows editing * of the dashboard. * * @package moodlecore * @subpackage my * @copyright 2010 Remote-Learner.net * @author Hubert Chathi * @author Olav Jordan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define('NO_OUTPUT_BUFFERING', true); require_once(__DIR__ . '/../config.php'); require_once($CFG->dirroot . '/my/lib.php'); require_once($CFG->libdir.'/adminlib.php'); redirect_if_major_upgrade_required(); $resetall = optional_param('resetall', false, PARAM_BOOL); $pagetitle = get_string('mypage', 'admin'); $PAGE->set_secondary_active_tab('appearance'); $PAGE->set_blocks_editing_capability('moodle/my:configsyspages'); $PAGE->set_url(new moodle_url('/my/indexsys.php')); admin_externalpage_setup('mypage', '', null, '', ['pagelayout' => 'mydashboard', 'nosearch' => true]); $PAGE->add_body_class('limitedwidth'); $PAGE->set_pagetype('my-index'); $PAGE->blocks->add_region('content'); $PAGE->set_title($pagetitle); $PAGE->set_heading($pagetitle); $PAGE->set_secondary_navigation(false); $PAGE->set_primary_active_tab('myhome'); // If we are resetting all, just output a progress bar. if ($resetall && confirm_sesskey()) { echo $OUTPUT->header($pagetitle); echo $OUTPUT->heading(get_string('resettingdashboards', 'my'), 3); $progressbar = new progress_bar(); $progressbar->create(); \core\session\manager::write_close(); my_reset_page_for_all_users(MY_PAGE_PRIVATE, 'my-index', $progressbar); core\notification::success(get_string('alldashboardswerereset', 'my')); echo $OUTPUT->continue_button($PAGE->url); echo $OUTPUT->footer(); die(); } // Get the My Moodle page info. Should always return something unless the database is broken. if (!$currentpage = my_get_page(null, MY_PAGE_PRIVATE)) { throw new \moodle_exception('mymoodlesetup'); } $PAGE->set_subpage($currentpage->id); // Display a button to reset everyone's dashboard. $url = $PAGE->url; $url->params(['resetall' => true, 'sesskey' => sesskey()]); $button = $OUTPUT->single_button($url, get_string('reseteveryonesdashboard', 'my')); $PAGE->set_button($button . $PAGE->button); echo $OUTPUT->header(); echo $OUTPUT->addblockbutton('content'); echo $OUTPUT->custom_block_region('content'); echo $OUTPUT->footer(); classes/external/view_page.php000064400000006346152265255660012531 0ustar00. namespace core_my\external; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; /** * External service to log viewed Dashboard and My pages. * * This is mainly used by the mobile application. * * @package core_my * @category external * @copyright 2023 Rodrigo Mady * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 4.3 */ class view_page extends external_api { /** * Returns description of method parameters * * @return external_function_parameters */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters([ 'page' => new external_value(PARAM_TEXT, 'My page to trigger a view event'), ]); } /** * Execute the My or Dashboard view event. * * @param string $page the page for trigger the event. * @return array */ public static function execute(string $page): array { $warnings = []; $status = true; // Validate the cmid ID. ['page' => $page] = self::validate_parameters( self::execute_parameters(), ['page' => $page] ); if ($page === 'my') { $eventname = '\core\event\mycourses_viewed'; } else if ($page === 'dashboard') { $eventname = '\core\event\dashboard_viewed'; } else { $status = false; $warnings[] = [ 'item' => $page, 'warningcode' => 'invalidmypage', 'message' => 'The value for the page request is invalid!' ]; } // Trigger my/dashboard view event. $context = \context_system::instance(); self::validate_context($context); if ($status) { $event = $eventname::create(['context' => $context]); $event->trigger(); } $result = [ 'status' => $status, 'warnings' => $warnings ]; return $result; } /** * Describe the return structure of the external service. * * @return external_single_structure */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 'warnings' => new external_warnings(), ]); } } classes/privacy/provider.php000064400000005224152265255660012242 0ustar00. /** * Privacy Subsystem implementation for core_my. * * @package core_my * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_my\privacy; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\context; use core_privacy\local\request\contextlist; use core_privacy\local\request\writer; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for core_my implementing metadata, plugin, and user_preference providers. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\user_preference_provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { $collection->add_user_preference( 'user_home_page_preference', 'privacy:metadata:core_my:preference:user_home_page_preference' ); return $collection; } /** * Export all user preferences for the plugin. * * @param int $userid The userid of the user whose data is to be exported. */ public static function export_user_preferences(int $userid) { $userhomepagepreference = get_user_preferences('user_home_page_preference', null, $userid); if (null !== $userhomepagepreference) { writer::export_user_preference( 'core_my', 'user_home_page_preference', $userhomepagepreference, get_string('privacy:metadata:core_my:preference:user_home_page_preference', 'core_my') ); } } } templates/dropdown.mustache000064400000004041152265255660012146 0ustar00{{! 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 core_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", "courserequesturl": "https://moodle.test/course/request.php?categoryid=1" } }}
{{#manageurl}}
{{/manageurl}} {{#newcourseurl}}
{{/newcourseurl}} {{#courserequesturl}}
{{/courserequesturl}}
upgrade.txt000064400000000365152265255660006756 0ustar00=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This files describes changes in the my system. === 4.0 === * Introduce a new /courses page * Tighten up the typing within the lib file index.php000064400000016375152265255660006416 0ustar00. /** * My Moodle -- a user's personal dashboard * * - 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 * - the administrators can define a default site dashboard for users who have * not created their own dashboard * * This script implements the user's view of the dashboard, and allows editing * of the dashboard. * * @package moodlecore * @subpackage my * @copyright 2010 Remote-Learner.net * @author Hubert Chathi * @author Olav Jordan * @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(); // TODO Add sesskey check to edit $edit = optional_param('edit', null, PARAM_BOOL); // Turn editing on and off $reset = optional_param('reset', null, PARAM_BOOL); require_login(); $hassiteconfig = has_capability('moodle/site:config', context_system::instance()); if ($hassiteconfig && moodle_needs_upgrading()) { redirect(new moodle_url('/admin/index.php')); } $strmymoodle = get_string('myhome'); if (empty($CFG->enabledashboard)) { // Dashboard is disabled, so the /my page shouldn't be displayed. $defaultpage = get_default_home_page(); if ($defaultpage == HOMEPAGE_MYCOURSES) { // If default page is set to "My courses", redirect to it. redirect(new moodle_url('/my/courses.php')); } else { // Otherwise, raise an exception to inform the dashboard is disabled. throw new moodle_exception('error:dashboardisdisabled', 'my'); } } if (isguestuser()) { // Force them to see system default, no editing allowed // If guests are not allowed my moodle, send them to front page. if (empty($CFG->allowguestmymoodle)) { redirect(new moodle_url('/', array('redirect' => 0))); } $userid = null; $USER->editing = $edit = 0; // Just in case $context = context_system::instance(); $PAGE->set_blocks_editing_capability('moodle/my:configsyspages'); // unlikely :) $strguest = get_string('guest'); $pagetitle = "$strmymoodle ($strguest)"; } else { // We are trying to view or edit our own My Moodle page $userid = $USER->id; // Owner of the page $context = context_user::instance($USER->id); $PAGE->set_blocks_editing_capability('moodle/my:manageblocks'); $pagetitle = $strmymoodle; } // 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'); } // Start setting up the page $params = array(); $PAGE->set_context($context); $PAGE->set_url('/my/index.php', $params); $PAGE->set_pagelayout('mydashboard'); $PAGE->set_docs_path('dashboard'); $PAGE->add_body_class('limitedwidth'); $PAGE->set_pagetype('my-index'); $PAGE->blocks->add_region('content'); $PAGE->set_subpage($currentpage->id); $PAGE->set_title($pagetitle); $PAGE->set_heading($pagetitle); if (!isguestuser()) { // Skip default home page for guests if (get_home_page() != HOMEPAGE_MY) { if (optional_param('setdefaulthome', false, PARAM_BOOL)) { set_user_preference('user_home_page_preference', HOMEPAGE_MY); } else if (!empty($CFG->defaulthomepage) && $CFG->defaulthomepage == HOMEPAGE_USER) { $frontpagenode = $PAGE->settingsnav->add(get_string('frontpagesettings'), null, navigation_node::TYPE_SETTING, null); $frontpagenode->force_open(); $frontpagenode->add(get_string('makethismyhome'), new moodle_url('/my/', array('setdefaulthome' => true)), navigation_node::TYPE_SETTING); } } } // Toggle the editing state and switches if (empty($CFG->forcedefaultmymoodle) && $PAGE->user_allowed_editing()) { if ($reset !== null) { if (!is_null($userid)) { require_sesskey(); if (!$currentpage = my_reset_page($userid, MY_PAGE_PRIVATE)) { throw new \moodle_exception('reseterror', 'my'); } redirect(new moodle_url('/my')); } } else if ($edit !== null) { // Editing state was specified $USER->editing = $edit; // Change editing state } else { // Editing state is in session if ($currentpage->userid) { // It's a page we can edit, so load from session if (!empty($USER->editing)) { $edit = 1; } else { $edit = 0; } } else { // For the page to display properly with the user context header the page blocks need to // be copied over to the user context. if (!$currentpage = my_copy_page($USER->id, MY_PAGE_PRIVATE)) { throw new \moodle_exception('mymoodlesetup'); } $context = context_user::instance($USER->id); $PAGE->set_context($context); $PAGE->set_subpage($currentpage->id); // It's a system page and they are not allowed to edit system pages $USER->editing = $edit = 0; // Disable editing completely, just to be safe } } // Add button for editing page $params = array('edit' => !$edit); $resetbutton = ''; $resetstring = get_string('resetpage', 'my'); $reseturl = new moodle_url("$CFG->wwwroot/my/index.php", array('edit' => 1, 'reset' => 1)); if (!$currentpage->userid) { // viewing a system page -- let the user customise it $editstring = get_string('updatemymoodleon'); $params['edit'] = 1; } else if (empty($edit)) { $editstring = get_string('updatemymoodleon'); } else { $editstring = get_string('updatemymoodleoff'); $resetbutton = $OUTPUT->single_button($reseturl, $resetstring); } $url = new moodle_url("$CFG->wwwroot/my/index.php", $params); $button = ''; if (!$PAGE->theme->haseditswitch) { $button = $OUTPUT->single_button($url, $editstring); } $PAGE->set_button($resetbutton . $button); } else { $USER->editing = $edit = 0; } echo $OUTPUT->header(); if (core_userfeedback::should_display_reminder()) { core_userfeedback::print_reminder_block(); } echo $OUTPUT->addblockbutton('content'); echo $OUTPUT->custom_block_region('content'); echo $OUTPUT->footer(); // Trigger dashboard has been viewed event. $eventparams = array('context' => $context); $event = \core\event\dashboard_viewed::create($eventparams); $event->trigger(); tests/external/view_page_test.php000064400000005452152265255660013272 0ustar00. namespace core_my\external; use externallib_advanced_testcase; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); /** * Test Class for external function core_my_view_page. * * @package core_my * @category external * @copyright 2023 Rodrigo Mady * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 4.3 * @covers \core_my\external\view_page */ final class view_page_test extends externallib_advanced_testcase { /** * Set up for every test. */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(); } /** * Helper. * * @param string $page * @return array */ protected function view_page(string $page): array { $result = view_page::execute($page); return \core_external\external_api::clean_returnvalue(view_page::execute_returns(), $result); } /** * Test for webservice my view page. */ public function test_view_page(): void { $user = $this->getDataGenerator()->create_user(); $this->setUser($user); // Trigger and capture the event. $sink = $this->redirectEvents(); // Request to trigger the view event in my. $result = $this->view_page('my'); $this->assertTrue($result['status']); $this->assertEmpty($result['warnings']); // Request to trigger the view event in dashboard. $result = $this->view_page('dashboard'); $this->assertTrue($result['status']); $this->assertEmpty($result['warnings']); // Wrong page to trigger the event. $result = $this->view_page('test'); $this->assertFalse($result['status']); $this->assertNotEmpty($result['warnings']); $events = $sink->get_events(); // Check if the log still with two rows. $this->assertCount(2, $events); $this->assertInstanceOf('\core\event\mycourses_viewed', $events[0]); $this->assertInstanceOf('\core\event\dashboard_viewed', $events[1]); $sink->close(); } } tests/privacy/provider_test.php000064400000005027152265255660013007 0ustar00. /** * Unit tests for the core_my implementation of the privacy API. * * @package core_my * @category test * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_my\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\local\request\writer; use core_my\privacy\provider; /** * Unit tests for the core_my implementation of the privacy API. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends \core_privacy\tests\provider_testcase { /** * Test for provider::test_export_user_preferences(). */ public function test_export_user_preferences(): void { global $DB; // Test setup. $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); // Add a user home page preference for the User. set_user_preference('user_home_page_preference', HOMEPAGE_MY); // Test the user preference exists. $params = [ 'userid' => $user->id, 'name' => 'user_home_page_preference' ]; $preferences = $DB->get_record('user_preferences', $params); $this->assertEquals('user_home_page_preference', $preferences->name); // Test the user preferences export contains 1 user preference record for the User. provider::export_user_preferences($user->id); $contextuser = \context_user::instance($user->id); $writer = writer::with_context($contextuser); $this->assertTrue($writer->has_any_data()); $prefs = $writer->get_user_preferences('core_my'); $this->assertCount(1, (array) $prefs); $this->assertEquals(HOMEPAGE_MY, $prefs->user_home_page_preference->value); } } tests/event/events_test.php000064400000021041152265255660012117 0ustar00. namespace core_my\event; use context_system; use context_user; /** * Unit tests for the dashboard events. * * @package core * @category test * @copyright 2016 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class events_test extends \advanced_testcase { /** @var user cobject */ protected $user; /** * Setup often used objects for the following tests. */ protected function setUp(): void { global $USER; parent::setUp(); $this->resetAfterTest(); // The user we are going to test this on. $this->setAdminUser(); $this->user = $USER; } /** * Test the dashboard viewed event. * * There is no external API for viewing the dashboard, so the unit test will simply * create and trigger the event and ensure data is returned as expected. */ public function test_dashboard_viewed(): void { $user = $this->user; // Trigger an event: dashboard viewed. $eventparams = array( 'context' => $context = \context_user::instance($user->id) ); $event = \core\event\dashboard_viewed::create($eventparams); // Trigger and capture the event. $sink = $this->redirectEvents(); $event->trigger(); $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\dashboard_viewed', $event); $this->assertEquals($user->id, $event->userid); $this->assertDebuggingNotCalled(); } /** * Test the dashboard reset event. * * We will reset the user dashboard to * trigger the event and ensure data is returned as expected. * * @covers ::my_reset_page */ public function test_dashboard_reset(): void { global $CFG, $DB; require_once($CFG->dirroot . '/my/lib.php'); $user = $this->user; $usercontext = context_user::instance($this->user->id); // Create at least one dashboard. my_copy_page($this->user->id); $this->assertNotEmpty($DB->get_records('my_pages', ['userid' => $this->user->id, 'private' => MY_PAGE_PRIVATE, 'name' => MY_PAGE_DEFAULT])); $this->assertNotEmpty($DB->get_records('block_instances', ['parentcontextid' => $usercontext->id])); // Reset the dashboard. $sink = $this->redirectEvents(); my_reset_page($user->id); // Assert that the page and all th blocks were deleted. $this->assertEmpty($DB->get_records('my_pages', ['userid' => $this->user->id, 'private' => MY_PAGE_PRIVATE, 'name' => MY_PAGE_DEFAULT])); $this->assertEmpty($DB->get_records('block_instances', ['parentcontextid' => $usercontext->id])); // Trigger and capture the event. $events = $sink->get_events(); $event = reset($events); $sink->close(); // Check that the event data is valid. $this->assertInstanceOf('\core\event\dashboard_reset', $event); $this->assertEquals($user->id, $event->userid); $this->assertEquals(MY_PAGE_PRIVATE, $event->other['private']); $this->assertEquals('my-index', $event->other['pagetype']); $this->assertDebuggingNotCalled(); // Reset the dashboard with private parameter is set to MY_PAGE_PUBLIC and pagetype set to 'user-profile'. $systempage = $DB->get_record('my_pages', ['userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => MY_PAGE_PUBLIC]); $this->getDataGenerator()->create_block('online_users', [ 'parentcontextid' => context_system::instance()->id, 'pagetypepattern' => 'user-profile', 'subpagepattern' => $systempage->id, ]); my_copy_page($this->user->id, MY_PAGE_PUBLIC, 'user-profile'); $this->assertNotEmpty($DB->get_records('my_pages', ['userid' => $this->user->id, 'private' => MY_PAGE_PUBLIC, 'name' => MY_PAGE_DEFAULT])); $this->assertNotEmpty($DB->get_records('block_instances', ['parentcontextid' => $usercontext->id])); $sink = $this->redirectEvents(); my_reset_page($user->id, MY_PAGE_PUBLIC, 'user-profile'); $this->assertEmpty($DB->get_records('my_pages', ['userid' => $this->user->id, 'private' => MY_PAGE_PUBLIC, 'name' => MY_PAGE_DEFAULT])); $this->assertEmpty($DB->get_records('block_instances', ['parentcontextid' => $usercontext->id])); // Trigger and capture the event. $events = $sink->get_events(); $event = reset($events); $sink->close(); $this->assertEquals(MY_PAGE_PUBLIC, $event->other['private']); $this->assertEquals('user-profile', $event->other['pagetype']); } /** * Test the dashboards reset event. * * We will reset all user dashboards to * trigger the event and ensure data is returned as expected. * * @covers ::my_reset_page_for_all_users */ public function test_dashboards_reset(): void { global $CFG, $USER, $DB; require_once($CFG->dirroot . '/my/lib.php'); $usercontext = context_user::instance($this->user->id); // Create at least one dashboard. my_copy_page($this->user->id); $this->assertNotEmpty($DB->get_records('my_pages', ['userid' => $this->user->id, 'private' => MY_PAGE_PRIVATE, 'name' => MY_PAGE_DEFAULT])); $this->assertNotEmpty($DB->get_records('block_instances', ['parentcontextid' => $usercontext->id])); // Reset all dashbaords. $sink = $this->redirectEvents(); my_reset_page_for_all_users(); // Assert that the page and all th blocks were deleted. $this->assertEmpty($DB->get_records('my_pages', ['userid' => $this->user->id, 'private' => MY_PAGE_PRIVATE, 'name' => MY_PAGE_DEFAULT])); $this->assertEmpty($DB->get_records('block_instances', ['parentcontextid' => $usercontext->id])); // Trigger and capture the event. $events = $sink->get_events(); $event = reset($events); $sink->close(); // Check that the event data is valid. $this->assertInstanceOf('\core\event\dashboards_reset', $event); $this->assertEquals($USER->id, $event->userid); $this->assertEquals(MY_PAGE_PRIVATE, $event->other['private']); $this->assertEquals('my-index', $event->other['pagetype']); $this->assertDebuggingNotCalled(); // Reset the dashboards with private parameter is set to MY_PAGE_PUBLIC and pagetype set to 'user-profile'. $systempage = $DB->get_record('my_pages', ['userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => MY_PAGE_PUBLIC]); $this->getDataGenerator()->create_block('online_users', [ 'parentcontextid' => context_system::instance()->id, 'pagetypepattern' => 'user-profile', 'subpagepattern' => $systempage->id, ]); my_copy_page($this->user->id, MY_PAGE_PUBLIC, 'user-profile'); $this->assertNotEmpty($DB->get_records('my_pages', ['userid' => $this->user->id, 'private' => MY_PAGE_PUBLIC, 'name' => MY_PAGE_DEFAULT])); $this->assertNotEmpty($DB->get_records('block_instances', ['parentcontextid' => $usercontext->id])); $sink = $this->redirectEvents(); my_reset_page_for_all_users(MY_PAGE_PUBLIC, 'user-profile'); $this->assertEmpty($DB->get_records('my_pages', ['userid' => $this->user->id, 'private' => MY_PAGE_PUBLIC, 'name' => MY_PAGE_DEFAULT])); $this->assertEmpty($DB->get_records('block_instances', ['parentcontextid' => $usercontext->id])); // Trigger and capture the event. $events = $sink->get_events(); $event = reset($events); $sink->close(); $this->assertEquals(MY_PAGE_PUBLIC, $event->other['private']); $this->assertEquals('user-profile', $event->other['pagetype']); } } tests/behat/my_courses.feature000064400000032535152265255660012564 0ustar00@core @core_my @block_myoverview Feature: Run tests over my courses page Background: Given the following "users" exist: | username | firstname | lastname | email | | user1 | User | 1 | user1@example.com | And the following "categories" exist: | name | category | idnumber | | CatA | 0 | cata | And the following "roles" exist: | shortname | name | archetype | | role1 | Role 1 | | And the following "system role assigns" exist: | user | role | contextlevel | reference | | user1 | role1 | Category | CatA | 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 "Create course" "button" should not exist in the "page-header" "region" And "Manage courses" "button" should not exist in the "page-header" "region" When I click on "Create course" "button" in the "page-content" "region" Then I should see "Add a new course" And I am on the "My courses" page And I click on "Manage course categories" "button" in the "page-content" "region" And I should see "Manage course categories and courses" # Check that the expected buttons are displayed in the header when the user is enrolled in a course. But the following "course" exists: | fullname | Course 1 | | shortname | C1 | | format | topics | And the following "course enrolment" exists: | user | admin | | course | C1 | | role | student | And I am on the "My courses" page And "Create course" "button" should exist in the "page-header" "region" And "Manage courses" "button" should exist in the "page-header" "region" And "Create course" "button" should not exist in the "page-content" "region" And "Manage courses" "button" should not exist in the "page-content" "region" And "Manage course categories" "button" should not exist in the "page-content" "region" Scenario: User without creating a course and managing category permissions cannot see any link When I am on the "My courses" page logged in as "user1" Then "Create course" "button" should not exist And "Manage courses" "button" should not exist And "Manage course categories" "button" should not exist # Check that the same buttons are displayed in the header when the user is enrolled in a course. But the following "course" exists: | fullname | Course 1 | | shortname | C1 | | format | topics | And the following "course enrolment" exists: | user | user1 | | course | C1 | | role | student | And I am on the "My courses" page And "Create course" "button" should not exist And "Manage courses" "button" should not exist And "Manage course categories" "button" should not exist Scenario: User without capability to browse courses cannot see any link Given the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/category:viewcourselist | Prevent | user | System | | When I am on the "My courses" page logged in as "user1" Then "Create course" "button" should not exist And "Manage courses" "button" should not exist And "Manage course categories" "button" should not exist # Check that the same buttons are displayed in the header when the user is enrolled in a course. But the following "course" exists: | fullname | Course 1 | | shortname | C1 | | format | topics | And the following "course enrolment" exists: | user | user1 | | course | C1 | | role | student | And I am on the "My courses" page And "Create course" "button" should not exist And "Manage courses" "button" should not exist And "Manage course categories" "button" should not exist @javascript Scenario: User with creating a course permission can see the Create course link only Given the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/course:create | Allow | role1 | Category | cata | When I am on the "My courses" page logged in as "user1" Then "Create course" "button" should exist in the "page-content" "region" But "Manage course categories" "button" should not exist And "Create course" "button" should not exist in the "page-header" "region" And I click on "Create course" "button" And I should see "Add a new course" And "CatA" "autocomplete_selection" should exist # Check that the same buttons are displayed in the header when the user is enrolled in a course. But the following "course" exists: | fullname | Course 1 | | shortname | C1 | | format | topics | And the following "course enrolment" exists: | user | user1 | | course | C1 | | role | student | And I am on the "My courses" page And "Create course" "button" should exist in the "page-header" "region" And "Manage courses" "button" should not exist And "Create course" "button" should not exist in the "page-content" "region" Scenario: User with managing a category permission can see the Manage course link only Given the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/category:manage | Allow | role1 | Category | cata | When I am on the "My courses" page logged in as "user1" Then "Manage course categories" "button" should exist in the "page-content" "region" And "Create course" "button" should not exist And I click on "Manage course categories" "button" in the "page-content" "region" And I should see "Manage course categories and courses" # Check that the same buttons are displayed in the header when the user is enrolled in a course. But the following "course" exists: | fullname | Course 1 | | shortname | C1 | | format | topics | And the following "course enrolment" exists: | user | user1 | | course | C1 | | role | student | And I am on the "My courses" page And "Manage courses" "button" should exist in the "page-header" "region" And "Create course" "button" should not exist And "Manage courses" "button" should not exist in the "page-content" "region" @javascript Scenario: User with both creating a course and managing a category permission can see both links Given the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/course:create | Allow | role1 | Category | cata | | moodle/category:manage | Allow | role1 | Category | cata | When I am on the "My courses" page logged in as "user1" Then "Create course" "button" should exist in the "page-content" "region" And "Manage course categories" "button" should exist in the "page-content" "region" And "Create course" "button" should not exist in the "page-header" "region" And "Manage courses" "button" should not exist in the "page-header" "region" And I click on "Create course" "button" And I should see "Add a new course" And "CatA" "autocomplete_selection" should exist And I am on the "My courses" page And I click on "Manage course categories" "button" And I should see "Manage course categories and courses" # Check that the same buttons are displayed in the header when the user is enrolled in a course. But the following "course" exists: | fullname | Course 1 | | shortname | C1 | | format | topics | And the following "course enrolment" exists: | user | user1 | | course | C1 | | role | student | And I am on the "My courses" page And "Create course" "button" should exist in the "page-header" "region" And "Manage courses" "button" should exist in the "page-header" "region" And "Create course" "button" should not exist in the "page-content" "region" And "Manage courses" "button" should not exist in the "page-content" "region" @javascript Scenario: Admin can see relevant blocks but not add or move them Given I log in as "admin" And I am on site homepage And I turn editing mode on And I add the "Text" block to the default region with: | Text block title | Text on all pages | | Content | This is visible on all pages | And I configure the "Text on all pages" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | | Default region | Right | And I click on "Save changes" "button" in the "Configure Text on all pages block" "dialogue" And I should see "This is visible on all pages" And "Move Text on all pages block" "menuitem" should exist in the "Text on all pages" "block" When I am on the "My courses" page # Check blocks visible but are "locked" in place. Then "Course overview" "text" should exist in the "region-main" "region" And I should not see "Add a block" And I should see "This is visible on all pages" And "Move Text on all pages block" "menuitem" should not exist in the "Text on all pages" "block" And "Move Course overview block" "menuitem" should not exist in the "Course overview" "block" And "Actions menu" "icon" in the "Course overview" "block" should not be visible Scenario: User with creating a course permission can't see the Request course link Given the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/course:request | Allow | user | System | | When I am on the "My courses" page logged in as "admin" Then "Create course" "button" should exist in the "page-content" "region" And "Request a course" "button" should not exist And "Create course" "button" should not exist in the "page-header" "region" # Check that the same buttons are displayed in the header when the user is enrolled in a course. But the following "course" exists: | fullname | Course 1 | | shortname | C1 | | format | topics | And the following "course enrolment" exists: | user | admin | | course | C1 | | role | student | And I am on the "My courses" page And "Create course" "button" should exist in the "page-header" "region" And "Request a course" "button" should not exist And "Create course" "button" should not exist in the "page-content" "region" Scenario: User without creating a course but with course request permission could see the Request course link Given the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/course:request | Allow | user | System | | When I am on the "My courses" page logged in as "user1" Then "Request a course" "button" should exist in the "page-content" "region" And "Create course" "button" should not exist in the "page-content" "region" And "Create course" "button" should not exist in the "page-header" "region" And "Request a course" "button" should not exist in the "page-header" "region" # Check the request a course button is not displayed when this feature is disabled. And the following config values are set as admin: | enablecourserequests | 0 | And I am on the "My courses" page logged in as "user1" And "Request a course" "button" should not exist # Check that the same buttons are displayed in the header when the user is enrolled in a course. But the following "course" exists: | fullname | Course 1 | | shortname | C1 | | format | topics | And the following "course enrolment" exists: | user | user1 | | course | C1 | | role | student | And the following config values are set as admin: | enablecourserequests | 1 | And I am on the "My courses" page And "Request a course" "button" should exist in the "page-header" "region" And "Create course" "button" should not exist And "Request a course" "button" should not exist in the "page-content" "region" Scenario: User without creating nor course request permission shouldn't see any Request course link Given I am on the "My courses" page logged in as "user1" Then "Request a course" "button" should not exist in the "page-content" "region" And "Create course" "button" should not exist in the "page-content" "region" And "Manage courses" "button" should not exist in the "page-content" "region" # Check that the same buttons are displayed in the header when the user is enrolled in a course. But the following "course" exists: | fullname | Course 1 | | shortname | C1 | | format | topics | And the following "course enrolment" exists: | user | user1 | | course | C1 | | role | student | And I am on the "My courses" page And "Create course" "button" should not exist And "Request a course" "button" should not exist And "Manage courses" "button" should not exist tests/behat/reset_all_pages.feature000064400000007666152265255660013534 0ustar00@core @core_my Feature: Reset all personalised pages to default In order to reset everyone's personalised pages As an admin I need to press a button on the pages to customise the default pages Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | | student3 | Student | 3 | student3@example.com | And the following "role capability" exists: | role | user | | moodle/block:edit | allow | | block/myprofile:addinstance | allow | And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | comments | User | student1 | my-index | side-pre | | myprofile | User | student2 | user-profile | side-pre | And I log in as "student1" And I should see "Comments" And I log out And I log in as "student2" And I follow "Profile" in the user menu And I should see "Logged in user" And I log out And I log in as "student3" And I should not see "Comments" And I follow "Profile" in the user menu And I should not see "Logged in user" And I log out Scenario: Reset Dashboard for all users Given I log in as "admin" And I navigate to "Appearance > Default Dashboard page" in site administration And I turn editing mode on And I add the "Latest announcements" block And I open the "Timeline" blocks action menu And I follow "Delete Timeline block" And I press "Yes" And I turn editing mode off And I log out And I log in as "student1" And I should not see "Latest announcements" And I should see "Timeline" And I log out And I log in as "student3" And I should not see "Latest announcements" And I should see "Timeline" And I log out And I log in as "admin" And I navigate to "Appearance > Default Dashboard page" in site administration When I press "Reset Dashboard for all users" And I should see "All Dashboard pages have been reset to default." And I log out And I log in as "student1" Then I should see "Latest announcements" And I should not see "Comments" And I should not see "Timeline" And I log out And I log in as "student3" And I should see "Latest announcements" And I should not see "Timeline" And I log out # Check that this did not affect the customised profiles. And I am on the "student2" "user > profile" page logged in as student2 And I should see "Logged in user" And I should not see "Latest announcements" Scenario: Reset profile for all users Given I log in as "admin" And I navigate to "Appearance > Default profile page" in site administration And I turn editing mode on And I add the "Latest announcements" block And I log out And I am on the "student2" "user > profile" page logged in as student2 And I should not see "Latest announcements" And I log out And I am on the "student3" "user > profile" page logged in as student3 And I should not see "Latest announcements" And I log out And I log in as "admin" And I navigate to "Appearance > Default profile page" in site administration When I press "Reset profile for all users" And I should see "All profile pages have been reset to default." And I log out And I am on the "student2" "user > profile" page logged in as student2 Then I should see "Latest announcements" And I should not see "Logged in user" And I log out And I am on the "student3" "user > profile" page logged in as student3 And I should see "Latest announcements" And I log out # Check that this did not affect the customised dashboards. And I log in as "student1" And I should see "Comments" And I should not see "Latest announcements" tests/behat/preferences_navigation.feature000064400000011711152265255660015105 0ustar00@core @core_my @javascript Feature: Navigate and use preferences page In order to navigate through preferences page As a user I need to be able to use preferences page Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Sam | Student | s1@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | And I log in as "admin" Scenario Outline: Navigating through user menu Preferences When I follow "Preferences" in the user menu # Click each link in the 'Preferences' page. And I click on "" "link" in the "#page-content" "css_element" # Confirm that each redirected page has 'Preferences' in the breadcrumbs. And "Users" "link" should not exist in the ".breadcrumb" "css_element" Then "Preferences" "link" should exist in the ".breadcrumb" "css_element" # Additional confirmation that breadcrumbs is correct. And "" "text" should exist in the ".breadcrumb" "css_element" # Confirm that user name and profile picture are displayed in header section. And I should see "Admin User" in the ".page-header-headings" "css_element" And ".page-header-image" "css_element" should exist in the "#page-header" "css_element" Examples: | userprefpage | | Edit profile | | Change password | | Preferred language | | Forum preferences | | Editor preferences | | Calendar preferences | | Content bank preferences | | Message preferences | | Notification preferences | | Manage badges | | Badge preferences | | Backpack settings | | This user's role assignments | | Permissions | | Check permissions | | Blog preferences | | External blogs | | Register an external blog | Scenario Outline: Navigating through course participant preferences Given I am on "Course 1" course homepage And I navigate to course participants And I follow "Sam Student" When I click on "Preferences" "link" in the "#region-main-box" "css_element" Then I should see "Sam Student" in the ".page-header-headings" "css_element" And ".page-header-image" "css_element" should exist in the "#page-header" "css_element" # Click each link in the 'Preferences' page. And I click on "" "link" in the "#page-content" "css_element" # Confirm that each redirected page has 'Users/{user}/Preferences' in the breadcrumbs. Then "Users" "link" should exist in the ".breadcrumb" "css_element" And "Sam Student" "link" should exist in the ".breadcrumb" "css_element" And "Preferences" "link" should exist in the ".breadcrumb" "css_element" # Additional confirmation that breadcrumbs is correct. And "" "text" should exist in the ".breadcrumb" "css_element" # Confirm that user name and profile picture are displayed in header section. And I should see "Sam Student" in the ".page-header-headings" "css_element" And ".page-header-image" "css_element" should exist in the "#page-header" "css_element" Examples: | courseprefpage | | Edit profile | | Preferred language | | Forum preferences | | Editor preferences | | Calendar preferences | | Content bank preferences | | Message preferences | | Notification preferences | | This user's role assignments | | Permissions | | Check permissions | Scenario: Navigation with Event monitoring enabled Given I navigate to "Reports > Event monitoring rules" in site administration And I click on "Enable" "link" And I press "Add a new rule" And I set the following fields to these values: | Rule name | Testing1 | | Area to monitor | Subsystem (core) | | Event | Allow role override | And I press "Save changes" When I follow "Preferences" in the user menu # Confirm that Event monitoring is visible and clickable. Then I should see "Miscellaneous" And I follow "Event monitoring" # Confirm that user can subscribe to new rule. And "Subscribe to rule \"Testing1\"" "link" should exist And I am on "Course 1" course homepage And I navigate to course participants And I follow "Sam Student" And I click on "Preferences" "link" in the "#region-main-box" "css_element" # Confirm that admin cannot change student's event monitor subscription. And I should not see "Event monitoring" tests/behat/add_blocks.feature000064400000002035152265255660012451 0ustar00@core @core_my Feature: Add blocks to dashboard page In order to add more functionality to dashboard page As a user I need to add blocks to dashboard page Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student2 | C1 | student | And I log in as "student1" Scenario: Add blocks to page When I turn editing mode on And I add the "Latest announcements" block And I turn editing mode off Then I should see "Latest announcements" in the "Latest announcements" "block" And I should see "Timeline" in the "Timeline" "block" And I should see "Calendar" in the "Calendar" "block" And I should not see "Upcoming events" tests/behat/restrict_available_blocks.feature000064400000003163152265255660015563 0ustar00@core @core_my Feature: Restrict which blocks can be added to Dashboard In order to restrict which blocks can be added As a student I need to ensure I can add the blocks As an admin I need to remove the capability to add a blocks As a student I need to ensure I can't add the blocks any more Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | Scenario: The comments block can be added to Dashboard by default And I log in as "student1" And I turn editing mode on Then the add block selector should contain "Comments" block And the add block selector should contain "Text" block And the add block selector should contain "Tags" block Scenario: Remove the ability to add the comments block to Dashboard Given the following "role capability" exists: | role | user | | block/comments:myaddinstance | prohibit | | block/course_list:myaddinstance | prohibit | | block/html:myaddinstance | prohibit | When I log in as "student1" And I turn editing mode on Then the add block selector should not contain "Comments" block And the add block selector should not contain "Courses" block And the add block selector should not contain "Text" block And the add block selector should contain "Tags" block tests/behat/welcome.feature000064400000003645152265255660012027 0ustar00@core @core_my Feature: Welcome message In order to welcome new or existing user As a user I will see welcome message when I log into moodle Scenario: Log in and being redirected to course page Given the following "users" exist: | username | password | firstname | lastname | email | | wf | test | Fei | Wang | fei@example.com | And the following "courses" exist: | fullname | shortname | | Math 101 | M1O1 | When I am on "Math 101" course homepage And I should see "You are not logged in" in the "page-footer" "region" And I set the field "Username" to "wf" And I set the field "Password" to "test" And I press "Log in" And I should see "Math 101" in the "page-header" "region" And I should not see "Welcome, Fei!" in the "page-header" "region" And I follow "Dashboard" Then I should see "Welcome, Fei!" in the "page-header" "region" @javascript Scenario: Log in and being redirected to default home page When I log in as "admin" And I should see "You are logged in as Admin User" in the "page-footer" "region" And I should see "Welcome, Admin!" in the "page-header" "region" And I log out And I should see "You are not logged in" in the "page-footer" "region" And I log in as "admin" Then I should see "Hi, Admin!" in the "page-header" "region" @accessibility @javascript Scenario Outline: The start page must meet accessibility standards when the welcome message is displayed Given the following config values are set as admin: | defaulthomepage | | When I log in as "admin" Then I should see "Welcome, Admin!" in the "page-header" "region" And the page should meet accessibility standards Examples: | defaulthomepage | # Home. | 0 | # Dashboard. | 1 | # My courses. | 2 | tests/behat/reset_page.feature000064400000002115152265255660012501 0ustar00@core @core_my Feature: Reset dashboard page to default In order to remove customisations from dashboard page As a user I need to reset dashboard page Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student2 | C1 | student | And I log in as "student1" Scenario: Add blocks to page and reset When I turn editing mode on And I add the "Latest announcements" block And I add the "Comments" block And I press "Reset page to default" Then I should not see "Latest announcements" And I should see "Timeline" And I should see "Calendar" And I should not see "Upcoming events" And I should not see "Comments" And I should not see "Reset page to default"