/** * Get course information - progress / grades, etc * * @return string */ public function get_courseinfo_action() { $courseids = optional_param('courseids', false, PARAM_SEQUENCE); if (!empty($courseids)) { $courseids = explode(',', $courseids); } $courseinfo = \theme_snap\local::courseinfo($courseids); return json_encode(array('info' => $courseinfo)); }
/** * Read page * * @throws \coding_exception * @return stdClass */ private function read_page() { global $PAGE, $COURSE; $cm = $PAGE->cm; $page = \theme_snap\local::get_page_mod($cm); $context = $PAGE->context; // Trigger module instance viewed event. $event = \mod_page\event\course_module_viewed::create(array('objectid' => $page->id, 'context' => $context)); $event->add_record_snapshot('course_modules', $cm); $event->add_record_snapshot('course', $COURSE); $event->add_record_snapshot('page', $page); $event->trigger(); return $page; }
/** * @throws coding_exception */ public function __construct() { global $PAGE, $COURSE; // Page path blacklist for admin menu. $adminblockblacklist = ['/user/profile.php']; if (in_array(local::current_url_path(), $adminblockblacklist)) { return; } // Admin users always see the admin menu with the exception of blacklisted pages. // The admin menu shows up for other users if they are a teacher in the current course. if (!is_siteadmin()) { // We don't want students to see the admin menu ever. // Editing teachers are identified as people who can manage activities and non editing teachers as those who // can view the gradebook. As editing teachers are almost certain to also be able to view the gradebook, the // grader:view capability is checked first. $caps = ['gradereport/grader:view', 'moodle/course:manageactivities']; $canmanageacts = has_any_capability($caps, $PAGE->context); $isstudent = !$canmanageacts && !is_role_switched($COURSE->id); if ($isstudent) { return; } } if (!$PAGE->blocks->is_block_present('settings')) { // Throw error if on front page or course page. // (There are pages that don't have a settings block so we shouldn't throw an error on those pages). if (strpos($PAGE->pagetype, 'course-view') === 0 || $PAGE->pagetype === 'site-index') { debugging('Settings block was not found on this page', DEBUG_DEVELOPER); } return; } // Core Moodle API appears to be missing a 'get block by name' function. // Cycle through all regions and block instances until we find settings. foreach ($PAGE->blocks->get_regions() as $region) { foreach ($PAGE->blocks->get_blocks_for_region($region) as $block) { if (isset($block->instance) && $block->instance->blockname == 'settings') { $this->instanceid = $block->instance->id; break 2; } } } if (!has_capability('moodle/block:view', \context_block::instance($this->instanceid))) { return; } $this->output = true; }
/** * CSS Processor * * @param string $css * @param theme_config $theme * @return string */ function theme_snap_process_css($css, theme_config $theme) { // Set the background image for the logo. $logo = $theme->setting_file_url('logo', 'logo'); $css = theme_snap_set_logo($css, $logo); // Set the background image for the poster. $css = \theme_snap\local::site_coverimage_css($css); // Set the custom css. if (!empty($theme->settings->customcss)) { $customcss = $theme->settings->customcss; } else { $customcss = null; } $css = theme_snap_set_customcss($css, $customcss); // Set bootswatch. $css = theme_snap_set_bootswatch($css, theme_snap_get_bootswatch_variables($theme)); return $css; }
/** * Read page * * @throws \coding_exception * @return stdClass */ private function read_page() { global $PAGE, $COURSE; $cm = $PAGE->cm; $page = \theme_snap\local::get_page_mod($cm); $context = $PAGE->context; // Trigger module instance viewed event. $event = \mod_page\event\course_module_viewed::create(array('objectid' => $page->id, 'context' => $context)); $event->add_record_snapshot('course_modules', $cm); $event->add_record_snapshot('course', $COURSE); $event->add_record_snapshot('page', $page); $event->trigger(); // Update 'viewed' state if required by completion system. $completion = new \completion_info($COURSE); $completion->set_module_viewed($cm); $renderer = $PAGE->get_renderer('core', 'course'); $page->completionhtml = $renderer->course_section_cm_completion($COURSE, $completion, $cm); return $page; }
$url = new moodle_url('/admin/settings.php', ['section' => 'themesettingsnap'], 'admin-poster'); echo $OUTPUT->cover_image_selector(); } ?> </div> <section id="region-main"> <?php echo $OUTPUT->course_content_header(); // Ensure edit blocks button is only shown for appropriate pages. $hasadminbutton = stripos($PAGE->button, '"adminedit"') || stripos($PAGE->button, '"edit"'); if ($hasadminbutton) { // List paths to black list for 'turn editting on' button here. // Note, to use regexs start and end with a pipe symbol - e.g. |^/report/| . $editbuttonblacklist = array('/comment/', '/cohort/index.php', '|^/report/|', '|^/admin/|', '|^/mod/data/|', '/tag/manage.php', '/grade/edit/scale/index.php', '/outcome/admin.php', '/mod/assign/adminmanageplugins.php', '/message/defaultoutputs.php', '/theme/index.php', '/user/editadvanced.php', '/user/profile/index.php', '/mnet/service/enrol/index.php', '/local/mrooms/view.php'); $pagepath = local::current_url_path(); foreach ($editbuttonblacklist as $blacklisted) { if ($blacklisted[0] == '|' && $blacklisted[strlen($blacklisted) - 1] == '|') { // Use regex to determine blacklisting. if (preg_match($blacklisted, $pagepath) === 1) { // This url path is blacklisted, stop button from being displayed. $PAGE->set_button(''); } } else { if ($pagepath == $blacklisted) { // This url path is blacklisted, stop button from being displayed. $PAGE->set_button(''); } } } }
/** * Populate forum id arrays. * @throws \coding_exception */ protected function populate_forums() { local::swap_global_user($this->user->id); // Note - we don't include the site in the list of courses. This is intentional - we want student engagement to // be increased in courses where learning takes place and the front page is unlikely to fit that model. // Currently we are using local::swap_global_user as a hack for the following function (MDL-51353). $this->courses = enrol_get_my_courses(); $forums = []; $hsuforums = []; foreach ($this->courses as $course) { $forums = $forums + forum_get_readable_forums($this->user->id, $course->id); if (function_exists('hsuforum_get_readable_forums')) { $hsuforums = $hsuforums + hsuforum_get_readable_forums($this->user->id, $course->id, true); } } // Remove Q&A forums from array. $forums = $this->purge_qa_forums($forums); $hsuforums = $this->purge_qa_forums($hsuforums); // Rmove forums in courses not accessed for a long time. $forums = $this->process_stale_forums($forums); $hsuforums = $this->process_stale_forums($hsuforums, true); $this->forums = $forums; $this->hsuforums = $hsuforums; $this->forumids = array_keys($forums); $this->forumidsallgroups = $this->forumids_accessallgroups($forums); $this->hsuforumids = array_keys($hsuforums); $this->hsuforumidsallgroups = $this->forumids_accessallgroups($hsuforums, 'hsuforum'); local::swap_global_user(false); }
/** * Get page module html * @param $mod * @return string */ protected function mod_page_html($mod) { if (!$mod->uservisible) { return ""; } global $DB; $sql = "SELECT * FROM {course_modules} cm\n JOIN {page} p ON p.id = cm.instance\n WHERE cm.id = ?"; $page = $DB->get_record_sql($sql, array($mod->id)); $context = context_module::instance($mod->id); $formatoptions = new stdClass(); $formatoptions->noclean = true; $formatoptions->overflowdiv = true; $formatoptions->context = $context; // Make sure we have some summary/extract text for the course page. if (!empty($page->intro)) { $page->summary = file_rewrite_pluginfile_urls($page->intro, 'pluginfile.php', $context->id, 'mod_page', 'intro', null); $page->summary = format_text($page->summary, $page->introformat, $formatoptions); } else { $preview = html_to_text($page->content, 0, false); $page->summary = shorten_text($preview, 200); } $content = file_rewrite_pluginfile_urls($page->content, 'pluginfile.php', $context->id, 'mod_page', 'content', $page->revision); $content = format_text($content, $page->contentformat, $formatoptions); $imgarr = \theme_snap\local::extract_first_image($content); $thumbnail = ''; if ($imgarr) { $img = html_writer::img($imgarr['src'], $imgarr['alt']); $thumbnail = "<div class=summary-figure>{$img}</div>"; } $readmore = get_string('readmore', 'theme_snap'); $close = get_string('close', 'theme_snap'); $o = "\n {$thumbnail}\n <div class='summary-text'>\n {$page->summary}\n <p><a class='pagemod-readmore' href='{$mod->url}'>{$readmore}</a></p>\n </div>\n\n <div class=pagemod-content tabindex='-1'>\n {$content}\n <div><hr><a class='snap-action-icon' href='#'>\n <i class='icon icon-office-52'></i><small>{$close}</small></a></div>\n </div>"; return $o; }
/** * Process site cover image. * * @throws Exception * @throws coding_exception * @throws dml_exception */ function theme_snap_process_site_coverimage() { $context = \context_system::instance(); \theme_snap\local::process_coverimage($context); theme_reset_all_caches(); }
/** * Get page module html * @param cm_info $mod * @return string */ protected function mod_page_html(cm_info $mod) { if (!$mod->uservisible) { return ""; } $page = \theme_snap\local::get_page_mod($mod); $imgarr = \theme_snap\local::extract_first_image($page->content); $thumbnail = ''; if ($imgarr) { $img = html_writer::img($imgarr['src'], $imgarr['alt']); $thumbnail = "<div class=summary-figure>{$img}</div>"; } $readmore = get_string('readmore', 'theme_snap'); $close = get_string('close', 'theme_snap'); // Identify content elements which should force an AJAX lazy load. $elcontentblist = ['iframe', 'video', 'object', 'embed']; $content = $page->content; $lazyload = false; foreach ($elcontentblist as $el) { if (stripos($content, '<' . $el) !== false) { $content = ''; // Don't include the content as it is likely to slow the page load down considerably. $lazyload = true; } } $contentloaded = !$lazyload ? 1 : 0; $o = "\n {$thumbnail}\n <div class='summary-text'>\n {$page->summary}\n <p><a class='pagemod-readmore' href='{$mod->url}' data-pagemodcontext='{$mod->context->id}'>{$readmore}</a></p>\n </div>\n\n <div class=pagemod-content tabindex='-1' data-content-loaded={$contentloaded}>\n {$content}\n <div><hr><a class='snap-action-icon' href='#'>\n <i class='icon icon-office-52'></i><small>{$close}</small></a></div>\n </div>"; return $o; }
public function test_courseinfo_teacher() { $this->setUser($this->teachers[0]); $actual = local::courseinfo([$this->course->id]); $this->assertCount(1, $actual); }
/** * Test that the summary, when generated from the intro text, does not strip out images or trim the text in anyway. * @throws \coding_exception * @throws \moodle_exception */ public function test_get_page_mod_intro_summary() { $this->resetAfterTest(); $generator = $this->getDataGenerator(); $course = $generator->create_course(); $pagegen = $generator->get_plugin_generator('mod_page'); $page = $pagegen->create_instance(['course' => $course->id, 'intro' => '<img src="http://fakeurl.local/testimg.png" alt="some alt text" />' . '<p>Some content text</p>' . str_pad('-', 200)]); $cm = get_course_and_cm_from_instance($page->id, 'page', $course->id)[1]; $pagemod = local::get_page_mod($cm); // Ensure summary contains text and is sitll within tags. $this->assertContains('<p>Some content text</p>', $pagemod->summary); // Ensure summary contains images. $this->assertContains('<img', $pagemod->summary); // Make sure summary text can be greater than 200 chars. $this->assertGreaterThan(200, strlen($pagemod->summary)); }
/** * Assert user activity. * @param stdClass $user * @param int $expected * @param int $limit */ protected function assert_user_activity($user, $expected, $limit = 10) { $activity = local::recent_forum_activity($user->id, $limit); // Ensure number of activity items matched. $this->assertEquals($expected, count($activity)); if (!empty($activity)) { // Ensure first activity item contains expected fields. $activityitem = $activity[0]; $this->assertNotEmpty($activityitem->user); $this->assertNotEmpty($activityitem->type); $this->assertNotEmpty($activityitem->content); $this->assertNotEmpty($activityitem->content->discussion); $this->assertNotEmpty($activityitem->content->id); $this->assertNotEmpty($activityitem->timestamp); $this->assertNotEmpty($activityitem->courseshortname); $this->assertNotEmpty($activityitem->forumname); $this->assertNotEmpty($activityitem->content->subject); } }
/** * The course update event. * * process cover image. * * @param course_updated $event * @return void */ public static function course_updated(course_updated $event) { $course = $event->get_record_snapshot('course', $event->objectid); $context = \context_course::instance($course->id); local::process_coverimage($context); }
public function test_poster_image_upload() { $this->resetAfterTest(); $beforeupload = local::site_coverimage_original(); $this->assertFalse($beforeupload); $fixtures = ['bpd_bikes_3888px.jpg' => true, 'bpd_bikes_1381px.jpg' => true, 'bpd_bikes_1380px.jpg' => false, 'bpd_bikes_1379px.jpg' => false, 'bpd_bikes_1280px.jpg' => false, 'testpng.png' => false, 'testpng_small.png' => false, 'testgif.gif' => false, 'testgif_small.gif' => false]; foreach ($fixtures as $filename => $shouldberesized) { $this->fake_site_image_setting_upload($filename); $css = '[[setting:poster]]'; $css = local::site_coverimage_css($css); $this->assertContains('/theme_snap/coverimage/', $css); $ext = pathinfo($filename, PATHINFO_EXTENSION); $this->assertContains("/site-image.{$ext}", $css); if ($shouldberesized) { $image = local::site_coverimage(); $finfo = $image->get_imageinfo(); $this->assertSame(1280, $finfo['width']); } } $this->fake_site_image_setting_cleared(); $css = '[[setting:poster]]'; $css = local::site_coverimage_css($css); $this->assertSame('', $css); $this->assertFalse(local::site_coverimage()); }
echo $OUTPUT->page_title(); ?> </title> <link rel="shortcut icon" href="<?php echo $OUTPUT->favicon(); ?> "/> <?php echo $OUTPUT->standard_head_html(); ?> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href='//fonts.googleapis.com/css?family=Roboto:500,100,400,300' rel='stylesheet' type='text/css'> <?php // Output course cover image? if ($COURSE->id != SITEID) { $courseimagecss = \theme_snap\local::course_coverimage_css($COURSE->id); } if (!empty($courseimagecss)) { echo "<style>{$courseimagecss}</style>"; } ?> </head> <body <?php echo $OUTPUT->body_attributes(); ?> > <?php echo $OUTPUT->standard_top_of_body_html();
"/> <?php echo $OUTPUT->standard_head_html(); ?> <meta name="theme-color" content="<?php echo $PAGE->theme->settings->themecolor; ?> "> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href='//fonts.googleapis.com/css?family=Roboto:500,100,400,300' rel='stylesheet' type='text/css'> <?php // Output course cover image? if ($COURSE->id != SITEID) { $coverimagecss = \theme_snap\local::course_coverimage_css($COURSE->id); } else { $coverimagecss = \theme_snap\local::site_coverimage_css(); } if (!empty($coverimagecss)) { echo "<style>{$coverimagecss}</style>"; } ?> </head> <body <?php echo $OUTPUT->body_attributes(); ?> > <?php echo $OUTPUT->standard_top_of_body_html();
/** * Set image css for course card (cover image, etc). */ private function apply_image_css() { $bgcolor = local::get_course_color($this->courseid); $this->imagecss = "background-color: #{$bgcolor};"; $bgimage = local::course_coverimage_url($this->courseid); if (!empty($bgimage)) { $this->imagecss .= "background-image: url({$bgimage});"; } }
/** * Custom hook that requires a patch to /index.php * for customized rendering of front page news. * * @return string */ public function site_frontpage_news() { global $CFG, $SITE; require_once $CFG->dirroot . '/mod/forum/lib.php'; if (!($forum = forum_get_course_forum($SITE->id, 'news'))) { print_error('cannotfindorcreateforum', 'forum'); } $cm = get_coursemodule_from_instance('forum', $forum->id, $SITE->id, false, MUST_EXIST); $context = context_module::instance($cm->id, MUST_EXIST); $output = html_writer::start_tag('div', array('id' => 'site-news-forum', 'class' => 'clearfix')); $output .= $this->heading(format_string($forum->name, true, array('context' => $context))); $groupmode = groups_get_activity_groupmode($cm, $SITE); $currentgroup = groups_get_activity_group($cm); if (!($discussions = forum_get_discussions($cm, 'p.modified DESC', true, null, $SITE->newsitems, false, -1, $SITE->newsitems))) { $output .= html_writer::tag('div', '(' . get_string('nonews', 'forum') . ')', array('class' => 'forumnodiscuss')); if (forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context)) { $output .= html_writer::link(new moodle_url('/mod/forum/post.php', array('forum' => $forum->id)), get_string('addanewtopic', 'forum'), array('class' => 'btn btn-primary')); } else { // No news and user cannot edit, so return nothing. return ''; } return $output . '</div>'; } $output .= html_writer::start_div('', array('id' => 'news-articles')); foreach ($discussions as $discussion) { if (!forum_user_can_see_discussion($forum, $discussion, $context)) { continue; } $message = file_rewrite_pluginfile_urls($discussion->message, 'pluginfile.php', $context->id, 'mod_forum', 'post', $discussion->id); $imagestyle = ''; $imgarr = \theme_snap\local::extract_first_image($message); if ($imgarr) { $imageurl = s($imgarr['src']); $imagestyle = " style=\"background-image:url('{$imageurl}')\""; } $name = format_string($discussion->name, true, array('context' => $context)); $date = userdate($discussion->timemodified, get_string('strftimedatetime', 'langconfig')); $readmorebtn = "<a class='btn btn-default toggle' href='" . $CFG->wwwroot . "/mod/forum/discuss.php?d=" . $discussion->discussion . "'>" . get_string('readmore', 'theme_snap') . "</a>"; $preview = ''; $newsimage = ''; if (!$imagestyle) { $preview = html_to_text($message, 0, false); $preview = "<div class='news-article-preview'><p>" . shorten_text($preview, 200) . "</p>\n <p class='text-right'>" . $readmorebtn . "</p></div>"; } else { $newsimage = '<div class="news-article-image toggle"' . $imagestyle . ' title="' . get_string('readmore', 'theme_snap') . '"></div>'; } $close = get_string('close', 'theme_snap'); $output .= <<<HTML <div class="news-article clearfix"> {$newsimage} <div class="news-article-inner"> <div class="news-article-content"> <h3 class='toggle'><a href="{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->discussion}">{$name}</a></h3> <em class="news-article-date">{$date}</em> </div> </div> {$preview} <div class="news-article-message" tabindex="-1"> {$message} <div><hr><a class="snap-action-icon toggle" href="#"> <i class="icon icon-office-52"></i><small>{$close}</small></a></div> </div> </div> HTML; } $actionlinks = html_writer::link(new moodle_url('/mod/forum/view.php', array('id' => $cm->id)), get_string('morenews', 'theme_snap'), array('class' => 'btn btn-default')); if (forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context)) { $actionlinks .= html_writer::link(new moodle_url('/mod/forum/post.php', array('forum' => $forum->id)), get_string('addanewtopic', 'forum'), array('class' => 'btn btn-primary')); } $output .= html_writer::end_div(); $output .= "<br><div class='text-center'>{$actionlinks}</div>"; $output .= html_writer::end_tag('div'); return $output; }
/** * Get coursecompletion data by course shortname. * @param string $shortname * @param array $previouslyunavailablesections * @param array $previouslyunavailablemods * @return array */ public function course_completion($shortname, $previouslyunavailablesections, $previouslyunavailablemods) { global $PAGE, $OUTPUT; $course = $this->coursebyshortname($shortname); list($unavailablesections, $unavailablemods) = local::conditionally_unavailable_elements($course); $newlyavailablesections = array_diff($previouslyunavailablesections, $unavailablesections); $newlyavailablemods = array_diff($previouslyunavailablemods, $unavailablemods); /** @var \theme_snap_core_course_renderer $courserenderer */ $courserenderer = $PAGE->get_renderer('core', 'course', RENDERER_TARGET_GENERAL); $newlyavailablesectionhtml = []; if (!empty($newlyavailablesections)) { foreach ($newlyavailablesections as $sectionnumber) { $html = $courserenderer->course_section_cm_list($course, $sectionnumber, $sectionnumber); $newlyavailablesectionhtml[$sectionnumber] = $html; } } $newlyavailablemodhtml = []; if (!empty($newlyavailablemods)) { $modinfo = get_fast_modinfo($course); foreach ($newlyavailablemods as $modid) { $completioninfo = new \completion_info($course); $cm = $modinfo->get_cm($modid); if (isset($newlyavailablesectionhtml[$cm->sectionnum])) { // This module's html has already been included in a newly available section. continue; } $html = $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $cm->sectionnum); $newlyavailablemodhtml[$modid] = $html; } } $unavailablesections = implode(',', $unavailablesections); $unavailablemods = implode(',', $unavailablemods); $toc = new course_toc($course); return ['unavailablesections' => $unavailablesections, 'unavailablemods' => $unavailablemods, 'newlyavailablemodhtml' => $newlyavailablemodhtml, 'newlyavailablesectionhtml' => $newlyavailablesectionhtml, 'toc' => $toc->export_for_template($OUTPUT)]; }
/** * Javascript required by both standard header layout and flexpage layout * * @return void */ public static function page_requires_js() { global $CFG, $PAGE, $COURSE, $USER; $PAGE->requires->jquery(); $PAGE->requires->strings_for_js(array('close', 'debugerrors', 'problemsfound', 'error:coverimageexceedsmaxbytes', 'error:coverimageresolutionlow', 'forumtopic', 'forumauthor', 'forumpicturegroup', 'forumreplies', 'forumlastpost', 'hiddencoursestoggle', 'loading', 'more', 'moving', 'movingcount', 'movehere', 'movefailed', 'movingdropsectionhelp', 'movingstartedhelp'), 'theme_snap'); $PAGE->requires->strings_for_js(['ok', 'cancel'], 'moodle'); $PAGE->requires->strings_for_js(['printbook'], 'booktool_print'); // Are we viewing /course/view.php - note, this is different from just checking the page type. // We only ever want to load course.js when on site page or view.php - no point in loading it when on // course settings page, etc. $courseviewpage = local::current_url_path() === '/course/view.php'; $pagehascoursecontent = $PAGE->pagetype === 'site-index' || $courseviewpage; $cancomplete = isloggedin() && !isguestuser(); $unavailablesections = []; $unavailablemods = []; if ($cancomplete) { $completioninfo = new \completion_info($COURSE); if ($completioninfo->is_enabled()) { $modinfo = get_fast_modinfo($COURSE); $sections = $modinfo->get_section_info_all(); foreach ($sections as $number => $section) { $ci = new \core_availability\info_section($section); $information = ''; if (!$ci->is_available($information, true)) { $unavailablesections[] = $number; } } foreach ($modinfo as $mod) { $ci = new \core_availability\info_module($mod); if (!$ci->is_available($information, true)) { $unavailablemods[] = $mod->id; } } } } list($unavailablesections, $unavailablemods) = local::conditionally_unavailable_elements($COURSE); $coursevars = (object) ['id' => $COURSE->id, 'shortname' => $COURSE->shortname, 'contextid' => $PAGE->context->id, 'ajaxurl' => '/course/rest.php', 'unavailablesections' => $unavailablesections, 'unavailablemods' => $unavailablemods, 'enablecompletion' => $COURSE->enablecompletion]; $initvars = [$coursevars, $pagehascoursecontent, get_max_upload_file_size($CFG->maxbytes)]; $PAGE->requires->js_call_amd('theme_snap/snap', 'snapInit', $initvars); // Does the page have editable course content? if ($pagehascoursecontent && $PAGE->user_allowed_editing()) { $canmanageacts = has_capability('moodle/course:manageactivities', context_course::instance($COURSE->id)); if ($canmanageacts && empty($USER->editing)) { $modinfo = get_fast_modinfo($COURSE); $modnamesused = $modinfo->get_used_module_names(); // Temporarily change edit mode to on for course ajax to be included. $USER->editing = true; self::include_course_ajax($COURSE, $modnamesused); $USER->editing = false; } } }
/** * Render recent forum activity. * * @param array $activities * @return string */ public function recent_forum_activity(array $activities) { global $OUTPUT; $output = ''; if (empty($activities)) { return ''; } $formatoptions = new stdClass(); $formatoptions->filter = false; foreach ($activities as $activity) { if (!empty($activity->user)) { $userpicture = new user_picture($activity->user); $userpicture->link = false; $userpicture->alttext = false; $userpicture->size = 32; $picture = $OUTPUT->render($userpicture); } else { $picture = ''; } $url = new moodle_url('/mod/' . $activity->type . '/discuss.php', ['d' => $activity->content->discussion], 'p' . $activity->content->id); $fullname = fullname($activity->user); $forumpath = $activity->courseshortname . ' / ' . $activity->forumname; $meta = [local::relative_time($activity->timestamp), format_text($forumpath, FORMAT_HTML, $formatoptions)]; $formattedsubject = '<p>' . format_text($activity->content->subject, FORMAT_HTML, $formatoptions) . '</p>'; $output .= $this->snap_media_object($url, $picture, $fullname, $meta, $formattedsubject); } return $output; }
/** * get course image * * @return bool|moodle_url */ public function get_course_image() { global $COURSE; return \theme_snap\local::course_coverimage_url($COURSE->id); }
public function test_course_completion() { global $DB; $this->resetAfterTest(); // Enable avaibility. // If not enabled all conditional fields will be ignored. set_config('enableavailability', 1); // Enable course completion. // If not enabled all completion settings will be ignored. set_config('enablecompletion', COMPLETION_ENABLED); $generator = $this->getDataGenerator(); // Create course with completion tracking enabled. $course = $generator->create_course(['enablecompletion' => 1, 'numsections' => 3], ['createsections' => true]); // Enrol user to completion tracking course. $sturole = $DB->get_record('role', array('shortname' => 'student')); $generator->enrol_user($this->user1->id, $course->id, $sturole->id); // Create page with completion marked on view. $page1 = $generator->create_module('page', array('course' => $course->id, 'name' => 'page1 complete on view'), array('completion' => 2, 'completionview' => 1)); $modinfo = get_fast_modinfo($course); $page1cm = $modinfo->get_cm($page1->cmid); // Create page restricted to only show when first page is viewed. $moduleinfo = (object) []; $moduleinfo->course = $course->id; $moduleinfo->name = 'page2 available after page1 viewed'; $moduleinfo->availability = json_encode(\core_availability\tree::get_root_json([\availability_completion\condition::get_json($page1->cmid, COMPLETION_COMPLETE)], '&')); $page2 = $generator->create_module('page', $moduleinfo); // Make section 2 restricted to only show when first page is viewed. $section = $modinfo->get_section_info(2); $sectionupdate = ['id' => $section->id, 'availability' => json_encode(\core_availability\tree::get_root_json([\availability_completion\condition::get_json($page1->cmid, COMPLETION_COMPLETE)], '&'))]; $DB->update_record('course_sections', $sectionupdate); // Check user1 has expected unavailable section and mod. $this->setUser($this->user1); // Dump cache and reget modinfo. get_fast_modinfo($course, 0, true); $modinfo = get_fast_modinfo($course); $page2cm = $modinfo->get_cm($page2->cmid); list($previouslyunavailablesections, $previouslyunavailablemods) = local::conditionally_unavailable_elements($course); $this->assertContains(2, $previouslyunavailablesections); $this->assertContains($page2cm->id, $previouslyunavailablemods); // View page1 to trigger completion $context = context_module::instance($page1->cmid); page_view($page1, $course, $page1cm, $context); $completion = new completion_info($course); $completiondata = $completion->get_data($page1cm); $this->assertEquals(COMPLETION_COMPLETE, $completiondata->completionstate); get_fast_modinfo($course, 0, true); // Reset modinfo. // Make sure that unavailable sections and mods no longer contain the ones requiring availabililty criteria // satisfying. list($unavailablesections, $unavailablemods) = local::conditionally_unavailable_elements($course); $this->assertNotContains($page2cm->id, $unavailablemods); $this->assertNotContains(2, $unavailablesections); $result = $this->courseservice->course_completion($course->shortname, $previouslyunavailablesections, $previouslyunavailablemods); // Make sure that the second page module (which is now newly available) appears in the list of newly available // module html. $this->assertTrue(isset($result['newlyavailablemodhtml'][$page2->cmid])); // Make sure that the second section (which is now wnely available) appears in the list of newly available // section html. $this->assertTrue(isset($result['newlyavailablesectionhtml'][2])); }
// 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 <http://www.gnu.org/licenses/>. /** * Theme config * * @package theme_snap * @copyright Copyright (c) 2015 Moodlerooms Inc. (http://www.moodlerooms.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; use theme_snap\local; global $SESSION, $COURSE, $USER, $PAGE; $theme = local::resolve_theme(); $themeissnap = $theme === 'snap'; $notajaxscript = !defined('AJAX_SCRIPT') || AJAX_SCRIPT == false; // The code inside this conditional block is to be executed prior to page rendering when the theme is set to snap and // when the current request is not an ajax request. // There doesn't appear to be an official hook we can use for doing things prior to page rendering, so this is a // workaround. if ($themeissnap && $notajaxscript) { // Setup debugging html. // This allows javascript to target debug messages and move them to footer. if (!empty($CFG->snapwrapdebug) && !function_exists('xdebug_break')) { ini_set('error_prepend_string', '<div class="php-debug">'); ini_set('error_append_string', '</div>'); } // SL - dec 2015 - Make sure editing sessions are not carried over between courses. if (empty($SESSION->theme_snap_last_course) || $SESSION->theme_snap_last_course != $COURSE->id) {