ÿØÿà 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-Ї Ç styles.css000064400000000173152266460710006613 0ustar00.block_recent_activity .unlist li { margin-bottom: 1em; } .block_recent_activity li .head .date { float: right; } renderer.php000064400000012073152266460710007077 0ustar00. /** * Renderer for block recent_activity * * @package block_recent_activity * @copyright 2012 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; /** * recent_activity block rendrer * * @package block_recent_activity * @copyright 2012 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_recent_activity_renderer extends plugin_renderer_base { /** * Renders HTML to display recent_activity block * * @param stdClass $course * @param int $timestart * @param array $recentenrolments array of changes in enrolments * @param array $structuralchanges array of changes in course structure * @param array $modulesrecentactivity array of changes in modules (provided by modules) * @return string */ public function recent_activity($course, $timestart, $recentenrolments, $structuralchanges, $modulesrecentactivity) { $output = html_writer::tag('div', get_string('activitysince', '', userdate($timestart)), array('class' => 'activityhead')); $output .= html_writer::tag('div', html_writer::link(new moodle_url('/course/recent.php', array('id' => $course->id)), get_string('recentactivityreport')), array('class' => 'activityhead mb-3')); $content = false; // Firstly, have there been any new enrolments? if ($recentenrolments) { $content = true; $context = context_course::instance($course->id); $viewfullnames = has_capability('moodle/site:viewfullnames', $context); $output .= html_writer::start_tag('div', array('class' => 'newusers')); $output .= $this->heading(get_string("newusers").':', 3); //Accessibility: new users now appear in an
    list. $output .= html_writer::start_tag('ol', array('class' => 'list')); foreach ($recentenrolments as $user) { $output .= html_writer::tag('li', html_writer::link(new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $course->id)), fullname($user, $viewfullnames)), array('class' => 'name')); } $output .= html_writer::end_tag('ol'); $output .= html_writer::end_tag('div'); } // Next, have there been any modifications to the course structure? if (!empty($structuralchanges)) { $content = true; $output .= $this->heading(get_string("courseupdates") . ':', 6); foreach ($structuralchanges as $changeinfo => $change) { $output .= $this->structural_change($change); } } // Now display new things from each module foreach ($modulesrecentactivity as $modname => $moduleactivity) { $content = true; $output .= $moduleactivity; } if (! $content) { $output .= html_writer::tag('p', get_string('nothingnew'), array('class' => 'message')); } return $output; } /** * Renders HTML for one change in course structure * * @see block_recent_activity::get_structural_changes() * @param array $change array containing attributes * 'action' - one of: 'add mod', 'update mod', 'delete mod' * 'module' - instance of cm_info (for 'delete mod' it is an object with attributes modname and modfullname) * @return string */ protected function structural_change($change) { $cm = $change['module']; switch ($change['action']) { case 'delete mod': $text = get_string('deletedactivity', 'moodle', $cm->modfullname); break; case 'add mod': $text = get_string('added', 'moodle', $cm->modfullname). '
    '. html_writer::link($cm->url, format_string($cm->name, true)); break; case 'update mod': $text = get_string('updated', 'moodle', $cm->modfullname). '
    '. html_writer::link($cm->url, format_string($cm->name, true)); break; default: return ''; } return html_writer::tag('p', $text, array('class' => 'activity')); } } db/tasks.php000064400000002336152266460710007004 0ustar00. /** * Task definition for block_recent_activity. * @author Farhan Karmali * @copyright Farhan Karmali 2018 * @package block_recent_activity * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $tasks = array( array( 'classname' => '\block_recent_activity\task\cleanup', 'blocking' => 0, 'minute' => 'R', 'hour' => 'R', 'day' => '*', 'month' => '*', 'dayofweek' => '*', 'disabled' => 0 ) ); db/access.php000064400000003277152266460710007125 0ustar00. /** * Recent activity block caps. * * @package block_recent_activity * @copyright Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'block/recent_activity:addinstance' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/site:manageblocks' ), 'block/recent_activity:viewaddupdatemodule' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'user' => CAP_ALLOW ) ), 'block/recent_activity:viewdeletemodule' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'user' => CAP_ALLOW ) ) ); db/upgrade.php000064400000004324152266460710007305 0ustar00. /** * This file keeps track of upgrades to the recent activity block * * Sometimes, changes between versions involve alterations to database structures * and other major things that may break installations. * * The upgrade function in this file will attempt to perform all the necessary * actions to upgrade your older installation to the current version. * * If there's something it cannot do itself, it will tell you what you need to do. * * The commands in here will all be database-neutral, using the methods of * database_manager class * * Please do not forget to use upgrade_set_timeout() * before any action that may take longer time to finish. * * @package block_recent_activity * @copyright 2014 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Upgrade code for the recent activity block. * * @param int $oldversion * @param object $block */ function xmldb_block_recent_activity_upgrade($oldversion, $block) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } db/install.xml000064400000002573152266460710007341 0ustar00
    db/events.php000064400000003315152266460710007161 0ustar00. /** * Event observer. * * @package block_recent_activity * @category event * @copyright 2014 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $observers = array ( array ( 'eventname' => '\core\event\course_module_created', 'callback' => 'block_recent_activity_observer::store', 'internal' => false, // This means that we get events only after transaction commit. 'priority' => 1000, ), array ( 'eventname' => '\core\event\course_module_updated', 'callback' => 'block_recent_activity_observer::store', 'internal' => false, // This means that we get events only after transaction commit. 'priority' => 1000, ), array ( 'eventname' => '\core\event\course_module_deleted', 'callback' => 'block_recent_activity_observer::store', 'internal' => false, // This means that we get events only after transaction commit. 'priority' => 1000, ), ); lang/en/block_recent_activity.php000064400000004123152266460710013157 0ustar00. /** * Strings for component 'block_recent_activity', language 'en', branch 'MOODLE_20_STABLE' * * @package block_recent_activity * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['cleanuptask'] = 'Cleanup task for recent activity block'; $string['pluginname'] = 'Recent activity'; $string['privacy:metadata'] = 'The recent activity block contains a cache of data stored elsewhere in Moodle.'; $string['privacy:metadata:block_recent_activity'] = 'Temporary log of recent teacher activity. Removed after two days'; $string['privacy:metadata:block_recent_activity:action'] = 'Action: created, updated or deleted'; $string['privacy:metadata:block_recent_activity:cmid'] = 'Course activity ID'; $string['privacy:metadata:block_recent_activity:courseid'] = 'Course ID'; $string['privacy:metadata:block_recent_activity:modname'] = 'Module type name (for delete action)'; $string['privacy:metadata:block_recent_activity:timecreated'] = 'Time when action was performed'; $string['privacy:metadata:block_recent_activity:userid'] = 'User performing the action'; $string['recent_activity:addinstance'] = 'Add a new recent activity block'; $string['recent_activity:viewaddupdatemodule'] = 'View added and updated modules in recent activity block'; $string['recent_activity:viewdeletemodule'] = 'View deleted modules in recent activity block'; version.php000064400000002253152266460710006755 0ustar00. /** * Version details * * @package block_recent_activity * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'block_recent_activity'; // Full name of the plugin (used for diagnostics) classes/privacy/provider.php000064400000010615152266460710012235 0ustar00. /** * Privacy subsystem implementation for block_recent_activity. * * @package block_recent_activity * @category privacy * @copyright 2018 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recent_activity\privacy; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\contextlist; use core_privacy\local\request\userlist; use core_privacy\local\request\approved_userlist; defined('MOODLE_INTERNAL') || die(); /** * The block_recent_activity does not keep any data for more than COURSE_MAX_RECENT_PERIOD. * * @copyright 2018 Shamim Rezaie * @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\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns metadata. * * @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 { // This plugin defines a db table but it is not considered personal data and, therefore, not exported or deleted. $collection->add_database_table('block_recent_activity', [ 'courseid' => 'privacy:metadata:block_recent_activity:courseid', 'cmid' => 'privacy:metadata:block_recent_activity:cmid', 'timecreated' => 'privacy:metadata:block_recent_activity:timecreated', 'userid' => 'privacy:metadata:block_recent_activity:userid', 'action' => 'privacy:metadata:block_recent_activity:action', 'modname' => 'privacy:metadata:block_recent_activity:modname' ], 'privacy:metadata:block_recent_activity'); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { return new contextlist(); } /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { } /** * Delete all data for all users in the specified context. * * @param \context $context The specific context to delete data for. */ public static function delete_data_for_all_users_in_context(\context $context) { } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { } } classes/observer.php000064400000005052152266460710010554 0ustar00. /** * Event observer. * * @package block_recent_activity * @copyright 2014 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Event observer. * Stores all actions about modules create/update/delete in plugin own's table. * This allows the block to avoid expensive queries to the log table. * * @package block_recent_activity * @copyright 2014 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_recent_activity_observer { /** @var int indicates that course module was created */ const CM_CREATED = 0; /** @var int indicates that course module was udpated */ const CM_UPDATED = 1; /** @var int indicates that course module was deleted */ const CM_DELETED = 2; /** * Store all actions about modules create/update/delete in own table. * * @param \core\event\base $event */ public static function store(\core\event\base $event) { global $DB; $eventdata = new \stdClass(); switch ($event->eventname) { case '\core\event\course_module_created': $eventdata->action = self::CM_CREATED; break; case '\core\event\course_module_updated': $eventdata->action = self::CM_UPDATED; break; case '\core\event\course_module_deleted': $eventdata->action = self::CM_DELETED; $eventdata->modname = $event->other['modulename']; break; default: return; } $eventdata->timecreated = $event->timecreated; $eventdata->courseid = $event->courseid; $eventdata->cmid = $event->objectid; $eventdata->userid = $event->userid; $DB->insert_record('block_recent_activity', $eventdata); } } classes/task/cleanup.php000064400000003603152266460710011316 0ustar00. /** * Task for updating RSS feeds for rss client block * * @package block_recent_activity * @author Farhan Karmali * @copyright Farhan Karmali 2018 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_recent_activity\task; defined('MOODLE_INTERNAL') || die(); /** * Task for updating RSS feeds for rss client block * * @package block_recent_activity * @author Farhan Karmali * @copyright Farhan Karmali 2018 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class cleanup extends \core\task\scheduled_task { /** * Name for this task. * * @return string */ public function get_name() { return get_string('cleanuptask', 'block_recent_activity'); } /** * Remove old entries from table block_recent_activity */ public function execute() { global $CFG, $DB; require_once("{$CFG->dirroot}/course/lib.php"); // Those entries will never be displayed as RECENT anyway. $DB->delete_records_select('block_recent_activity', 'timecreated < ?', [ time() - COURSE_MAX_RECENT_PERIOD, ]); } } tests/behat/structural_changes.feature000064400000024526152266460710014270 0ustar00@block @block_recent_activity Feature: View structural changes in recent activity block In order to know when activities were changed As a user In need to see the structural changes in recent activity block Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Terry1 | Teacher1 | teacher1@example.com | | assistant1 | Terry2 | Teacher2 | teacher2@example.com | | student1 | Sam1 | Student1 | student1@example.com | | student2 | Sam2 | Student2 | student2@example.com | | student3 | Sam3 | Student3 | student3@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | assistant1 | C1 | teacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | And the following "groups" exist: | name | course | idnumber | | Group 1 | C1 | G1 | | Group 2 | C1 | G2 | And the following "groupings" exist: | name | course | idnumber | | Grouping 1 | C1 | GG1 | | Grouping 2 | C1 | GG2 | | Grouping 3 | C1 | GG3 | And the following "group members" exist: | user | group | | student1 | G1 | | student2 | G2 | | student3 | G1 | | student3 | G2 | | assistant1 | G1 | And the following "grouping groups" exist: | grouping | group | | GG1 | G1 | | GG2 | G2 | | GG3 | G1 | | GG3 | G2 | Scenario: Check that Added module information is displayed respecting view capability Given the following "activities" exist: | activity | course | name | idnumber | groupmode | grouping | visible | | forum | C1 | ForumVisibleGroups | forum1 | 2 | | 1 | | forum | C1 | ForumSeparateGroups | forum2 | 1 | | 1 | | forum | C1 | ForumHidden | forum3 | 1 | | 0 | | forum | C1 | ForumNoGroups | forum4 | 0 | | 1 | | forum | C1 | ForumVisibleGroupsG1 | forum5 | 2 | GG1 | 1 | | forum | C1 | ForumSeparateGroupsG1 | forum6 | 1 | GG1 | 1 | | forum | C1 | ForumVisibleGroupsG2 | forum7 | 2 | GG2 | 1 | | forum | C1 | ForumSeparateGroupsG2 | forum8 | 1 | GG2 | 1 | And I am on the "ForumVisibleGroupsG1" "forum activity editing" page logged in as teacher1 And I set the following fields to these values: | Access restrictions | Grouping: Grouping 1 | And I press "Save and return to course" And I am on the "ForumSeparateGroupsG1" "forum activity editing" page And I set the following fields to these values: | Access restrictions | Grouping: Grouping 1 | And I press "Save and return to course" And I am on the "ForumVisibleGroupsG2" "forum activity editing" page And I set the following fields to these values: | Access restrictions | Grouping: Grouping 2 | And I press "Save and return to course" And I am on the "ForumSeparateGroupsG2" "forum activity editing" page And I set the following fields to these values: | Access restrictions | Grouping: Grouping 2 | And I press "Save and return to course" And I am on "Course 1" course homepage with editing mode on When I add the "Recent activity" block Then I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" And I am on the "Course 1" course page logged in as student1 And I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should not see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should not see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should not see "ForumSeparateGroupsG2" in the "Recent activity" "block" And I am on the "Course 1" course page logged in as student2 And I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should not see "ForumHidden" in the "Recent activity" "block" And I should not see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should not see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" And I am on the "Course 1" course page logged in as student3 And I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should not see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" # Teachers have capability to see all groups and hidden activities And I am on the "Course 1" course page logged in as assistant1 And I should see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" Scenario: Updates and deletes in recent activity block Given the following "activity" exists: | activity | forum | | course | C1 | | idnumber | forum1 | | name | ForumNew | When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Recent activity" block Then I should see "Added Forum" in the "Recent activity" "block" And I should see "ForumNew" in the "Recent activity" "block" And I log out # Update forum as a teacher after a second to ensure we have a new timestamp for recent activity. And I wait "1" seconds And I am on the "Course 1" course page logged in as student1 And I should see "Added Forum" in the "Recent activity" "block" And I should see "ForumNew" in the "Recent activity" "block" And I log out # Update forum as a teacher after a second to ensure we have a new timestamp for recent activity. And I wait "1" seconds # Update forum as a teacher And I am on the "ForumNew" "forum activity editing" page logged in as teacher1 And I set the following fields to these values: | name | ForumUpdated | And I press "Save and return to course" And I log out And I wait "1" seconds # Student 1 already saw that forum was created, now he can see that forum was updated And I am on the "Course 1" course page logged in as student1 And I should not see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should see "Updated Forum" in the "Recent activity" "block" And I should see "ForumUpdated" in the "Recent activity" "block" And I log out And I wait "1" seconds # Student 2 has bigger interval and he can see one entry that forum was created but with the new name And I am on the "Course 1" course page logged in as student2 And I should see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should not see "Updated Forum" in the "Recent activity" "block" And I should see "ForumUpdated" in the "Recent activity" "block" And I log out And I wait "1" seconds # Delete forum as a teacher And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I delete "ForumUpdated" activity And I run all adhoc tasks And I log out And I wait "1" seconds # Students 1 and 2 see that forum was deleted And I am on the "Course 1" course page logged in as student1 And I should not see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should not see "Updated Forum" in the "Recent activity" "block" And I should not see "ForumUpdated" in the "Recent activity" "block" And I should see "Deleted Forum" in the "Recent activity" "block" And I log out And I wait "1" seconds # Student 3 never knew that forum was created, so he does not see anything And I am on the "Course 1" course page logged in as student3 And I should not see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should not see "Updated Forum" in the "Recent activity" "block" And I should not see "ForumUpdated" in the "Recent activity" "block" And I should not see "Deleted Forum" in the "Recent activity" "block" block_recent_activity.php000064400000027220152266460710011637 0ustar00. /** * class block_recent_activity * * @package block_recent_activity * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once($CFG->dirroot.'/course/lib.php'); /** * class block_recent_activity * * @package block_recent_activity * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_recent_activity extends block_base { /** * Use {@link block_recent_activity::get_timestart()} to access * * @var int stores the time since when we want to show recent activity */ protected $timestart = null; /** * Initialises the block */ function init() { $this->title = get_string('pluginname', 'block_recent_activity'); } /** * Returns the content object * * @return stdObject */ function get_content() { if ($this->content !== NULL) { return $this->content; } if (empty($this->instance)) { $this->content = ''; return $this->content; } $this->content = new stdClass; $this->content->text = ''; $this->content->footer = ''; $renderer = $this->page->get_renderer('block_recent_activity'); $this->content->text = $renderer->recent_activity($this->page->course, $this->get_timestart(), $this->get_recent_enrolments(), $this->get_structural_changes(), $this->get_modules_recent_activity()); return $this->content; } /** * Returns the time since when we want to show recent activity * * For guest users it is 2 days, for registered users it is the time of last access to the course * * @return int */ protected function get_timestart() { global $USER; if ($this->timestart === null) { $this->timestart = round(time() - COURSE_MAX_RECENT_PERIOD, -2); // better db caching for guests - 100 seconds if (!isguestuser()) { if (!empty($USER->lastcourseaccess[$this->page->course->id])) { if ($USER->lastcourseaccess[$this->page->course->id] > $this->timestart) { $this->timestart = $USER->lastcourseaccess[$this->page->course->id]; } } } } return $this->timestart; } /** * Returns all recent enrolments. * * This function previously used get_recent_enrolments located in lib/deprecatedlib.php which would * return an empty array which was identified in MDL-36993. The use of this function outside the * deprecated lib was removed in MDL-40649. * * @todo MDL-36993 this function always return empty array * @return array array of entries from {user} table */ protected function get_recent_enrolments() { return array(); } /** * Returns list of recent changes in course structure * * It includes adding, editing or deleting of the resources or activities * Excludes changes on modules without a view link (i.e. labels), and also * if activity was both added and deleted * * @return array array of changes. Each element is an array containing attributes: * 'action' - one of: 'add mod', 'update mod', 'delete mod' * 'module' - instance of cm_info (for 'delete mod' it is an object with attributes modname and modfullname) */ protected function get_structural_changes() { global $DB; $course = $this->page->course; $context = context_course::instance($course->id); $canviewdeleted = has_capability('block/recent_activity:viewdeletemodule', $context); $canviewupdated = has_capability('block/recent_activity:viewaddupdatemodule', $context); if (!$canviewdeleted && !$canviewupdated) { return; } $timestart = $this->get_timestart(); $changelist = array(); // The following query will retrieve the latest action for each course module in the specified course. // Also the query filters out the modules that were created and then deleted during the given interval. $sql = "SELECT cmid, MIN(action) AS minaction, MAX(action) AS maxaction, MAX(modname) AS modname FROM {block_recent_activity} WHERE timecreated > ? AND courseid = ? GROUP BY cmid ORDER BY MAX(timecreated) DESC"; $params = array($timestart, $course->id); $logs = $DB->get_records_sql($sql, $params); if (isset($logs[0])) { // If special record for this course and cmid=0 is present, migrate logs. self::migrate_logs($course); $logs = $DB->get_records_sql($sql, $params); } if ($logs) { $modinfo = get_fast_modinfo($course); foreach ($logs as $log) { // We used aggregate functions since constants CM_CREATED, CM_UPDATED and CM_DELETED have ascending order (0,1,2). $wasdeleted = ($log->maxaction == block_recent_activity_observer::CM_DELETED); $wascreated = ($log->minaction == block_recent_activity_observer::CM_CREATED); if ($wasdeleted && $wascreated) { // Activity was created and deleted within this interval. Do not show it. continue; } else if ($wasdeleted && $canviewdeleted) { if (plugin_supports('mod', $log->modname, FEATURE_NO_VIEW_LINK, false)) { // Better to call cm_info::has_view() because it can be dynamic. // But there is no instance of cm_info now. continue; } // Unfortunately we do not know if the mod was visible. $modnames = get_module_types_names(); $changelist[$log->cmid] = array('action' => 'delete mod', 'module' => (object)array( 'modname' => $log->modname, 'modfullname' => isset($modnames[$log->modname]) ? $modnames[$log->modname] : $log->modname )); } else if (!$wasdeleted && isset($modinfo->cms[$log->cmid]) && $canviewupdated) { // Module was either added or updated during this interval and it currently exists. // If module was both added and updated show only "add" action. $cm = $modinfo->cms[$log->cmid]; if ($cm->has_view() && $cm->uservisible) { $changelist[$log->cmid] = array( 'action' => $wascreated ? 'add mod' : 'update mod', 'module' => $cm ); } } } } return $changelist; } /** * Returns list of recent activity within modules * * For each used module type executes callback MODULE_print_recent_activity() * * @return array array of pairs moduletype => content */ protected function get_modules_recent_activity() { $context = context_course::instance($this->page->course->id); $viewfullnames = has_capability('moodle/site:viewfullnames', $context); $hascontent = false; $modinfo = get_fast_modinfo($this->page->course); $usedmodules = $modinfo->get_used_module_names(); $recentactivity = array(); foreach ($usedmodules as $modname => $modfullname) { // Each module gets it's own logs and prints them ob_start(); $hascontent = component_callback('mod_'. $modname, 'print_recent_activity', array($this->page->course, $viewfullnames, $this->get_timestart()), false); if ($hascontent) { $recentactivity[$modname] = ob_get_contents(); } ob_end_clean(); } return $recentactivity; } /** * Which page types this block may appear on. * * @return array page-type prefix => true/false. */ function applicable_formats() { return array('all' => true, 'my' => false, 'tag' => false); } /** * Migrates entries from table {log} into {block_recent_activity} * * We only migrate logs for the courses that actually have recent activity * block and that are being viewed within COURSE_MAX_RECENT_PERIOD time * after the upgrade. * * The presence of entry in {block_recent_activity} with the cmid=0 indicates * that the course needs log migration. Those entries were installed in * db/upgrade.php when the table block_recent_activity was created. * * @param stdClass $course */ protected static function migrate_logs($course) { global $DB; if (!$logstarted = $DB->get_record('block_recent_activity', array('courseid' => $course->id, 'cmid' => 0), 'id, timecreated')) { return; } $DB->delete_records('block_recent_activity', array('id' => $logstarted->id)); try { $logs = $DB->get_records_select('log', "time > ? AND time < ? AND course = ? AND module = 'course' AND (action = 'add mod' OR action = 'update mod' OR action = 'delete mod')", array(time()-COURSE_MAX_RECENT_PERIOD, $logstarted->timecreated, $course->id), 'id ASC', 'id, time, userid, cmid, action, info'); } catch (Exception $e) { // Probably table {log} was already removed. return; } if (!$logs) { return; } $modinfo = get_fast_modinfo($course); $entries = array(); foreach ($logs as $log) { $info = explode(' ', $log->info); if (count($info) != 2) { continue; } $modname = $info[0]; $instanceid = $info[1]; $entry = array('courseid' => $course->id, 'userid' => $log->userid, 'timecreated' => $log->time, 'modname' => $modname); if ($log->action == 'delete mod') { if (!$log->cmid) { continue; } $entry['action'] = 2; $entry['cmid'] = $log->cmid; } else { if (!isset($modinfo->instances[$modname][$instanceid])) { continue; } if ($log->action == 'add mod') { $entry['action'] = 0; } else { $entry['action'] = 1; } $entry['cmid'] = $modinfo->instances[$modname][$instanceid]->id; } $entries[] = $entry; } $DB->insert_records('block_recent_activity', $entries); } }