Commit 0d8b6a69 authored by sam marshall's avatar sam marshall
Browse files

lib MDL-25981 Improved modinfo

This commit:
a) moves modinfo code into new library modinfolib.php
b) uses classes instead of stdClass objects, allowing a huge amount of documentation (and IDE completion)
c) adds hooks so that plugins other than forum can display messages like forum's 'unread', and plugins other than label can display html (apart from/as well as their view.php link) on the course view page
d) removes current hacks for forum and label (mainly in print_section but also across the code), replacing with new 'content' and similar variables [this is the reason for the changes in blocks, etc]
e) reduces size of modinfo in database (only when rebuilt) by excluding empty fields

The change is intended to be backward compatible and does not affect the format of modinfo in database.
parent 8cdc85ac
......@@ -27,7 +27,8 @@ class block_activity_modules extends block_list {
$archetypes = array();
foreach($modinfo->cms as $cm) {
if (!$cm->uservisible or $cm->modname === 'label') {
// Exclude activities which are not visible or have no link (=label)
if (!$cm->uservisible or !$cm->has_view()) {
continue;
}
if (array_key_exists($cm->modname, $modfullnames)) {
......
......@@ -40,21 +40,19 @@ class block_site_main_menu extends block_list {
if (!$cm->uservisible) {
continue;
}
if ($cm->modname == 'label') {
$this->content->items[] = format_text($cm->extra, FORMAT_HTML, $options);
list($content, $instancename) =
get_print_section_cm_text($cm, $course);
if (!($url = $cm->get_url())) {
$this->content->items[] = $content;
$this->content->icons[] = '';
} else {
$linkcss = $cm->visible ? '' : ' class="dimmed" ';
$instancename = format_string($cm->name, true, $course->id);
//Accessibility: incidental image - should be empty Alt text
if (!empty($cm->icon)) {
$icon = $OUTPUT->pix_url($cm->icon);
} else {
$icon = $OUTPUT->pix_url('icon', $cm->modname);
}
$icon = '<img src="'.$icon.'" class="icon" alt="" />&nbsp;';
$icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />&nbsp;';
$this->content->items[] = '<a title="'.$cm->modplural.'" '.$linkcss.' '.$cm->extra.
' href="'.$CFG->wwwroot.'/mod/'.$cm->modname.'/view.php?id='.$cm->id.'">'.$icon.$instancename.'</a>';
' href="' . $url . '">' . $icon . $instancename . '</a>';
}
}
}
......@@ -114,28 +112,18 @@ class block_site_main_menu extends block_list {
'<img style="height:16px; width:80px; border:0px" src="'.$OUTPUT->pix_url('movehere') . '" alt="'.$strmovehere.'" /></a>';
$this->content->icons[] = '';
}
$instancename = $modinfo->cms[$modnumber]->name;
$instancename = format_string($instancename, true, $course->id);
list($content, $instancename) =
get_print_section_cm_text($modinfo->cms[$modnumber], $course);
$linkcss = $mod->visible ? '' : ' class="dimmed" ';
if (!empty($modinfo->cms[$modnumber]->extra)) {
$extra = $modinfo->cms[$modnumber]->extra;
} else {
$extra = '';
}
if (!empty($modinfo->cms[$modnumber]->icon)) {
$icon = $OUTPUT->pix_url($modinfo->cms[$modnumber]->icon);
} else {
$icon = $OUTPUT->pix_url('icon', $mod->modname);
}
if ($mod->modname == 'label') {
$this->content->items[] = format_text($extra, FORMAT_HTML,$options).$editbuttons;
if (!($url = $mod->get_url())) {
$this->content->items[] = $content . $editbuttons;
$this->content->icons[] = '';
} else {
//Accessibility: incidental image - should be empty Alt text
$icon = '<img src="'.$icon.'" class="icon" alt="" />&nbsp;';
$this->content->items[] = '<a title="'.$mod->modfullname.'" '.$linkcss.' '.$extra.
' href="'.$CFG->wwwroot.'/mod/'.$mod->modname.'/view.php?id='.$mod->id.'">'.$icon.$instancename.'</a>'.$editbuttons;
$icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />&nbsp;';
$this->content->items[] = '<a title="' . $mod->modfullname . '" ' . $linkcss . ' ' . $mod->extra .
' href="' . $url . '">' . $icon . $instancename . '</a>' . $editbuttons;
}
}
}
......
......@@ -42,21 +42,19 @@ class block_social_activities extends block_list {
if (!$cm->uservisible) {
continue;
}
if ($cm->modname == 'label') {
$this->content->items[] = format_text($cm->extra, FORMAT_HTML, $options);
list($content, $instancename) =
get_print_section_cm_text($cm, $course);
if (!($url = $cm->get_url())) {
$this->content->items[] = $content;
$this->content->icons[] = '';
} else {
$linkcss = $cm->visible ? '' : ' class="dimmed" ';
$instancename = format_string($cm->name, true, $course->id);
//Accessibility: incidental image - should be empty Alt text
if (!empty($cm->icon)) {
$icon = $OUTPUT->pix_url($cm->icon);
} else {
$icon = $OUTPUT->pix_url('icon', $cm->modname);
}
$icon = '<img src="'.$icon.'" class="icon" alt="" />&nbsp;';
$icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />&nbsp;';
$this->content->items[] = '<a title="'.$cm->modplural.'" '.$linkcss.' '.$cm->extra.
' href="'.$CFG->wwwroot.'/mod/'.$cm->modname.'/view.php?id='.$cm->id.'">'.$icon.$instancename.'</a>';
' href="' . $url . '">' . $icon . $instancename . '</a>';
}
}
}
......@@ -123,28 +121,19 @@ class block_social_activities extends block_list {
'<img style="height:16px; width:80px; border:0px" src="'.$OUTPUT->pix_url('movehere') . '" alt="'.$strmovehere.'" /></a>';
$this->content->icons[] = '';
}
$instancename = $modinfo->cms[$modnumber]->name;
$instancename = format_string($instancename, true, $course->id);
list($content, $instancename) =
get_print_section_cm_text($modinfo->cms[$modnumber], $course);
$linkcss = $mod->visible ? '' : ' class="dimmed" ';
if (!empty($modinfo->cms[$modnumber]->extra)) {
$extra = $modinfo->cms[$modnumber]->extra;
} else {
$extra = '';
}
if (!empty($modinfo->cms[$modnumber]->icon)) {
$icon = $OUTPUT->pix_url($modinfo->cms[$modnumber]->icon);
} else {
$icon = $OUTPUT->pix_url('icon', $mod->modname);
}
if ($mod->modname == 'label') {
$this->content->items[] = format_text($extra, FORMAT_HTML, $options).$editbuttons;
if (!($url = $mod->get_url())) {
$this->content->items[] = $content . $editbuttons;
$this->content->icons[] = '';
} else {
//Accessibility: incidental image - should be empty Alt text
$icon = '<img src="'.$icon.'" class="icon" alt="" />&nbsp;';
$this->content->items[] = '<a title="'.$mod->modfullname.'" '.$linkcss.' '.$extra.
' href="'.$CFG->wwwroot.'/mod/'.$mod->modname.'/view.php?id='.$mod->id.'">'.$icon.$instancename.'</a>'.$editbuttons;
$icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />&nbsp;';
$this->content->items[] = '<a title="' . $mod->modfullname . '" ' . $linkcss . ' ' . $mod->extra .
' href="' . $url . '">' . $icon . $instancename . '</a>' . $editbuttons;
}
}
}
......
......@@ -48,10 +48,6 @@ define('FIRSTUSEDEXCELROW', 3);
define('MOD_CLASS_ACTIVITY', 0);
define('MOD_CLASS_RESOURCE', 1);
if (!defined('MAX_MODINFO_CACHE_SIZE')) {
define('MAX_MODINFO_CACHE_SIZE', 10);
}
function make_log_url($module, $url) {
switch ($module) {
case 'course':
......@@ -959,6 +955,9 @@ function print_recent_activity($course) {
}
$info = explode(' ', $log->info);
// note: in most cases I replaced hardcoding of label with use of
// $cm->has_view() but it was not possible to do this here because
// we don't necessarily have the $cm for it
if ($info[0] == 'label') { // Labels are ignored in recent activity
continue;
}
......@@ -1112,9 +1111,6 @@ function get_array_of_activities($courseid) {
if (function_exists($functionname)) {
if ($info = $functionname($rawmods[$seq])) {
if (!empty($info->extra)) {
$mod[$seq]->extra = $info->extra;
}
if (!empty($info->icon)) {
$mod[$seq]->icon = $info->icon;
}
......@@ -1124,11 +1120,45 @@ function get_array_of_activities($courseid) {
if (!empty($info->name)) {
$mod[$seq]->name = $info->name;
}
if ($info instanceof cached_cm_info) {
// When using cached_cm_info you can include three new fields
// that aren't available for legacy code
if (!empty($info->content)) {
$mod[$seq]->content = $info->content;
}
if (!empty($info->extraclasses)) {
$mod[$seq]->extraclasses = $info->extraclasses;
}
if (!empty($info->onclick)) {
$mod[$seq]->onclick = $info->onclick;
}
if (!empty($info->customdata)) {
$mod[$seq]->customdata = $info->customdata;
}
} else {
// When using a stdclass, the (horrible) deprecated ->extra field
// is available for BC
if (!empty($info->extra)) {
$mod[$seq]->extra = $info->extra;
}
}
}
}
if (!isset($mod[$seq]->name)) {
$mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance));
}
// Minimise the database size by unsetting default options when they are
// 'empty'. This list corresponds to code in the cm_info constructor.
foreach(array('idnumber', 'groupmode', 'groupingid', 'groupmembersonly',
'indent', 'completion', 'extra', 'extraclasses', 'onclick', 'content',
'icon', 'iconcomponent', 'customdata', 'availablefrom', 'availableuntil',
'conditionscompletion', 'conditionsgrade') as $property) {
if (property_exists($mod[$seq], $property) &&
empty($mod[$seq]->{$property})) {
unset($mod[$seq]->{$property});
}
}
}
}
}
......@@ -1250,6 +1280,44 @@ function set_section_visible($courseid, $sectionnumber, $visibility) {
}
}
/**
* Obtains shared data that is used in print_section when displaying a
* course-module entry.
*
* Calls format_text or format_string as appropriate, and obtains the correct icon.
*
* This data is also used in other areas of the code.
* @param cm_info $cm Course-module data (must come from get_fast_modinfo)
* @param object $course Moodle course object
* @return array An array with the following values in this order:
* $content (optional extra content for after link),
* $instancename (text of link)
*/
function get_print_section_cm_text(cm_info $cm, $course) {
global $OUTPUT;
// Get course context
$coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
// Get content from modinfo if specified. Content displays either
// in addition to the standard link (below), or replaces it if
// the link is turned off by setting ->url to null.
if (($content = $cm->get_content()) !== '') {
$labelformatoptions = new stdClass();
$labelformatoptions->noclean = true;
$labelformatoptions->overflowdiv = true;
$labelformatoptions->context = $coursecontext;
$content = format_text($content, FORMAT_HTML, $labelformatoptions);
} else {
$content = '';
}
$stringoptions = new stdClass;
$stringoptions->context = $coursecontext;
$instancename = format_string($cm->name, true, $stringoptions);
return array($content, $instancename);
}
/**
* Prints a section full of activity modules
*/
......@@ -1265,8 +1333,8 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
static $strmovehere;
static $strmovefull;
static $strunreadpostsone;
static $usetracking;
static $groupings;
static $modulenames;
if (!isset($initialised)) {
$groupbuttons = ($course->groupmode or (!$course->groupmodeforce));
......@@ -1277,18 +1345,12 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
$strmovehere = get_string("movehere");
$strmovefull = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'"));
}
include_once($CFG->dirroot.'/mod/forum/lib.php');
if ($usetracking = forum_tp_can_track_forums()) {
$strunreadpostsone = get_string('unreadpostsone', 'forum');
}
$modulenames = array();
$initialised = true;
}
$labelformatoptions = new stdClass();
$labelformatoptions->noclean = true;
$labelformatoptions->overflowdiv = true;
$tl = textlib_get_instance();
/// Casting $course->modinfo to string prevents one notice when the field is null
$modinfo = get_fast_modinfo($course);
$completioninfo = new completion_info($course);
......@@ -1304,6 +1366,9 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
continue;
}
/**
* @var cm_info
*/
$mod = $mods[$modnumber];
if ($ismoving and $mod->id == $USER->activitycopy) {
......@@ -1341,6 +1406,11 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
}
}
if (!isset($modulenames[$mod->modname])) {
$modulenames[$mod->modname] = get_string('modulename', $mod->modname);
}
$modulename = $modulenames[$mod->modname];
// In some cases the activity is visible to user, but it is
// dimmed. This is done if viewhiddenactivities is true and if:
// 1. the activity is not visible, or
......@@ -1366,6 +1436,10 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
$liclasses[] = 'activity';
$liclasses[] = $mod->modname;
$liclasses[] = 'modtype_'.$mod->modname;
$extraclasses = $mod->get_extra_classes();
if ($extraclasses) {
$liclasses = array_merge($liclasses, explode(' ', $extraclasses));
}
echo html_writer::start_tag('li', array('class'=>join(' ', $liclasses), 'id'=>'module-'.$modnumber));
if ($ismoving) {
echo '<a title="'.$strmovefull.'"'.
......@@ -1384,105 +1458,122 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
}
echo html_writer::start_tag('div', array('class'=>join(' ', $classes)));
$extra = '';
if (!empty($modinfo->cms[$modnumber]->extra)) {
$extra = $modinfo->cms[$modnumber]->extra;
// Get data about this course-module
list($content, $instancename) =
get_print_section_cm_text($modinfo->cms[$modnumber], $course);
//Accessibility: for files get description via icon, this is very ugly hack!
$altname = '';
$altname = $mod->modfullname;
if (!empty($customicon)) {
$archetype = plugin_supports('mod', $mod->modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
if ($archetype == MOD_ARCHETYPE_RESOURCE) {
$mimetype = mimeinfo_from_icon('type', $customicon);
$altname = get_mimetype_description($mimetype);
}
}
// Avoid unnecessary duplication: if e.g. a forum name already
// includes the word forum (or Forum, etc) then it is unhelpful
// to include that in the accessible description that is added.
if (false !== strpos($tl->strtolower($instancename),
$tl->strtolower($altname))) {
$altname = '';
}
// File type after name, for alphabetic lists (screen reader).
if ($altname) {
$altname = get_accesshide(' '.$altname);
}
if ($mod->modname == "label") {
if ($accessiblebutdim || !$mod->uservisible) {
echo '<div class="dimmed_text"><span class="accesshide">'.
get_string('hiddenfromstudents').'</span>';
// We may be displaying this just in order to show information
// about visibility, without the actual link
$contentpart = '';
if ($mod->uservisible) {
// Nope - in this case the link is fully working for user
$linkclasses = '';
$textclasses = '';
if ($accessiblebutdim) {
$linkclasses .= ' dimmed';
$textclasses .= ' dimmed_text';
$accesstext = '<span class="accesshide">'.
get_string('hiddenfromstudents').': </span>';
} else {
echo '<div>';
$accesstext = '';
}
echo format_text($extra, FORMAT_HTML, $labelformatoptions);
echo "</div>";
if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
if (!isset($groupings)) {
$groupings = groups_get_all_groupings($course->id);
}
echo " <span class=\"groupinglabel\">(".format_string($groupings[$mod->groupingid]->name).')</span>';
if ($linkclasses) {
$linkcss = 'class="' . trim($linkclasses) . '" ';
} else {
$linkcss = '';
}
if ($textclasses) {
$textcss = 'class="' . trim($textclasses) . '" ';
} else {
$textcss = '';
}
} else { // Normal activity
$instancename = format_string($modinfo->cms[$modnumber]->name, true, $course->id);
// Get on-click attribute value if specified
$onclick = $mod->get_on_click();
if ($onclick) {
$onclick = ' onclick="' . $onclick . '"';
}
$customicon = $modinfo->cms[$modnumber]->icon;
if (!empty($customicon)) {
if (substr($customicon, 0, 4) === 'mod/') {
list($modname, $iconname) = explode('/', substr($customicon, 4), 2);
$icon = $OUTPUT->pix_url($iconname, $modname);
} else {
$icon = $OUTPUT->pix_url($customicon);
if ($url = $mod->get_url()) {
// Display link itself
echo '<a ' . $linkcss . $mod->extra . $onclick .
' href="' . $url . '"><img src="' . $mod->get_icon_url() .
'" class="activityicon" alt="' .
$modulename . '" /> ' .
$accesstext . '<span class="instancename">' .
$instancename . $altname . '</span></a>';
// If specified, display extra content after link
if ($content) {
$contentpart = '<div class="contentafterlink' .
trim($textclasses) . '">' . $content . '</div>';
}
} else {
$icon = $OUTPUT->pix_url('icon', $mod->modname);
// No link, so display only content
$contentpart = '<div ' . $textcss . $mod->extra . '>' .
$accesstext . $content . '</div>';
}
//Accessibility: for files get description via icon, this is very ugly hack!
$altname = '';
$altname = $mod->modfullname;
if (!empty($customicon)) {
$archetype = plugin_supports('mod', $mod->modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
if ($archetype == MOD_ARCHETYPE_RESOURCE) {
$mimetype = mimeinfo_from_icon('type', $customicon);
$altname = get_mimetype_description($mimetype);
if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
if (!isset($groupings)) {
$groupings = groups_get_all_groupings($course->id);
}
echo " <span class=\"groupinglabel\">(".format_string($groupings[$mod->groupingid]->name).')</span>';
}
// Avoid unnecessary duplication.
if (false !== stripos($instancename, $altname)) {
$altname = '';
}
// File type after name, for alphabetic lists (screen reader).
if ($altname) {
$altname = get_accesshide(' '.$altname);
} else {
$textclasses = $extraclasses;
$textclasses .= ' dimmed_text';
if ($textclasses) {
$textcss = 'class="' . trim($textclasses) . '" ';
} else {
$textcss = '';
}
$accesstext = '<span class="accesshide">' .
get_string('notavailableyet', 'condition') .
': </span>';
// We may be displaying this just in order to show information
// about visibility, without the actual link
if ($mod->uservisible) {
// Display normal module link
if (!$accessiblebutdim) {
$linkcss = '';
$accesstext ='';
} else {
$linkcss = ' class="dimmed" ';
$accesstext = '<span class="accesshide">'.
get_string('hiddenfromstudents').': </span>';
}
echo '<a '.$linkcss.' '.$extra.
' href="'.$CFG->wwwroot.'/mod/'.$mod->modname.'/view.php?id='.$mod->id.'">'.
'<img src="'.$icon.'" class="activityicon" alt="'.get_string('modulename',$mod->modname).'" /> '.
$accesstext.'<span class="instancename">'.$instancename.$altname.'</span></a>';
if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
if (!isset($groupings)) {
$groupings = groups_get_all_groupings($course->id);
}
echo " <span class=\"groupinglabel\">(".format_string($groupings[$mod->groupingid]->name).')</span>';
}
} else {
if ($url = $mod->get_url()) {
// Display greyed-out text of link
echo '<span class="dimmed_text" '.$extra.' ><span class="accesshide">'.
get_string('notavailableyet','condition').': </span>'.
'<img src="'.$icon.'" class="activityicon" alt="'.get_string('modulename', $mod->modname).'" /> <span>'.
$instancename.$altname.'</span></span>';
}
}
if ($usetracking && $mod->modname == 'forum') {
if ($unread = forum_tp_count_forum_unread_posts($mod, $course)) {
echo '<span class="unread"> <a href="'.$CFG->wwwroot.'/mod/forum/view.php?id='.$mod->id.'">';
if ($unread == 1) {
echo $strunreadpostsone;
} else {
print_string('unreadpostsnumber', 'forum', $unread);
}
echo '</a></span>';
echo '<div ' . $textcss . $mod->extra .
' >' . '<img src="' . $mod->get_icon_url() .
'" class="activityicon" alt="' .
$modulename .
'" /> <span>'. $instancename . $altname .
'</span></div>';
// Do not display content after link when it is greyed out like this.
} else {
// No link, so display only content (also greyed)
$contentpart = '<div ' . $textcss . $mod->extra . '>' .
$accesstext . $content . '</div>';
}
}
// Module can put text after the link (e.g. forum unread)
echo $mod->get_after_link();
if ($isediting) {
if ($groupbuttons and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
if (! $mod->groupmodelink = $groupbuttonslink) {
......@@ -1494,6 +1585,7 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
}
echo '&nbsp;&nbsp;';
echo make_editing_buttons($mod, $absolute, true, $mod->indent, $section->section);
echo $mod->get_after_edit_icons();
}
// Completion
......@@ -1566,6 +1658,9 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
}
}
// Display the content (if any) at this part of the html
echo $contentpart;
// Show availability information (for someone who isn't allowed to
// see the activity itself, or for staff)
if (!$mod->uservisible) {
......
......@@ -74,7 +74,7 @@
foreach ($modinfo->sections as $sectionnum=>$section) {
foreach ($section as $cmid) {
$cm = $modinfo->cms[$cmid];
if ($cm->modname == 'label') {
if (!$cm->has_view()) {
continue;
}
if (!$cm->uservisible) {
......
......@@ -63,7 +63,7 @@
$modinfo = get_fast_modinfo($course);
$modules = $DB->get_records_select('modules', "visible = 1 AND name <> 'label'", null, 'name ASC');
$modules = $DB->get_records_select('modules', "visible = 1", null, 'name ASC');
$instanceoptions = array();
foreach ($modules as $module) {
......@@ -72,8 +72,16 @@
}
$instances = array();
foreach ($modinfo->instances[$module->name] as $cm) {
// Skip modules such as label which do not actually have links;
// this means there's nothing to participate in
if (!$cm->has_view()) {
continue;
}
$instances[$cm->id] = format_string($cm->name);
}
if (count($instances) == 0) {
continue;
}
$instanceoptions[] = array(get_string('modulenameplural', $module->name)=>$instances);
}
......
......@@ -37,9 +37,6 @@ $allmodules = $DB->get_records('modules', array('visible'=>1));
$modules = array();
foreach ($allmodules as $key=>$module) {
$modname = $module->name;
if ($modname === 'label') {
continue;