You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1381 lines
63 KiB

<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Tiles course format, main course output class to prepare data for mustache templates
*
* @package format_tiles
* @copyright 2018 David Watson {@link http://evolutioncode.uk}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace format_tiles\output;
use format_tiles\tile_photo;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot .'/course/format/lib.php');
require_once("$CFG->libdir/resourcelib.php"); // To import RESOURCELIB_DISPLAY_POPUP.
/**
* Tiles course format, main course output class to prepare data for mustache templates
* @copyright 2018 David Watson
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_output implements \renderable, \templatable
{
/**
* Course object for this class
* @var \stdClass
*/
private $course;
/**
* Whether this class is called from AJAX
* @var bool
*/
private $fromajax;
/**
* The section number of the section we want to display
* @var int
*/
private $sectionnum;
/**
* The course renderer object
* @var \renderer_base
*/
private $courserenderer;
/**
* Array of display names to be used at the top of sub tiles depending
* on resource type of the module.
* e.g. 'mod/lti' => 'External Tool' 'mod/resource','xls' = "Spreadsheet'
* @var array
*/
private $resourcedisplaynames;
/**
* Names of the modules for which modal windows should be used e.g. 'page'
* @var array of resources and modules
*/
private $usemodalsforcoursemodules;
/**
* User's device type e.g. DEVICE_TYPE_MOBILE ('mobile')
* @var string
*/
private $devicetype;
/**
* The course format.
* @var
*/
private $format;
/**
* @var \course_modinfo|null
*/
private $modinfo;
/**
* @var bool
*/
private $isediting;
/**
* @var bool
*/
private $canviewhidden;
/**
* @var \context_course
*/
private $coursecontext;
/**
* @var \completion_info
*/
private $completioninfo;
/**
* @var bool
*/
private $completionenabled;
/**
* @var mixed
*/
private $courseformatoptions;
/**
* course_output constructor.
* @param \stdClass $course the course object.
* @param bool $fromajax Whether we are rendering for AJAX request.
* @param int $sectionnum the id of the current section
* @param \renderer_base|null $courserenderer
*/
public function __construct($course, $fromajax = false, $sectionnum = 0, \renderer_base $courserenderer = null) {
global $PAGE;
$this->course = $course;
$this->fromajax = $fromajax;
$this->sectionnum = $sectionnum;
if ($fromajax) {
$this->courserenderer = $PAGE->get_renderer('core', 'course');
} else {
$this->courserenderer = $courserenderer;
}
$this->devicetype = \core_useragent::get_device_type();
$this->usemodalsforcoursemodules = format_tiles_allowed_modal_modules();
$this->format = course_get_format($course);
$this->modinfo = get_fast_modinfo($this->course);
$this->isediting = $PAGE->user_is_editing();
$this->coursecontext = \context_course::instance($this->course->id);
$this->canviewhidden = has_capability('moodle/course:viewhiddensections', $this->coursecontext);
if ($this->course->enablecompletion && !isguestuser()) {
$this->completioninfo = new \completion_info($this->course);
}
$this->completionenabled = $course->enablecompletion && !isguestuser();
$this->courseformatoptions = $this->get_course_format_options($this->fromajax);
}
/**
* Export the course data for the mustache template.
* @param \renderer_base $output
* @return array|\stdClass
* @throws \coding_exception
* @throws \dml_exception
* @throws \moodle_exception
*/
public function export_for_template(\renderer_base $output) {
$data = $this->get_basic_data($output);
$data = $this->append_section_zero_data($data, $output);
// We have assembled the "common data" needed for both single and multiple section pages.
// Now we can go off and get the specific data for the single or multiple page as required.
if ($this->sectionnum) {
// We are outputting a single section page.
return $this->append_single_section_page_data($output, $data);
} else {
// We are outputting a single section page.
return $this->append_multi_section_page_data($output, $data);
}
}
/**
* Get the basic data required to render (required whatever we are doing).
* @param \renderer_base $output
* @return array data
* @throws \coding_exception
* @throws \dml_exception
*/
private function get_basic_data($output) {
$data = [];
$data['canedit'] = has_capability('moodle/course:update', $this->coursecontext);
$data['canviewhidden'] = $this->canviewhidden;
$data['courseid'] = $this->course->id;
$data['completionenabled'] = $this->completionenabled;
$data['from_ajax'] = $this->fromajax;
$data['ismobile'] = $this->devicetype == \core_useragent::DEVICETYPE_MOBILE;
if (isset($SESSION->format_tiles_jssuccessfullyused)) {
// If this flag is set, user is being shown JS versions of pages.
// Allow them to cancel the session var if they have no JS.
$data['showJScancelLink'] = 1;
} else {
$data['showJScancelLink'] = 0;
};
$data['isediting'] = $this->isediting;
$data['sesskey'] = sesskey();
$data['showinitialpageloadingicon'] = format_tiles_width_template_data($this->course->id)['hidetilesinitially'];
$data['userdisabledjsnav'] = get_user_preferences('format_tiles_stopjsnav');
$data['useSubtiles'] = get_config('format_tiles', 'allowsubtilesview') && $this->courseformatoptions['courseusesubtiles'];
$data['usingjsnav'] = get_config('format_tiles', 'usejavascriptnav')
&& !get_user_preferences('format_tiles_stopjsnav');
if (!$this->isediting) {
$data['course_activity_clipboard'] = $output->course_activity_clipboard($this->course, $this->sectionnum);
}
foreach ($this->courseformatoptions as $k => $v) {
$data[$k] = $v;
}
return $data;
}
/**
* Export the course data for the mustache template.
* @param \renderer_base $output
* @param array $cmids the course module ids for the cms to export.
* @return array|\stdClass
* @throws \coding_exception
* @throws \dml_exception
* @throws \moodle_exception
*/
public function export_for_template_cms_only(\renderer_base $output, $cmids) {
$data = $this->get_basic_data($output);
if (!$this->fromajax) {
throw new \invalid_parameter_exception("Allowed from AJAX only");
}
if (is_numeric($this->sectionnum)) {
if (!isset($this->modinfo->sections[$this->sectionnum])) {
debugging("Section not in course " . $this->sectionnum);
return [];
} else {
$section = $this->modinfo->get_section_info($this->sectionnum);
$data['coursemodules'] = $this->section_course_mods($section, $output);
return $data;
}
} else {
// We have a list of cmids to process instead;
// TODO fix this.
throw BadMethodCallException("Not yet implemented");
}
}
/**
* Export the course data for the mustache template.
* @param \renderer_base $output
* @return array|\stdClass
* @throws \coding_exception
* @throws \dml_exception
* @throws \moodle_exception
*/
public function export_for_template_modchooser_only(\renderer_base $output) {
$data = $this->get_basic_data($output);
if (!$this->fromajax) {
throw new \invalid_parameter_exception("Allowed from AJAX only");
}
if ($this->sectionnum && $this->isediting) {
$section = $this->modinfo->get_section_info($this->sectionnum);
$data['single_sec_add_cm_control_html'] = $this->courserenderer->course_section_add_cm_control(
$this->course, $this->sectionnum, $section->id
);
}
return $data;
}
/**
* Append the data we need to render section zero.
* @param [] $data
* @param \renderer_base $output
* @return mixed
* @throws \coding_exception
* @throws \dml_exception
* @throws \moodle_exception
*/
private function append_section_zero_data($data, $output) {
$seczero = $this->modinfo->get_section_info(0);
$data['section_zero']['summary'] = $output->format_summary_text($seczero);
$data['section_zero']['content']['course_modules'] = $this->section_course_mods($seczero, $output);
$data['section_zero']['secid'] = $this->modinfo->get_section_info(0)->id;
$data['section_zero']['is_section_zero'] = true;
$data['section_zero']['tileid'] = 0;
$data['section_zero']['visible'] = true;
// Only show section zero if we need it.
$data['section_zero_show'] = 0;
if ($this->sectionnum == 0 || get_config('format_tiles', 'showseczerocoursewide')) {
// We only want to show section zero if we are on the landing page, or admin has said we should show it course wide.
if ($this->isediting || $seczero->summary || !empty($data['section_zero']['content']['course_modules'])) {
// We do have something to show, or are editing, so need to show it.
$data['section_zero_show'] = 1;
}
}
if ($this->courseformatoptions['courseusesubtiles'] && $this->courseformatoptions['usesubtilesseczero']) {
$data['section_zero']['useSubtiles'] = 1;
} else {
$data['section_zero']['useSubtiles'] = 0;
}
return $data;
}
/**
* Get the course format options (how depends on where we are calling from).
* @param bool $fromajax is this request from AJAX.
* @return array
*/
private function get_course_format_options($fromajax) {
// Custom course settings not in course object if called from AJAX, so make sure we get them.
$options = [
'defaulttileicon', 'basecolour', 'courseusesubtiles', 'courseshowtileprogress',
'displayfilterbar', 'usesubtilesseczero', 'courseusebarforheadings'
];
$data = [];
if (!$fromajax) {
foreach ($options as $option) {
if (isset($this->course->$option)) {
$data[$option] = $this->course->$option;
}
}
} else {
$data = $this->format->get_format_options();
}
return $data;
}
/**
* Take the "common data" supplied as the $data argument, and build on it
* with data which is specific to single section pages, then return
* the amalgamated data
* @param \renderer_base $output the renderer for this format
* @param array $data the common data
* @return array the amalgamated data
* @throws \coding_exception
* @throws \dml_exception
* @throws \moodle_exception
*/
private function append_single_section_page_data($output, $data) {
// If we have nothing to output, don't.
if (!($thissection = $this->modinfo->get_section_info($this->sectionnum))) {
// This section doesn't exist.
print_error('unknowncoursesection', 'error', null, $this->course->fullname);
return $data;
}
if (!$thissection->uservisible) {
// Can't view this section - in that case the template will just render 'Not available' and nothing else.
$data['hidden_section'] = true;
return $data;
}
// Data for the requested section page.
$data['title'] = get_section_name($this->course, $thissection->section);
$data['summary'] = $output->format_summary_text($thissection);
$data['tileid'] = $thissection->section;
$data['secid'] = $thissection->id;
$data['tileicon'] = $thissection->tileicon;
// If photo tile backgrounds are allowed by site admin, prepare the image for this section.
if (get_config('format_tiles', 'allowphototiles')) {
$tilephoto = new tile_photo($this->course->id, $thissection->id);
$tilephotourl = $tilephoto->get_image_url();
$data['phototileinlinestyle'] = 'style = "background-image: url(' . $tilephotourl . ')";';
$data['hastilephoto'] = $tilephotourl ? 1 : 0;
$data['phototileurl'] = $tilephotourl;
$data['phototileediturl'] = new \moodle_url(
'/course/format/tiles/editimage.php',
array('courseid' => $this->course->id, 'sectionid' => $thissection->id)
);
}
// Include completion help icon HTML.
if ($this->completioninfo) {
$data['completion_help'] = true;
}
// The list of activities on the page (HTML).
$data['course_modules'] = $this->section_course_mods($thissection, $output);
// If lots of content in this section, we include nav arrows again at bottom of page.
// But otherwise not as looks odd when no content.
$longsectionlength = 10000;
if (strlen('single_sec_content') > $longsectionlength) {
$data['single_sec_content_is_long'] = true;
}
$previousnext = $this->get_previous_next_section_ids($thissection->section);
$data['previous_tile_id'] = $previousnext['previous'];
$data['next_tile_id'] = $previousnext['next'];
// If user is editing, add the edit controls.
if ($this->isediting) {
if (optional_param('section', 0, PARAM_INT)) {
$data['inplace_editable_title'] = $output->section_title_without_link($thissection, $this->course);
} else {
$data['inplace_editable_title'] = $output->section_title($thissection, $this->course);
}
$data['single_sec_add_cm_control_html'] = $this->courserenderer->course_section_add_cm_control(
$this->course, $thissection->section, $thissection->section
);
}
$data['visible'] = $thissection->visible;
// If user can view hidden items, include the explanation as to why an item is hidden.
if ($this->canviewhidden) {
$data['availabilitymessage'] = $output->section_availability_message($thissection, $this->canviewhidden);
}
return $data;
}
/**
* Take the "common data" supplied as the $data argument, and build on it
* with data which is specific to multiple section pages, then return
* the amalgamated data
* @param \renderer_base $output the renderer for this format
* @param array $data the common data
* @return array the amalgamated data
* @throws \coding_exception
* @throws \dml_exception
* @throws \moodle_exception
*/
private function append_multi_section_page_data($output, $data) {
global $SESSION;
$data['is_multi_section'] = true;
// If using completion tracking, get the data.
if ($this->completionenabled) {
$data['overall_progress']['num_complete'] = 0;
$data['overall_progress']['num_out_of'] = 0;
}
$data['hasNoSections'] = true;
// Before we start the section loop. get key vars for photo tiles ready.
$allowedphototiles = get_config('format_tiles', 'allowphototiles');
$usingphotoaltstyle = get_config('format_tiles', 'phototilesaltstyle');
if ($allowedphototiles) {
$data['allowphototiles'] = 1;
$phototileids = tile_photo::get_photo_tile_ids($this->course->id);
$phototileextraclasses = 'phototile';
if ($usingphotoaltstyle) {
$phototileextraclasses .= ' altstyle';
}
}
foreach ($this->modinfo->get_section_info_all() as $sectionnum => $section) {
// Show the section if the user is permitted to access it, OR if it's not available
// but there is some available info text which explains the reason & should display,
// OR it is hidden but the course has a setting to display hidden sections as unavilable.
$isphototile = $allowedphototiles && array_search($section->id, $phototileids) !== false;
$showsection = $section->uservisible ||
($section->visible && !$section->available && !empty($section->availableinfo));
if ($sectionnum != 0 && $showsection) {
$title = htmlspecialchars_decode($this->truncate_title(get_section_name($this->course, $sectionnum)));
if ($allowedphototiles && $usingphotoaltstyle && $isphototile) {
// Replace the last space with &nbsp; to avoid having one word on the last line of the tile title.
$title = preg_replace('/\s(\S*)$/', '&nbsp;$1', $title);
}
$longtitlelength = 65;
$newtile = array(
'tileid' => $section->section,
'secid' => $section->id,
'title' => $title,
'tileicon' => $section->tileicon,
'current' => course_get_format($this->course)->is_section_current($section),
'hidden' => !$section->visible,
'visible' => $section->visible,
'restricted' => !($section->available),
'userclickable' => $section->available || $section->uservisible,
'activity_summary' => $output->section_activity_summary($section, $this->course, null),
'titleclass' => strlen($title) >= $longtitlelength ? ' longtitle' : '',
'progress' => false,
'isactive' => $this->course->marker == $section->section,
'extraclasses' => ''
);
// If photo tile backgrounds are allowed by site admin, prepare them for this tile.
if ($isphototile) {
$tilephoto = new tile_photo($this->course->id, $section->id);
$tilephotourl = $tilephoto->get_image_url();
$newtile['extraclasses'] .= $phototileextraclasses;
$newtile['phototileinlinestyle'] = 'style = "background-image: url(' . $tilephotourl . ')";';
$newtile['hastilephoto'] = $tilephotourl ? 1 : 0;
$newtile['phototileurl'] = $tilephotourl;
$newtile['phototileediturl'] = new \moodle_url(
'/course/format/tiles/editimage.php',
array('courseid' => $this->course->id, 'sectionid' => $section->id)
);
}
// If user is editing, add the edit controls.
if ($this->isediting) {
$newtile['inplace_editable_title'] = $output->section_title($section, $this->course);
$newtile['section_edit_control'] = $output->section_edit_control_menu(
$output->section_edit_control_items($this->course, $section, false),
$this->course,
$section
);
}
// Include completion tracking data for each tile (if used).
if ($section->visible && $this->completionenabled) {
if (isset($this->modinfo->sections[$sectionnum])) {
$completionthistile = $this->section_progress(
$this->modinfo->sections[$sectionnum],
$this->modinfo->cms, $this->completioninfo
);
// Keep track of overall progress so we can show this too - add this tile's completion to the totals.
$data['overall_progress']['num_out_of'] += $completionthistile['outof'];
$data['overall_progress']['num_complete'] += $completionthistile['completed'];
// We only add the tile values to the individual tile if courseshowtileprogress is true.
// (Otherwise we only retain overall completion as above, not for each tile).
if ($this->courseformatoptions['courseshowtileprogress']) {
$showaspercent = $this->courseformatoptions['courseshowtileprogress'] == 2 ? true : false;
$newtile['progress'] = $this->completion_indicator(
$completionthistile['completed'],
$completionthistile['outof'],
$showaspercent,
false
);
}
}
}
// If item is restricted, user needs to know why.
$newtile['availabilitymessage'] = $output->section_availability_message($section, $this->canviewhidden);
if ($this->courseformatoptions['displayfilterbar'] == FORMAT_TILES_FILTERBAR_OUTCOMES
|| $this->courseformatoptions['displayfilterbar'] == FORMAT_TILES_FILTERBAR_BOTH) {
$newtile['tileoutcomeid'] = $section->tileoutcomeid;
}
// See below about when "hide add cm control" is true.
$newtile['hideaddcmcontrol'] = false;
$newtile['single_sec_add_cm_control_html'] = $this->courserenderer->course_section_add_cm_control(
$this->course, $section->section, 0
);
if ($this->is_section_editing_expanded($section->section)) {
// The list of activities on the page (HTML).
$newtile['course_modules'] = $this->section_course_mods($section, $output);
$newtile['is_expanded'] = true;
} else {
$newtile['is_expanded'] = false;
}
// Finally add tile we constructed to the array.
$data['tiles'][] = $newtile;
} else if ($sectionnum == 0) {
// Add in section zero completion data to overall completion count.
if ($section->visible && $this->completionenabled) {
if (isset($this->modinfo->sections[$sectionnum])) {
$completionthistile = $this->section_progress(
$this->modinfo->sections[$sectionnum],
$this->modinfo->cms, $this->completioninfo
);
// Keep track of overall progress so we can show this too - add this tile's completion to the totals.
$data['overall_progress']['num_out_of'] += $completionthistile['outof'];
$data['overall_progress']['num_complete'] += $completionthistile['completed'];
}
}
}
}
$data['all_tiles_expanded'] = $this->isediting &&
(
optional_param('expanded', 0, PARAM_INT) == 1
|| (
isset($SESSION->editing_all_sections_expanded_course)
&& $SESSION->editing_all_sections_expanded_course == $this->course->id
)
);
// Now the filter buttons (if used).
$data['has_filter_buttons'] = false;
if ($this->courseformatoptions['displayfilterbar']) {
$firstidoutcomebuttons = 1;
if ($this->courseformatoptions['displayfilterbar'] == FORMAT_TILES_FILTERBAR_NUMBERS
|| $this->courseformatoptions['displayfilterbar'] == FORMAT_TILES_FILTERBAR_BOTH) {
$data['fiternumberedbuttons'] = $this->get_filter_numbered_buttons_data($data['tiles']);
if (count($data['fiternumberedbuttons']) > 0) {
$firstidoutcomebuttons = count($data['fiternumberedbuttons']) + 1;
$data['has_filter_buttons'] = true;
}
}
if ($this->courseformatoptions['displayfilterbar'] == FORMAT_TILES_FILTERBAR_OUTCOMES
|| $this->courseformatoptions['displayfilterbar'] == FORMAT_TILES_FILTERBAR_BOTH) {
$outcomes = course_get_format($this->course)->format_tiles_get_course_outcomes($this->course->id);
$data['fiteroutcomebuttons'] = $this->get_filter_outcome_buttons_data(
$data['tiles'], $outcomes, $firstidoutcomebuttons
);
if (count($data['fiternumberedbuttons']) > 0) {
$data['has_filter_buttons'] = true;
}
}
}
$data['section_zero_add_cm_control_html'] = $this->courserenderer->course_section_add_cm_control($this->course, 0, 0);
if ($this->completionenabled && $data['overall_progress']['num_out_of'] > 0) {
$data['overall_progress_indicator'] = $this->completion_indicator(
$data['overall_progress']['num_complete'],
$data['overall_progress']['num_out_of'],
true,
true
);
$data['overall_progress_indicator']['tileid'] = 0;
// If completion tracking is on but nothing to track at activity level, display help to teacher.
if ($this->isediting && $data['overall_progress']['num_out_of'] == 0) {
$bulklink = \html_writer::link(
new \moodle_url('/course/bulkcompletion.php', array('id' => $this->course->id)),
get_string('completionwarning_changeinbulk', 'format_tiles')
);
$helplink = \html_writer::link(
get_docs_url('Activity_completion_settings#Changing_activity_completion_settings_in_bulk'),
$output->pix_icon('help', '', 'core')
);
\core\notification::WARNING(
get_string('completionwarning', 'format_tiles') . ' ' . $bulklink . ' ' . $helplink
);
}
}
return $data;
}
/**
* Count the number of course modules with completion tracking activated
* in this section, and the number which the student has completed
* Exclude labels if we are using sub tiles, as these are not checkable
* Also exclude items the user cannot see e.g. restricted
* @param array $sectioncmids the ids of course modules to count
* @param array $coursecms the course module objects for this course
* @return array with the completion data x items complete out of y
*/
private function section_progress($sectioncmids, $coursecms) {
$completed = 0;
$outof = 0;
foreach ($sectioncmids as $cmid) {
$thismod = $coursecms[$cmid];
if ($thismod->uservisible && !$this->treat_as_label($thismod)) {
if ($this->completioninfo->is_enabled($thismod) != COMPLETION_TRACKING_NONE) {
$outof++;
$completiondata = $this->completioninfo->get_data($thismod, true);
if ($completiondata->completionstate == COMPLETION_COMPLETE ||
$completiondata->completionstate == COMPLETION_COMPLETE_PASS
) {
$completed++;
}
}
}
}
return array('completed' => $completed, 'outof' => $outof);
}
/**
* Get the details of the filter buttons to be displayed at the top of this course
* where the teacher has selected to use numbered filter buttons e.g. button 1 might
* filter to tiles 1-3, button 2 to tiles 4-6 etc
* @see get_button_map() which calls this function
* @param array $tiles the tiles which relate to filters
* @return array the button details
*/
private function get_filter_numbered_buttons_data($tiles) {
$numberoftiles = count($tiles);
if ($numberoftiles == 0) {
return array();
}
// Find out the number to use for each tile from its title e.g. "1 Introduction" filters to "1".
$tilenumbers = [];
foreach ($tiles as $tile) {
if ($statednum = $this->get_stated_tile_num($tile)) {
$tilenumbers[$statednum] = $tile['tileid'];
}
}
ksort($tilenumbers);
// Break the tiles down into chunks - one chunk per button.
if ($numberoftiles <= 15) {
$tilesperbutton = 3;
} else if ($numberoftiles <= 30) {
$tilesperbutton = 4;
} else {
$tilesperbutton = 6;
}
$buttons = array_chunk($tilenumbers, $tilesperbutton, true);
// Now populate each button and map the tile details to it.
$buttonmap = [];
$buttonid = 1;
foreach ($buttons as $button => $tilesthisbutton) {
if (!empty($tiles)) {
$tilestatednumers = array_keys($tilesthisbutton);
if ($tilestatednumers[0] == end($tilestatednumers)) {
$title = $tilestatednumers[0];
} else {
$title = $tilestatednumers[0] . '-' . end($tilestatednumers);
}
$buttonmap[] = array(
'id' => 'filterbutton' . $buttonid,
'title' => $title,
'sections' => json_encode(array_values($tilesthisbutton)),
'buttonnum' => $buttonid
);
}
$buttonid++;
}
return $buttonmap;
}
/**
* Get the details of the filter buttons to be displayed at the top of this course
* where the teacher has selected to use OUTCOME filter buttons e.g. button 1 might
* filter to outcome 1, button 2 to outcome 2 etc
* @param array $tiles the tiles output object showing the outcome ID for each tile
* @param array $outcomenames the course outcome names to display
* @param int $firstbuttonid first button id so it follows on from last one
* @see get_filter_numbered_buttons()
* @return array|string the button details
*/
private function get_filter_outcome_buttons_data($tiles, $outcomenames, $firstbuttonid = 1) {
$outcomebuttons = [];
if ($outcomenames) {
// Build array showing, for each outcome, which sections of the course use it.
$outcomesections = [];
foreach ($tiles as $index => $tile) {
if (isset($tile['tileoutcomeid']) && $tile['tileoutcomeid']) {
// This tile has an outcome attached, so add it to the array of tiles for that outcome.
$outcomesections[$tile['tileoutcomeid']][] = $tile['tileid'];
}
}
// For each outcome found on tiles, add its outcome name and all tiles found for it to return array.
$buttonid = $firstbuttonid;
foreach ($outcomesections as $outcomeid => $outcomesectionsthisoutcome) {
if (array_key_exists($outcomeid, $outcomenames)) {
$outcomebuttons[] = array(
'id' => 'filterbutton' . $buttonid,
'title' => $outcomenames[$outcomeid],
'sections' => json_encode(array_values($outcomesectionsthisoutcome)),
);
}
$buttonid++;
}
}
return $outcomebuttons;
}
/**
* Get the number which the author has stated for this tile so that it can
* be used for filter buttons. e.g. "1 Introduction" or "Week 1 Introduction" give
* a filtering number of 1
*
* @param array $tile the tile output data
* @return string HTML to output.
*/
private function get_stated_tile_num($tile) {
if (!$tile['title']) {
return $tile['tileid'];
} else {
// If title for example starts "16.2" or "16)" treat it as "16".
$title = str_replace(')', ' ', str_replace('.', ' ', $tile['title']));
$title = explode(' ', $title);
for ($i = 0; $i <= count($title) - 1; $i++) {
// Iterate through each word in the title and see if it's a number - if it is, we have what we want.
$statednumber = preg_replace('/[^0-9]/', '', $title[$i]);
if ($statednumber && ctype_digit($statednumber)) {
return intval($statednumber);
}
}
}
return null;
}
/**
* Take a title (e.g. from a section) and truncate it if too big for sub tile
* @param string $title to truncated
* @return string truncated
*/
private function truncate_title($title) {
$maxtitlelength = 75;
if (strlen($title) >= $maxtitlelength) {
$lastspace = strripos(substr($title, 0, $maxtitlelength), ' ');
$title = substr($title, 0, $lastspace) . ' ...';
}
return trim($title);
}
/**
* Gets the data (context) to be used with the activityinstance template
* @param object $section the section object we want content for
* @param \renderer_base $output
* @see \cm_info for full detail of $mod instance variables
* @see \core_completion\manager::get_activities() which covers similar ground
* @see \core_course_renderer::course_section_cm_completion() which covers similar ground
* In the snap theme, course_renderer::course_section_cm_list_item() covers similar ground
* @return array
* @throws \coding_exception
* @throws \dml_exception
* @throws \moodle_exception
*/
private function section_course_mods($section, $output) {
if (!isset($section->section)) {
debugging("section->section is not set");
}
if (!isset($this->modinfo->sections[$section->section]) || !$cmids = $this->modinfo->sections[$section->section]) {
// There are no CMs for the section (i.e. section is empty) so we silently return.
return [];
}
if (empty($cmids)) {
// There are no CMs for the section (i.e. section is empty) so we silently return.
return [];
}
$previouswaslabel = false;
$sectioncontent = [];
foreach ($cmids as $index => $cmid) {
$mod = $this->modinfo->get_cm($cmid);
$treataslabel = $this->treat_as_label($mod);
$moduledata = $this->course_module_data(
$mod,
$treataslabel,
$section,
$previouswaslabel,
$index == 0,
$output
);
$previouswaslabel = $treataslabel;
if (!empty($moduledata)) {
$sectioncontent[] = $moduledata;
}
}
return $sectioncontent;
}
/**
* Assemble and return the data to render a single course module.
* @param \cm_info $mod
* @param bool $treataslabel
* @param object $section
* @param bool $previouswaslabel
* @param bool $isfirst
* @param \renderer_base $output
* @return array
* @throws \coding_exception
* @throws \dml_exception
* @throws \moodle_exception
*/
private function course_module_data($mod, $treataslabel, $section, $previouswaslabel, $isfirst, $output) {
global $PAGE, $CFG, $DB;
$moduleobject = [];
if ($this->canviewhidden) {
$moduleobject['uservisible'] = true;
$moduleobject['clickable'] = true;
} else if (!$mod->uservisible && $mod->visibleoncoursepage && $mod->availableinfo && $mod->visible) {
// Activity is not available, not hidden from course page and has availability info.
// So it is actually visible on the course page (with availability info and without a link).
$moduleobject['uservisible'] = true;
$moduleobject['clickable'] = false;
} else {
$moduleobject['uservisible'] = $mod->uservisible;
$moduleobject['clickable'] = $mod->uservisible;
}
if (!$moduleobject['uservisible'] || $mod->deletioninprogress || ($mod->is_stealth() && !$this->canviewhidden)) {
return [];
}
// If the module isn't available, or we are a teacher (can view hidden activities) get availability info.
if (!$mod->available || $this->canviewhidden) {
$moduleobject['availabilitymessage'] = $this->courserenderer->course_section_cm_availability($mod, array());
}
$moduleobject['available'] = $mod->available;
$moduleobject['cmid'] = $mod->id;
$moduleobject['modtitle'] = $mod->get_formatted_name();
$moduleobject['modname'] = $mod->modname;
$moduleobject['iconurl'] = $mod->get_icon_url()->out(true);
$moduleobject['url'] = $mod->url;
$moduleobject['visible'] = $mod->visible;
$moduleobject['launchtype'] = 'standard';
$moduleobject['content'] = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
if (!$this->courseformatoptions['courseusesubtiles'] && $mod->indent) {
$moduleobject['indentlevel'] = $mod->indent;
}
// We set this here, with the value from the last loop, before updating it in the next block.
// So that we can use it again on the next loop.
$moduleobject['previouswaslabel'] = $previouswaslabel;
if ($treataslabel) {
$moduleobject['is_label'] = true;
$moduleobject['long_label'] = strlen($mod->content) > 300 ? 1 : 0;
if ($isfirst && !$previouswaslabel && $this->courseformatoptions['courseusesubtiles']) {
$moduleobject['hasSpacersBefore'] = 1;
}
}
if (isset($mod->instance)) {
$moduleobject['modinstance'] = $mod->instance;
}
$moduleobject['modResourceType'] = $this->get_resource_filetype($mod);
$moduleobject['modnameDisplay'] = $this->mod_displayname($mod->modname, $moduleobject['modResourceType']);
// Specific handling for embedded resource items (e.g. PDFs) as allowed by site admin.
if ($mod->modname == 'resource') {
if (array_search($moduleobject['modResourceType'], $this->usemodalsforcoursemodules['resources']) !== false) {
$moduleobject['isEmbeddedResource'] = 1;
$moduleobject['launchtype'] = 'resource-modal';
$moduleobject['pluginfileUrl'] = $this->plugin_file_url($mod);
} else {
// We don't want to embed the file in a modal.
// If this is a mobile device or tablet, override the standard URL (already allocated above).
// Then user can access file natively in their device (better than embedded).
// Otherwise the standard URL will remain i.e. mod/resource/view.php?id=...
if ($this->devicetype == \core_useragent::DEVICETYPE_TABLET
|| $this->devicetype == \core_useragent::DEVICETYPE_MOBILE) {
$moduleobject['url'] = $this->plugin_file_url($mod);
}
}
}
// Specific handling for embedded course module items (e.g. page) as allowed by site admin.
if (array_search($mod->modname, $this->usemodalsforcoursemodules['modules']) !== false) {
$moduleobject['isEmbeddedModule'] = 1;
$moduleobject['launchtype'] = 'module-modal';
}
$moduleobject['showdescription'] =
isset($mod->showdescription) && !$this->treat_as_label($mod) ? $mod->showdescription : 0;
if ($moduleobject['showdescription']) {
// The reason we need 'noclean' arg here is that otherwise youtube etc iframes will be stripped out.
$moduleobject['description'] = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
}
$moduleobject['extraclasses'] = $mod->extraclasses;
$moduleobject['afterlink'] = $mod->afterlink;
if ($mod->is_stealth()) {
$moduleobject['extraclasses'] .= ' stealth';
$moduleobject['stealth'] = 1;
} else if (
(!$mod->visible && !$mod->visibleold)
|| !$mod->available
|| !$section->visible
|| (isset($moduleobject['availabilitymessage']) && strlen($moduleobject['availabilitymessage']) > 1 )
) {
$moduleobject['extraclasses'] .= ' dimmed';
}
if ($mod->completionview == COMPLETION_VIEW_REQUIRED) {
// Auto completion with a view required.
$moduleobject['extraclasses'] .= " completeonview";
} else if ($mod->completion == COMPLETION_TRACKING_AUTOMATIC) {
// Auto completion with no view required (e.g. grade required).
$moduleobject['extraclasses'] .= " completeonevent";
} else if ($mod->completion == COMPLETION_TRACKING_MANUAL) {
$moduleobject['extraclasses'] .= " completeonmanual";
}
if ($this->isediting) {
$moduleobject['cmmove'] = course_get_cm_move($mod, $section->section);
$editactions = $this->tiles_get_cm_edit_actions($mod, $section->section);
if (isset($editactions['groupsseparate'])
|| isset($editactions['groupsvisible']) || isset($editactions['groupsnone'])) {
$moduleobject['extraclasses'] .= " margin-rt";
// We need to change the right margin in CSS if the edit menu contains a separate groups item.
}
$moduleobject['cmeditmenu'] = $this->courserenderer->course_section_cm_edit_actions($editactions, $mod);
$moduleobject['cmeditmenu'] .= $mod->afterediticons;
if (!$this->treat_as_label($mod)) {
if (!$mod->visible || !$section->visible) {
$attr = array('class' => 'dimmed');
} else {
$attr = null;
}
$moduleobject['modtitle_inplaceeditable'] = array(
"displayvalue" => \html_writer::link($mod->url, $mod->get_formatted_name(), $attr),
"value" => $mod->name,
"itemid" => $mod->id,
"component" => "core_course",
"itemtype" => "activityname",
"edithint" => get_string('edit'),
"editlabel" => get_string('newactivityname') . $mod->name,
"type" => "text",
"options" => "",
"linkeverything" => 0
);
}
}
if ($mod->modname == 'folder') {
// Folders set to display inline will not work this theme.
// This is not a very elegant solution, but it will ensure that the URL is correctly shown.
// If the user is editing it will change the format of the folder.
// It will show on a separate page, and alert the editing user as to what it has done.
$moduleobject['url'] = new \moodle_url('/mod/folder/view.php', array('id' => $mod->id));
if ($PAGE->user_is_editing()) {
$folder = $DB->get_record('folder', array('id' => $mod->instance));
if ($folder->display == FOLDER_DISPLAY_INLINE) {
$DB->set_field('folder', 'display', FOLDER_DISPLAY_PAGE, array('id' => $folder->id));
\core\notification::info(
get_string('folderdisplayerror', 'format_tiles', $moduleobject['url']->out())
);
rebuild_course_cache($mod->course, true);
}
};
}
if ($mod->modname == 'url') {
$url = $DB->get_record('url', array('id' => $mod->instance), '*', MUST_EXIST);
$modifiedvideourl = $this->check_modify_embedded_url($url->externalurl);
if ($url->display == RESOURCELIB_DISPLAY_POPUP) {
$moduleobject['pluginfileUrl'] = $url->externalurl;
$moduleobject['extraclasses'] .= ' urlpopup';
} else if ($url->display == RESOURCELIB_DISPLAY_EMBED) {
// We need a secondary URL to show under the embed window so users can click it if embed doesn't work.
// We will also use it to redirect mobile users to YouTube or wherever since embed wont work well for them.
$moduleobject['secondaryurl'] = $url->externalurl;
if (array_search('url', $this->usemodalsforcoursemodules['resources']) !== false) {
if ($modifiedvideourl) {
$moduleobject['pluginfileUrl'] = $modifiedvideourl;
} else {
$moduleobject['pluginfileUrl'] = $url->externalurl;
}
$moduleobject['launchtype'] = 'url-modal';
}
} else if ($url->display == RESOURCELIB_DISPLAY_AUTO) {
require_once("$CFG->dirroot/mod/url/locallib.php");
// TODO modify this later to treat embed as launch modal.
$treataspopup = [
RESOURCELIB_DISPLAY_EMBED,
RESOURCELIB_DISPLAY_FRAME,
RESOURCELIB_DISPLAY_NEW,
RESOURCELIB_DISPLAY_DOWNLOAD,
RESOURCELIB_DISPLAY_POPUP
];
if (array_search(url_get_final_display_type($url), $treataspopup) !== false) {
$moduleobject['pluginfileUrl'] = $url->externalurl;
$moduleobject['extraclasses'] .= ' urlpopup';
}
}
if ($modifiedvideourl) {
// Even though it's really a URL activity, display it as "video" activity with video icon.
if ($this->courseformatoptions['courseusesubtiles']) {
$moduleobject['extraclasses'] .= ' video';
$moduleobject['modnameDisplay'] = get_string('displaytitle_mod_mp4', 'format_tiles');
} else {
$moduleobject['iconurl'] = $output->image_url('play-circle-solid', 'format_tiles');
}
}
} else {
// This produces lots of unnecessary code for URL, trying to get around redirect, but we don't need it.
$moduleobject['onclick'] = $mod->onclick;
}
// Now completion information for the individual course module.
$completion = $mod->completion && $this->completioninfo && $this->completioninfo->is_enabled($mod) && $mod->available;
if ($completion) {
// Add completion icon to the course module if appropriate.
$moduleobject['completionInUseForCm'] = true;
$completiondata = $this->completioninfo->get_data($mod, true);
$moduleobject['completionstate'] = $completiondata->completionstate;
$moduleobject['completionicon'] = 'n'; // Not yet complete i.e. grey check in circle.
$moduleobject['completionstateInverse'] = $completiondata->completionstate == 1 ? 0 : 1;
if ($mod->completion == COMPLETION_TRACKING_MANUAL) {
$moduleobject['completionIsManual'] = 1;
switch ($completiondata->completionstate) {
case COMPLETION_INCOMPLETE:
$moduleobject['completionstring'] = get_string('togglecompletion', 'format_tiles');
break;
case COMPLETION_COMPLETE:
$moduleobject['completionstring'] = get_string('togglecompletion', 'format_tiles');
$moduleobject['completionicon'] = 'y'; // Green check in circle.
break;
}
} else { // Automatic.
switch ($completiondata->completionstate) {
case COMPLETION_INCOMPLETE:
$moduleobject['completionstring'] = get_string('complete-n-auto', 'format_tiles');
break;
case COMPLETION_COMPLETE:
$moduleobject['completionstring'] = get_string('complete-y-auto', 'format_tiles');
$moduleobject['completionicon'] = 'y'; // Green check in circle.
break;
case COMPLETION_COMPLETE_PASS:
$moduleobject['completionstring'] = get_string('completion-pass', 'core_completion', $mod->name);
$moduleobject['completionicon'] = 'y'; // Green check in circle.
break;
case COMPLETION_COMPLETE_FAIL:
$moduleobject['completionstring'] = get_string('completion-fail', 'core_completion', $mod->name);
$moduleobject['completionicon'] = 'fail'; // Red cross in circle.
break;
}
}
}
return $moduleobject;
}
/**
* Get resource file type e.g. 'doc' from the icon URL e.g. 'document-24.png'
* Not ideal but we already have icon name so it's efficient
* Adapted from Snap theme
* @see mod_displayname() which gets the display name for the type
*
* @param \cm_info $mod the mod info object we are checking
* @return string the type e.g. 'doc'
*/
private function get_resource_filetype(\cm_info $mod) {
if ($mod->modname === 'resource') {
$matches = array();
preg_match('#/(\w+)-#', $mod->icon, $matches);
$filetype = $matches[1];
$extensions = array(
'powerpoint' => 'ppt',
'document' => 'doc',
'spreadsheet' => 'xls',
'archive' => 'zip',
'pdf' => 'pdf',
'mp3' => 'mp3',
'mpeg' => 'mp4',
'jpeg' => 'jpeg',
'text' => 'txt',
'html' => 'html'
);
if (in_array($filetype, array_keys($extensions))) {
return $extensions[$filetype];
}
}
return '';
}
/**
* Adapted from mod/resource/view.php
* @param \cm_info $cm the course module object
* @return string url for file
* @throws \coding_exception
* @throws \dml_exception
*/
private function plugin_file_url($cm) {
global $DB, $CFG;
$context = \context_module::instance($cm->id);
$resource = $DB->get_record('resource', array('id' => $cm->instance), '*', MUST_EXIST);
$fs = get_file_storage();
$files = $fs->get_area_files(
$context->id, 'mod_resource', 'content', 0, 'sortorder DESC, id ASC', false
);
if (count($files) >= 1 ) {
$file = reset($files);
unset($files);
$resource->mainfile = $file->get_filename();
return $CFG->wwwroot . '/pluginfile.php/' . $context->id . '/mod_resource/content/'
. $resource->revision . $file->get_filepath() . rawurlencode($file->get_filename());
}
return '';
}
/**
* Get the display name for each module or resource type
* from the modname, to be displayed at the top of each tile
* e.g. 'mod/lti' => 'External Tool' 'mod/resource','xls' = "Spreadsheet'
* Once we have it , store it in instance var e.g. to avoid repeated check of 'pdf'
* @param string $modname the name of the module e.g. 'resource'
* @param string|null $resourcetype if this is a resource, the specific type eg. 'xls' or 'pdf'
* @return string to be displayed on tile
* @see get_resource_filetype()
* @throws \coding_exception
*/
private function mod_displayname($modname, $resourcetype = null) {
if ($modname == 'resource') {
if (isset($this->resourcedisplaynames[$resourcetype])) {
return $this->resourcedisplaynames[$resourcetype];
} else if (get_string_manager()->string_exists('displaytitle_mod_' . $resourcetype, 'format_tiles')) {
$str = get_string('displaytitle_mod_' . $resourcetype, 'format_tiles');
$this->resourcedisplaynames[$resourcetype] = $str;
return $str;
} else {
$str = get_string('other', 'format_tiles');
$this->resourcedisplaynames[$resourcetype] = $str;
return $str;
}
} else {
return get_string('modulename', 'mod_' . $modname);
}
}
/**
* For the legacy navigation arrows, establish the id of the next and previous sections
* @param int $currentsectionnum the id of the section we are in
* @return array previous and next ids
*/
private function get_previous_next_section_ids($currentsectionnum) {
$visiblesectionnums = [];
$currentsectionarrayindex = -1;
foreach ($this->modinfo->get_section_info_all() as $section) {
if ($section->uservisible) {
$visiblesectionnums[] = $section->section;
if ($section->section == $currentsectionnum) {
$currentsectionarrayindex = $section->section;
}
}
}
if ($currentsectionarrayindex == 0) {
$previous = 0; // There is no previous.
} else {
$previous = $visiblesectionnums[$currentsectionarrayindex - 1];
}
if ($currentsectionarrayindex == count($visiblesectionnums) - 1) {
$next = 0; // There is no next.
} else {
$next = $visiblesectionnums[$currentsectionarrayindex + 1];
}
return array('previous' => $previous, 'next' => $next);
}
/**
* Prepare the data required to render a progress indicator (.e. 2/3 items complete)
* to be shown on the tile or as an overall course progress indicator
* @param int $numcomplete how many items are complete
* @param int $numoutof how many items are available for completion
* @param boolean $aspercent should we show the indicator as a percentage or numeric
* @param boolean $isoverall whether this is an overall course completion indicator
* @return array data for output template
*/
private function completion_indicator($numcomplete, $numoutof, $aspercent, $isoverall) {
$percentcomplete = $numoutof == 0 ? 0 : round(($numcomplete / $numoutof) * 100, 0);
$progressdata = array(
'numComplete' => $numcomplete,
'numOutOf' => $numoutof,
'percent' => $percentcomplete,
'isComplete' => $numcomplete > 0 && $numcomplete == $numoutof ? 1 : 0,
'isOverall' => $isoverall,
);
if ($aspercent && $numcomplete != $numoutof) {
// Percent in circle.
$progressdata['showAsPercent'] = true;
$circumference = 106.8;
$progressdata['percentCircumf'] = $circumference;
$progressdata['percentOffset'] = round(((100 - $percentcomplete) / 100) * $circumference, 0);
}
$progressdata['isSingleDigit'] = $percentcomplete < 10 ? true : false; // Position single digit in centre of circle.
return $progressdata;
}
/**
* The menu to edit a course module is generated by
* @see \core_course_renderer::course_section_cm_edit_actions()
* but its format/content are not ideal for tiles
* So before we call here we adapt the menu items to make
* them more compatible with this format
* @param \cm_info $mod the course module object
* @param int $sectionnum the id of the section number we are in
* @return array the amended actions
* @throws \coding_exception
* @throws \dml_exception
* @throws \moodle_exception
*/
private function tiles_get_cm_edit_actions($mod, $sectionnum) {
// First get the standard list of actions from course/lib.
// Only use the indent action if course is not using subtiles.
$indent = ! $this->courseformatoptions['courseusesubtiles'] ? $mod->indent : -1;
$actions = course_get_cm_edit_actions($mod, $indent, $sectionnum);
if ($mod->modname === "label") {
if (get_config('format_tiles', 'allowlabelconversion' )
&& has_capability('mod/page:addinstance', $this->coursecontext)
&& has_capability('moodle/course:manageactivities', $this->coursecontext)) {
$converttext = get_string('converttopage', 'format_tiles');
$actions['labelconvert'] = new \action_menu_link_secondary(
new \moodle_url(
'/course/view.php', array(
'id' => $mod->course,
'section' => $sectionnum,
'labelconvert' => $mod->id,
'sesskey' => sesskey()
)
),
new \pix_icon('random', $converttext, 'format_tiles'),
$converttext,
array('class' => 'editing_labelconvert ', 'data-action' => 'labelconvert',
'data-keepopen' => true, 'data-sectionreturn' => $sectionnum)
);
}
}
// Otherwise proceed to adapt the standard items to this format.
foreach ($actions as $actionname => $action) {
$actionstomodify = ['hide', 'show', 'duplicate', 'groupsseparate', 'groupsvisible', 'groupsnone', 'stealth'];
if (!$this->treat_as_label($mod) && array_search($actionname, $actionstomodify) !== false) {
// For non labels, we don't want core JS to be used to hide/show etc when these menu items are used.
// Core converts the cm HTML to the standard activity display format (not subtile).
// Instead we want to use our own JS to render the new cm adding 'tiles-' to the start of data-action.
// E.g. tiles-show will prevent core JS running and allow our custom JS to run instead.
// (The core JS is in core_course/actions::editModule (actions.js).
// Note 'stealth' action can only be available if site admin has allowed stealth activities.
$action->attributes['data-action'] = "tiles-" . $action->attributes['data-action'];
$action->attributes['data-cmid'] = $mod->id;
}
if (get_class($action) == 'action_menu_link_primary') {
// We don't want items to be displayed as "action_menu_link_primary" in this format.
// E.g. separate groups item would be if we left it as is.
// So make a secondary menu item instead and replace it for the primary one.
$action = new \action_menu_link_secondary(
$action->url,
$action->icon,
$action->text,
$action->attributes
);
// And we don't want clicking them to trigger core JS calls.
$action->attributes['data-action'] = "tiles-" . $action->attributes['data-action'];
}
// We want to truncate if too long for this format.
$containsbracketat = strpos($action->text, '(');
if ($containsbracketat !== false) {
// Not much room in the drop down so truncate after open bracket e.g. "Separate Groups (Click to change)".
$action->text = substr($action->text, 0, $containsbracketat - 1);
}
}
return $actions;
}
/**
* We want to treat label and plugins that behave like labels as labels.
* E.g. we don't render them as subtiles but show their content directly on page.
* This includes plugins like mod_customlabel and mod_unilabel.
* @param \cm_info $mod the course module.
* @return bool whether it's to be treated as a label or not.
*/
private function treat_as_label($mod) {
return array_search($mod->modname, $this->format->labellikecoursemods) !== false;
}
/**
* Should a given section be shown as expanded or not?
* Only editors see sections expanded like this - students use AJAX expanding.
* @param int $sectionnum the id of the section (id not section number)
* @return bool whether it should be shown as expanded.
* @throws \coding_exception
*/
private function is_section_editing_expanded($sectionnum) {
global $SESSION;
if (!$this->isediting) {
return false;
}
if (isset($SESSION->editing_last_edited_section)
&& $SESSION->editing_last_edited_section == $this->course->id . "-" . $sectionnum) {
return true;
} else if (optional_param('expand', 0, PARAM_INT) == $sectionnum) {
// User is clicking to expand one section.
return true;
} else if (optional_param('expanded', 0, PARAM_INT) == 1) {
// User is clicking to expand all sections.
return true;
} else if (isset($SESSION->editing_all_sections_expanded_course)
&& $SESSION->editing_all_sections_expanded_course == $this->course->id) {
// User has previously expanded all sections for this course and we are remembering for this session.
return true;
}
return false;
}
/**
* If the URL is a YouTube or Vimeo URL etc, make some adjustments for embedding.
* Teacher probably used standard watch URL so fix it if so.
* @param string $originalurl
* @return string|boolean string the URL if it was en embed video URL, false if not.
*/
private function check_modify_embedded_url($originalurl) {
$modifiedurl = $originalurl;
// Remove http:// or https:// etc.
$removeprefixes = ["http://www.", "https://www.", "http://", "https://"];
foreach ($removeprefixes as $prefix) {
$modifiedurl = str_replace($prefix, "", $modifiedurl);
}
$modifiedurl = explode("/", $modifiedurl);
$domain = $modifiedurl[0];
if (strpos($domain, "youtube.") === 0 && isset($modifiedurl[1])
&& strpos($modifiedurl[1], 'watch?v=') === 0) {
// We have a YouTube watch URL.
return str_replace('/watch?v=', '/embed/', $originalurl);
} else if (strpos($domain, "vimeo.") === 0
&& isset($modifiedurl[1]) && is_numeric($modifiedurl[1])) {
// We have a Vimeo URL.
// Change https://vimeo.com/12345678 format to https://player.vimeo.com/video/12345678.
return "https://player.vimeo.com/video/" . $modifiedurl[1];
}
return false;
}
}