lib MDL-25981 Improved modinfo
authorsam marshall <s.marshall@open.ac.uk>
Wed, 26 Jan 2011 10:29:16 +0000 (10:29 +0000)
committersam marshall <s.marshall@open.ac.uk>
Wed, 26 Jan 2011 10:29:16 +0000 (10:29 +0000)
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.

14 files changed:
blocks/activity_modules/block_activity_modules.php
blocks/site_main_menu/block_site_main_menu.php
blocks/social_activities/block_social_activities.php
course/lib.php
course/report/outline/index.php
course/report/participation/index.php
course/resources.php
lib/modinfolib.php [new file with mode: 0644]
lib/moodlelib.php
lib/navigationlib.php
lib/setup.php
lib/weblib.php
mod/forum/lib.php
mod/label/lib.php

index adff582..c44ceb4 100644 (file)
@@ -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)) {
index 3e139ac..d3434f8 100644 (file)
@@ -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;
                     }
                 }
             }
index bcf7d7b..886e888 100644 (file)
@@ -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;
                     }
                 }
             }
index 28983a9..7cb7d08 100644 (file)
@@ -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) {
index 5c151df..ad4ef64 100644 (file)
@@ -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) {
index bb460b8..2ad66bb 100644 (file)
@@ -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) {
         }
         $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);
     }
 
index b98ced9..abf50b2 100644 (file)
@@ -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;
-    }
     $libfile = "$CFG->dirroot/mod/$modname/lib.php";
     if (!file_exists($libfile)) {
         continue;
@@ -80,6 +77,10 @@ foreach ($modinfo->cms as $cm) {
     if (!array_key_exists($cm->modname, $modules)) {
         continue;
     }
+    if (!$cm->has_view()) {
+        // Exclude label and similar
+        continue;
+    }
     $cms[$cm->id] = $cm;
     $resources[$cm->modname][] = $cm->instance;
 }
diff --git a/lib/modinfolib.php b/lib/modinfolib.php
new file mode 100644 (file)
index 0000000..d5d169e
--- /dev/null
@@ -0,0 +1,1092 @@
+<?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/>.
+
+/**
+ * modinfolib.php - Functions/classes relating to cached information about module instances on
+ * a course.
+ * @package    core
+ * @subpackage lib
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     sam marshall
+ */
+
+
+// Maximum number of modinfo items to keep in memory cache. Do not increase this to a large
+// number because:
+// a) modinfo can be big (megabyte range) for some courses
+// b) performance of cache will deteriorate if there are very many items in it
+if (!defined('MAX_MODINFO_CACHE_SIZE')) {
+    define('MAX_MODINFO_CACHE_SIZE', 10);
+}
+
+
+/**
+ * Information about a course that is cached in the course table 'modinfo' field (and then in
+ * memory) in order to reduce the need for other database queries.
+ *
+ * This includes information about the course-modules and the sections on the course. It can also
+ * include dynamic data that has been updated for the current user.
+ */
+class course_modinfo {
+    // For convenience we store the course object here as it is needed in other parts of code
+    private $course;
+
+    // Existing data fields
+    ///////////////////////
+
+    // These are public for backward compatibility. Note: it is not possible to retain BC
+    // using PHP magic get methods because behaviour is different with regard to empty().
+
+    /**
+     * Course ID
+     * @var int
+     * @deprecated For new code, use get_course_id instead.
+     */
+    public $courseid;
+
+    /**
+     * User ID
+     * @var int
+     * @deprecated For new code, use get_user_id instead.
+     */
+    public $userid;
+
+    /**
+     * Array from int (section num, e.g. 0) => array of int (course-module id); this list only
+     * includes sections that actually contain at least one course-module
+     * @var array
+     * @deprecated For new code, use get_sections instead
+     */
+    public $sections;
+
+    /**
+     * Array from int (cm id) => cm_info object
+     * @var array
+     * @deprecated For new code, use get_cms or get_cm instead.
+     */
+    public $cms;
+
+    /**
+     * Array from string (modname) => int (instance id) => cm_info object
+     * @var array
+     * @deprecated For new code, use get_instances or get_instances_of instead.
+     */
+    public $instances;
+
+    /**
+     * Groups that the current user belongs to. This value is usually not available (set to null)
+     * unless the course has activities set to groupmembersonly. When set, it is an array of
+     * grouping id => array of group id => group id. Includes grouping id 0 for 'all groups'.
+     * @var array
+     * @deprecated Don't use this! For new code, use get_groups.
+     */
+    public $groups;
+
+    // Get methods for data
+    ///////////////////////
+
+    /**
+     * @return object Moodle course object that was used to construct this data
+     */
+    public function get_course() {
+        return $this->course;
+    }
+
+    /**
+     * @return int Course ID
+     */
+    public function get_course_id() {
+        return $this->courseid;
+    }
+
+    /**
+     * @return int User ID
+     */
+    public function get_user_id() {
+        return $this->userid;
+    }
+
+    /**
+     * @return array Array from section number (e.g. 0) to array of course-module IDs in that
+     *   section; this only includes sections that contain at least one course-module
+     */
+    public function get_sections() {
+        return $this->sections;
+    }
+
+    /**
+     * @return array Array from course-module instance to cm_info object within this course, in
+     *   order of appearance
+     */
+    public function get_cms() {
+        return $this->cms;
+    }
+
+    /**
+     * Obtains a single course-module object (for a course-module that is on this course).
+     * @param int $cmid Course-module ID
+     * @return cm_info Information about that course-module
+     * @throws moodle_exception If the course-module does not exist
+     */
+    public function get_cm($cmid) {
+        if (empty($this->cms[$cmid])) {
+            throw new moodle_exception('invalidcoursemodule', 'error');
+        }
+        return $this->cms[$cmid];
+    }
+
+    /**
+     * Obtains all module instances on this course.
+     * @return array Array from module name => array from instance id => cm_info
+     */
+    public function get_instances() {
+        return $this->instances;
+    }
+
+    /**
+     * Obtains all instances of a particular module on this course.
+     * @param $modname Name of module (not full frankenstyle) e.g. 'label'
+     * @return array Array from instance id => cm_info for modules on this course; empty if none
+     */
+    public function get_instances_of($modname) {
+        if (empty($this->instances[$modname])) {
+            return array();
+        }
+        return $this->instances[$modname];
+    }
+
+    /**
+     * Returns groups that the current user belongs to on the course. Note: If not already
+     * available, this may make a database query.
+     * @param int $groupingid Grouping ID or 0 (default) for all groups
+     * @return array Array of int (group id) => int (same group id again); empty array if none
+     */
+    public function get_groups($groupingid=0) {
+        if (is_null($this->groups)) {
+            // NOTE: Performance could be improved here. The system caches user groups
+            // in $USER->groupmember[$courseid] => array of groupid=>groupid. Unfortunately this
+            // structure does not include grouping information. It probably could be changed to
+            // do so, without a significant performance hit on login, thus saving this one query
+            // each request.
+            $this->groups = groups_get_user_groups($this->courseid, $this->userid);
+        }
+        if (!isset($this->groups[$groupingid])) {
+            return array();
+        }
+        return $this->groups[$groupingid];
+    }
+
+    /**
+     * Constructs based on course.
+     * Note: This constructor should not usually be called directly.
+     * Use get_fast_modinfo($course) instead as this maintains a cache.
+     * @param object $course Moodle course object, which may include modinfo
+     * @param int $userid User ID
+     */
+    public function __construct($course, $userid) {
+        global $CFG;
+
+        // Set initial values
+        $this->courseid = $course->id;
+        $this->userid = $userid;
+        $this->sections = array();
+        $this->cms = array();
+        $this->instances = array();
+        $this->groups = null;
+        $this->course = $course;
+
+        // Check modinfo field is set. If not, build and load it.
+        if (empty($course->modinfo)) {
+            rebuild_course_cache($course->id);
+            $course->modinfo = $DB->get_field('course', 'modinfo', array('id'=>$course->id));
+        }
+
+        // Load modinfo field into memory as PHP object and check it's valid
+        $info = unserialize($course->modinfo);
+        if (!is_array($info)) {
+            // hmm, something is wrong - lets try to fix it
+            rebuild_course_cache($course->id);
+            $course->modinfo = $DB->get_field('course', 'modinfo', array('id'=>$course->id));
+            $info = unserialize($course->modinfo);
+            if (!is_array($info)) {
+                // If it still fails, abort
+                debugging('Problem with "modinfo" data for this course');
+                return;
+            }
+        }
+
+        // If we haven't already preloaded contexts for the course, do it now
+        preload_course_contexts($course->id);
+
+        // Loop through each piece of module data, constructing it
+        $modexists = array();
+        foreach ($info as $mod) {
+            if (empty($mod->name)) {
+                // something is wrong here
+                continue;
+            }
+
+            // Skip modules which don't exist
+            if (empty($modexists[$mod->mod])) {
+                if (!file_exists("$CFG->dirroot/mod/$mod->mod/lib.php")) {
+                    continue;
+                }
+                $modexists[$mod->mod] = true;
+            }
+
+            // Construct info for this module
+            $cm = new cm_info($this, $course, $mod, $info);
+
+            // Store module in instances and cms array
+            if (!isset($this->instances[$cm->modname])) {
+                $this->instances[$cm->modname] = array();
+            }
+            $this->instances[$cm->modname][$cm->instance] = $cm;
+            $this->cms[$cm->id] = $cm;
+
+            // Reconstruct sections. This works because modules are stored in order
+            if (!isset($this->sections[$cm->sectionnum])) {
+                $this->sections[$cm->sectionnum] = array();
+            }
+            $this->sections[$cm->sectionnum][] = $cm->id;
+        }
+
+        // We need at least 'dynamic' data from each course-module (this is basically the remaining
+        // data which was always present in previous version of get_fast_modinfo, so it's required
+        // for BC). Creating it in a second pass is necessary because obtain_dynamic_data sometimes
+        // needs to be able to refer to a 'complete' (with basic data) modinfo.
+        foreach ($this->cms as $cm) {
+            $cm->obtain_dynamic_data();
+        }
+    }
+}
+
+
+/**
+ * Data about a single module on a course. This contains most of the fields in the course_modules
+ * table, plus additional data when required.
+ *
+ * This object has many public fields; code should treat all these fields as read-only and set
+ * data only using the supplied set functions. Setting the fields directly is not supported
+ * and may cause problems later.
+ */
+class cm_info {
+    /**
+     * State: Only basic data from modinfo cache is available.
+     */
+    const STATE_BASIC = 0;
+
+    /**
+     * State: Dynamic data is available too.
+     */
+    const STATE_DYNAMIC = 1;
+
+    /**
+     * State: View data (for course page) is available.
+     */
+    const STATE_VIEW = 2;
+
+    /**
+     * Parent object
+     * @var course_modinfo
+     */
+    private $modinfo;
+
+    /**
+     * Level of information stored inside this object (STATE_xx constant)
+     * @var int
+     */
+    private $state;
+
+    // Existing data fields
+    ///////////////////////
+
+    /**
+     * Course-module ID - from course_modules table
+     * @var int
+     */
+    public $id;
+
+    /**
+     * Module instance (ID within module table) - from course_modules table
+     * @var int
+     */
+    public $instance;
+
+    /**
+     * Course ID - from course_modules table
+     * @var int
+     */
+    public $course;
+
+    /**
+     * 'ID number' from course-modules table (arbitrary text set by user) - from
+     * course_modules table
+     * @var string
+     */
+    public $idnumber;
+
+    /**
+     * Visible setting (0 or 1; if this is 0, students cannot see/access the activity)  - from
+     * course_modules table
+     * @var int
+     */
+    public $visible;
+
+    /**
+     * Group mode (one of the constants NONE, SEPARATEGROUPS, or VISIBLEGROUPS) - from
+     * course_modules table
+     * @var int
+     */
+    public $groupmode;
+
+    /**
+     * Grouping ID (0 = all groupings)
+     * @var int
+     */
+    public $groupingid;
+
+    /**
+     * Group members only (if set to 1, only members of a suitable group see this link on the
+     * course page; 0 = everyone sees it even if they don't belong to a suitable group)  - from
+     * course_modules table
+     * @var int
+     */
+    public $groupmembersonly;
+
+    /**
+     * Indent level on course page (0 = no indent) - from course_modules table
+     * @var int
+     */
+    public $indent;
+
+    /**
+     * Activity completion setting for this activity, COMPLETION_TRACKING_xx constant - from
+     * course_modules table
+     * @var int
+     */
+    public $completion;
+
+    /**
+     * Available date for this activity (0 if not set, or set to seconds since epoch; before this
+     * date, activity does not display to students) - from course_modules table
+     * @var int
+     */
+    public $availablefrom;
+
+    /**
+     * Available until date for this activity (0 if not set, or set to seconds since epoch; from
+     * this date, activity does not display to students) - from course_modules table
+     * @var int
+     */
+    public $availableuntil;
+
+    /**
+     * When activity is unavailable, this field controls whether it is shown to students (0 =
+     * hide completely, 1 = show greyed out with information about when it will be available) -
+     * from course_modules table
+     * @var int
+     */
+    public $showavailability;
+
+    /**
+     * Extra HTML that is put in an unhelpful part of the HTML when displaying this module in
+     * course page - from cached data in modinfo field
+     * @deprecated This is crazy, don't use it. Replaced by ->extraclasses and ->onclick
+     * @var string
+     */
+    public $extra;
+
+    /**
+     * Name of icon to use - from cached data in modinfo field
+     * @var string
+     */
+    public $icon;
+
+    /**
+     * Component that contains icon - from cached data in modinfo field
+     * @var string
+     */
+    public $iconcomponent;
+
+    /**
+     * Name of module e.g. 'forum' (this is the same name as the module's main database
+     * table) - from cached data in modinfo field
+     * @var string
+     */
+    public $modname;
+
+    /**
+     * Name of module instance for display on page e.g. 'General discussion forum' - from cached
+     * data in modinfo field
+     * @var string
+     */
+    public $name;
+
+    /**
+     * Section number that this course-module is in (section 0 = above the calendar, section 1
+     * = week/topic 1, etc) - from cached data in modinfo field
+     * @var string
+     */
+    public $sectionnum;
+
+    /**
+     * Availability conditions for this course-module based on the completion of other
+     * course-modules (array from other course-module id to required completion state for that
+     * module) - from cached data in modinfo field
+     * @var array
+     */
+    public $conditionscompletion;
+
+    /**
+     * Availability conditions for this course-module based on course grades (array from
+     * grade item id to object with ->min, ->max fields) - from cached data in modinfo field
+     * @var array
+     */
+    public $conditionsgrade;
+
+    /**
+     * Plural name of module type, e.g. 'Forums' - from lang file
+     * @deprecated Do not use this value (you can obtain it by calling get_string instead); it
+     *   will be removed in a future version (see later TODO in this file)
+     * @var string
+     */
+    public $modplural;
+
+    /**
+     * True if this course-module is available to students i.e. if all availability conditions
+     * are met - obtained dynamically
+     * @var bool
+     */
+    public $available;
+
+    /**
+     * If course-module is not available to students, this string gives information about
+     * availability which can be displayed to students and/or staff (e.g. 'Available from 3
+     * January 2010') for display on main page - obtained dynamically
+     * @var string
+     */
+    public $availableinfo;
+
+    /**
+     * True if this course-module is available to the CURRENT user (for example, if current user
+     * has viewhiddenactivities capability, they can access the course-module even if it is not
+     * visible or not available, so this would be true in that case)
+     * @var bool
+     */
+    public $uservisible;
+
+    // New data available only via functions
+    ////////////////////////////////////////
+
+    /**
+     * @var moodle_url
+     */
+    private $url;
+
+    /**
+     * @var string
+     */
+    private $content;
+
+    /**
+     * @var string
+     */
+    private $extraclasses;
+
+    /**
+     * @var string
+     */
+    private $onclick;
+
+    /**
+     * @var mixed
+     */
+    private $customdata;
+
+    /**
+     * @var string
+     */
+    private $afterlink;
+
+    /**
+     * @var string
+     */
+    private $afterediticons;
+
+    /**
+     * @return bool True if this module has a 'view' page that should be linked to in navigation
+     *   etc (note: modules may still have a view.php file, but return false if this is not
+     *   intended to be linked to from 'normal' parts of the interface; this is what label does).
+     */
+    public function has_view() {
+        return !is_null($this->url);
+    }
+
+    /**
+     * @return moodle_url URL to link to for this module, or null if it doesn't have a view page
+     */
+    public function get_url() {
+        return $this->url;
+    }
+
+    /**
+     * Obtains content to display on main (view) page.
+     * Note: Will collect view data, if not already obtained.
+     * @return string Content to display on main page below link, or empty string if none
+     */
+    public function get_content() {
+        $this->obtain_view_data();
+        return $this->content;
+    }
+
+    /**
+     * Note: Will collect view data, if not already obtained.
+     * @return string Extra CSS classes to add to html output for this activity on main page
+     */
+    public function get_extra_classes() {
+        $this->obtain_view_data();
+        return $this->extraclasses;
+    }
+
+    /**
+     * @return string Content of HTML on-click attribute. This string will be used literally
+     * as a string so should be pre-escaped.
+     */
+    public function get_on_click() {
+        // Does not need view data; may be used by navigation
+        return $this->onclick;
+    }
+    /**
+     * @return mixed Optional custom data stored in modinfo cache for this activity, or null if none
+     */
+    public function get_custom_data() {
+        return $this->customdata;
+    }
+
+    /**
+     * Note: Will collect view data, if not already obtained.
+                * @return string Extra HTML code to display after link
+     */
+    public function get_after_link() {
+        $this->obtain_view_data();
+        return $this->afterlink;
+    }
+
+    /**
+     * Note: Will collect view data, if not already obtained.
+     * @return string Extra HTML code to display after editing icons (e.g. more icons)
+     */
+    public function get_after_edit_icons() {
+        $this->obtain_view_data();
+        return $this->afterediticons;
+    }
+
+    /**
+     * @param moodle_core_renderer $output Output render to use, or null for default (global)
+     * @return moodle_url Icon URL for a suitable icon to put beside this cm
+     */
+    public function get_icon_url($output = null) {
+        global $OUTPUT;
+        if (!$output) {
+            $output = $OUTPUT;
+        }
+        if (!empty($this->icon)) {
+            if (substr($this->icon, 0, 4) === 'mod/') {
+                list($modname, $iconname) = explode('/', substr($this->icon, 4), 2);
+                $icon = $output->pix_url($iconname, $modname);
+            } else {
+                if (!empty($this->iconcomponent)) {
+                    // Icon  has specified component
+                    $icon = $output->pix_url($this->icon, $this->iconcomponent);
+                } else {
+                    // Icon does not have specified component, use default
+                    $icon = $output->pix_url($this->icon);
+                }
+            }
+        } else {
+            $icon = $output->pix_url('icon', $this->modname);
+        }
+        return $icon;
+    }
+
+    /**
+     * @return course_modinfo Modinfo object that this came from
+     */
+    public function get_modinfo() {
+        return $this->modinfo;
+    }
+
+    /**
+     * @return object Moodle course object that was used to construct this data
+     */
+    public function get_course() {
+        return $this->modinfo->get_course();
+    }
+
+    // Set functions
+    ////////////////
+
+    /**
+     * Sets content to display on course view page below link (if present).
+     * @param string $content New content as HTML string (empty string if none)
+     * @return void
+     */
+    public function set_content($content) {
+        $this->content = $content;
+    }
+
+    /**
+     * Sets extra classes to include in CSS.
+     * @param string $extraclasses Extra classes (empty string if none)
+     * @return void
+     */
+    public function set_extra_classes($extraclasses) {
+        $this->extraclasses = $extraclasses;
+    }
+
+    /**
+     * Sets value of on-click attribute for JavaScript.
+     * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
+     * @param string $onclick New onclick attribute which should be HTML-escaped
+     *   (empty string if none)
+     * @return void
+     */
+    public function set_on_click($onclick) {
+        $this->check_not_view_only();
+        $this->onclick = $onclick;
+    }
+
+    /**
+     * Sets HTML that displays after link on course view page.
+     * @param string $afterlink HTML string (empty string if none)
+     * @return void
+     */
+    public function set_after_link($afterlink) {
+        $this->afterlink = $afterlink;
+    }
+
+    /**
+     * Sets HTML that displays after edit icons on course view page.
+     * @param string $afterediticons HTML string (empty string if none)
+     * @return void
+     */
+    public function set_after_edit_icons($afterediticons) {
+        $this->afterediticons = $afterediticons;
+    }
+
+    /**
+     * Changes the name (text of link) for this module instance.
+     * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
+     * @param string $name Name of activity / link text
+     * @return void
+     */
+    public function set_name($name) {
+        $this->update_user_visible();
+        $this->name = $name;
+    }
+
+    /**
+     * Turns off the view link for this module instance.
+     * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
+     * @return void
+     */
+    public function set_no_view_link() {
+        $this->check_not_view_only();
+        $url = null;
+    }
+
+    /**
+     * Sets the 'uservisible' flag. This can be used (by setting false) to prevent access and
+     * display of this module link for the current user.
+     * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
+     * @param bool $uservisible
+     * @return void
+     */
+    public function set_user_visible($uservisible) {
+        $this->check_not_view_only();
+        $this->uservisible = $uservisible;
+    }
+
+    /**
+     * Sets the 'available' flag and related details. This flag is normally used to make
+     * course modules unavailable until a certain date or condition is met. (When a course
+     * module is unavailable, it is still visible to users who have viewhiddenactivities
+     * permission.)
+     *
+     * When this is function is called, user-visible status is recalculated automatically.
+     *
+     * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
+     * @param bool $available False if this item is not 'available'
+     * @param int $showavailability 0 = do not show this item at all if it's not available,
+     *   1 = show this item greyed out with the following message
+     * @param string $availableinfo Information about why this is not available which displays
+     *   to those who have viewhiddenactivities, and to everyone if showavailability is set;
+     *   note that this function replaces the existing data (if any)
+     * @return void
+     */
+    public function set_available($available, $showavailability=0, $availableinfo='') {
+        $this->check_not_view_only();
+        $this->available = $available;
+        $this->showavailability = $showavailability;
+        $this->availableinfo = $availableinfo;
+        $this->update_user_visible();
+    }
+
+    /**
+     * Some set functions can only be called from _cm_info_dynamic and not _cm_info_view.
+     * This is because they may affect parts of this object which are used on pages other
+     * than the view page (e.g. in the navigation block, or when checking access on
+     * module pages).
+     * @return void
+     */
+    private function check_not_view_only() {
+        if ($this->state >= self::STATE_DYNAMIC) {
+            throw new coding_exception('Cannot set this data from _cm_info_view because it may ' .
+                    'affect other pages as well as view');
+        }
+    }
+
+    /**
+     * Constructor should not be called directly; use get_fast_modinfo.
+     * @param course_modinfo $modinfo Parent object
+     * @param object $course Course row
+     * @param object $mod Module object from the modinfo field of course table
+     * @param object $info Entire object from modinfo field of course table
+     */
+    public function __construct(course_modinfo $modinfo, $course, $mod, $info) {
+        global $CFG;
+        $this->modinfo = $modinfo;
+
+        $this->id               = $mod->cm;
+        $this->instance         = $mod->id;
+        $this->course           = $course->id;
+        $this->modname          = $mod->mod;
+        $this->idnumber         = isset($mod->idnumber) ? $mod->idnumber : '';
+        $this->name             = $mod->name;
+        $this->visible          = $mod->visible;
+        $this->sectionnum       = $mod->section;
+        $this->groupmode        = isset($mod->groupmode) ? $mod->groupmode : 0;
+        $this->groupingid       = isset($mod->groupingid) ? $mod->groupingid : 0;
+        $this->groupmembersonly = isset($mod->groupmembersonly) ? $mod->groupmembersonly : 0;
+        $this->indent           = isset($mod->indent) ? $mod->indent : 0;
+        $this->completion       = isset($mod->completion) ? $mod->completion : 0;
+        $this->extra            = isset($mod->extra) ? $mod->extra : '';
+        $this->extraclasses     = isset($mod->extraclasses) ? $mod->extraclasses : '';
+        $this->onclick          = isset($mod->onclick) ? $mod->onclick : '';
+        $this->content          = isset($mod->content) ? $mod->content : '';
+        $this->icon             = isset($mod->icon) ? $mod->icon : '';
+        $this->iconcomponent    = isset($mod->iconcomponent) ? $mod->iconcomponent : '';
+        $this->customdata       = isset($mod->customdata) ? $mod->customdata : '';
+        $this->state = self::STATE_BASIC;
+
+        // This special case handles old label data. Labels used to use the 'name' field for
+        // content
+        if ($this->modname === 'label' && $this->content === '') {
+            $this->content = $this->extra;
+            $this->extra = '';
+        }
+
+        if (!empty($CFG->enableavailability)) {
+            // We must have completion information from modinfo. If it's not
+            // there, cache needs rebuilding
+            if (!isset($mod->showavailability)) {
+                throw new modinfo_rebuild_cache_exception(
+                        'enableavailability option was changed; rebuilding '.
+                        'cache for course ' . $course->id);
+            }
+            $this->showavailability = $mod->showavailability;
+            $this->availablefrom = isset($mod->availablefrom) ? $mod->availablefrom : 0;
+            $this->availableuntil = isset($mod->availableuntil) ? $mod->availableuntil : 0;
+            $this->conditionscompletion = isset($mod->conditionscompletion)
+                    ? $mod->conditionscompletion : array();
+            $this->conditionsgrade = isset($mod->conditionsgrade)
+                    ? $mod->conditionsgrade : array();
+        }
+
+        // Get module plural name.
+        // TODO This was a very old performance hack and should now be removed as the information
+        // certainly doesn't belong in modinfo. On a 'normal' page this is only used in the
+        // activity_modules block, so if it needs caching, it should be cached there.
+        static $modplurals;
+        if (!isset($modplurals[$this->modname])) {
+            $modplurals[$this->modname] = get_string('modulenameplural', $this->modname);
+        }
+        $this->modplural = $modplurals[$this->modname];
+
+        static $modviews;
+        if (!isset($modviews[$this->modname])) {
+            $modviews[$this->modname] = !plugin_supports('mod', $this->modname,
+                    FEATURE_NO_VIEW_LINK);
+        }
+        $this->url = $modviews[$this->modname]
+                ? new moodle_url('/mod/' . $this->modname . '/view.php', array('id'=>$this->id))
+                : null;
+    }
+
+    /**
+     * If dynamic data for this course-module is not yet available, gets it.
+     *
+     * This function is automatically called when constructing course_modinfo, so users don't
+     * need to call it.
+     *
+     * Dynamic data is data which does not come directly from the cache but is calculated at
+     * runtime based on the current user. Primarily this concerns whether the user can access
+     * the module or not.
+     *
+     * As part of this function, the module's _cm_info_dynamic function from its lib.php will
+     * be called (if it exists).
+     * @return void
+     */
+    public function obtain_dynamic_data() {
+        global $CFG;
+        if ($this->state >= self::STATE_DYNAMIC) {
+            return;
+        }
+        $userid = $this->modinfo->get_user_id();
+
+        if (!empty($CFG->enableavailability)) {
+            // Get availability information
+            $ci = new condition_info($this);
+            // Note that the modinfo currently available only includes minimal details (basic data)
+            // so passing it to this function is a bit dangerous as it would cause infinite
+            // recursion if it tried to get dynamic data, however we know that this function only
+            // uses basic data.
+            $this->available = $ci->is_available($this->availableinfo, true,
+                    $userid, $this->modinfo);
+        } else {
+            $this->available = true;
+        }
+
+        // Update visible state for current user
+        $this->update_user_visible();
+
+        // Let module make dynamic changes at this point
+        $this->call_mod_function('cm_info_dynamic');
+        $this->state = self::STATE_DYNAMIC;
+    }
+
+    /**
+     * Works out whether activity is visible *for current user* - if this is false, they
+     * aren't allowed to access it.
+     * @return void
+     */
+    private function update_user_visible() {
+        global $CFG;
+        $modcontext = get_context_instance(CONTEXT_MODULE, $this->id);
+        $userid = $this->modinfo->get_user_id();
+        $this->uservisible = true;
+        if ((!$this->visible or !$this->available) and
+                !has_capability('moodle/course:viewhiddenactivities', $modcontext, $userid)) {
+            // If the activity is hidden or unavailable, and you don't have viewhiddenactivities,
+            // set it so that user can't see or access it
+            $this->uservisible = false;
+        } else if (!empty($CFG->enablegroupmembersonly) and !empty($this->groupmembersonly)
+                and !has_capability('moodle/site:accessallgroups', $modcontext, $userid)) {
+            // If the activity has 'group members only' and you don't have accessallgroups...
+            $groups = $this->modinfo->get_groups();
+            if (empty($this->groups[$this->groupingid])) {
+                // ...and you don't belong to a group, then set it so you can't see/access it
+                $this->uservisible = false;
+            }
+        }
+    }
+
+    /**
+     * Calls a module function (if exists), passing in one parameter: this object.
+     * @param string $type Name of function e.g. if this is 'grooblezorb' and the modname is
+     *   'forum' then it will try to call 'mod_forum_grooblezorb' or 'forum_grooblezorb'
+     * @return void
+     */
+    private function call_mod_function($type) {
+        global $CFG;
+        $libfile = $CFG->dirroot . '/mod/' . $this->modname . '/lib.php';
+        if (file_exists($libfile)) {
+            include_once($libfile);
+            $function = 'mod_' . $this->modname . '_' . $type;
+            if (function_exists($function)) {
+                $function($this);
+            } else {
+                $function = $this->modname . '_' . $type;
+                if (function_exists($function)) {
+                    $function($this);
+                }
+            }
+        }
+    }
+
+    /**
+     * If view data for this course-module is not yet available, obtains it.
+     *
+     * This function is automatically called if any of the functions (marked) which require
+     * view data are called.
+     *
+     * View data is data which is needed only for displaying the course main page (& any similar
+     * functionality on other pages) but is not needed in general. Obtaining view data may have
+     * a performance cost.
+     *
+     * As part of this function, the module's _cm_info_view function from its lib.php will
+     * be called (if it exists).
+     * @return void
+     */
+    private function obtain_view_data() {
+        if ($this->state >= self::STATE_VIEW) {
+            return;
+        }
+
+        // Let module make changes at this point
+        $this->call_mod_function('cm_info_view');
+        $this->state = self::STATE_VIEW;
+    }
+}
+
+
+/**
+ * Special exception that may only be thrown within the constructor for course_modinfo to
+ * indicate that the cache needs to be rebuilt. Not for use anywhere else.
+ */
+class modinfo_rebuild_cache_exception extends coding_exception {
+    function __construct($why) {
+        // If it ever escapes, that's a code bug
+        parent::__construct('This exception should be caught by code', $why);
+    }
+}
+
+
+/**
+ * Returns reference to full info about modules in course (including visibility).
+ * Cached and as fast as possible (0 or 1 db query).
+ *
+ * @global object
+ * @global object
+ * @global moodle_database
+ * @uses MAX_MODINFO_CACHE_SIZE
+ * @param mixed $course object or 'reset' string to reset caches, modinfo may be updated in db
+ * @param int $userid Defaults to current user id
+ * @return course_modinfo Module information for course, or null if resetting
+ */
+function get_fast_modinfo(&$course, $userid=0) {
+    global $CFG, $USER, $DB;
+    require_once($CFG->dirroot.'/course/lib.php');
+
+    if (!empty($CFG->enableavailability)) {
+        require_once($CFG->libdir.'/conditionlib.php');
+    }
+
+    static $cache = array();
+
+    if ($course === 'reset') {
+        $cache = array();
+        return null;
+    }
+
+    if (empty($userid)) {
+        $userid = $USER->id;
+    }
+
+    if (array_key_exists($course->id, $cache) and $cache[$course->id]->userid == $userid) {
+        return $cache[$course->id];
+    }
+
+    if (!property_exists($course, 'modinfo')) {
+        debugging('Coding problem - missing course modinfo property in get_fast_modinfo() call');
+    }
+
+    unset($cache[$course->id]); // prevent potential reference problems when switching users
+
+    try {
+        $cache[$course->id] = new course_modinfo($course, $userid);
+    } catch (modinfo_rebuild_cache_exception $e) {
+        debugging($e->debuginfo);
+        rebuild_course_cache($course->id, true);
+        $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+        // This second time we don't catch the exception - if you request cache rebuild twice
+        // in a row, that's a bug => coding_exception
+        $cache[$course->id] = new course_modinfo($course, $userid);
+    }
+
+    // Ensure cache does not use too much RAM
+    if (count($cache) > MAX_MODINFO_CACHE_SIZE) {
+        reset($cache);
+        $key = key($cache);
+        unset($cache[$key]);
+    }
+
+    return $cache[$course->id];
+}
+
+
+/**
+ * Class that is the return value for the _get_coursemodule_info module API function.
+ *
+ * Note: For backward compatibility, you can also return a stdclass object from that function.
+ * The difference is that the stdclass object may contain an 'extra' field (deprecated because
+ * it was crazy, except for label which uses it differently). The stdclass object may not contain
+ * the new fields defined here (content, extraclasses, customdata).
+ */
+class cached_cm_info {
+    /**
+     * Name (text of link) for this activity; Leave unset to accept default name
+     * @var string
+     */
+    public $name;
+
+    /**
+     * Name of icon for this activity. Normally, this should be used together with $iconcomponent
+     * to define the icon, as per pix_url function.
+     * For backward compatibility, if this value is of the form 'mod/forum/icon' then an icon
+     * within that module will be used.
+     * @see cm_info::get_icon_url()
+     * @see renderer_base::pix_url()
+     * @var string
+     */
+    public $icon;
+
+    /**
+     * Component for icon for this activity, as per pix_url; leave blank to use default 'moodle'
+     * component
+     * @see renderer_base::pix_url()
+     * @var string
+     */
+    public $iconcomponent;
+
+    /**
+     * HTML content to be displayed on the main page below the link (if any) for this course-module
+     * @var string
+     */
+    public $content;
+
+    /**
+     * Custom data to be stored in modinfo for this activity; useful if there are cases when
+     * internal information for this activity type needs to be accessible from elsewhere on the
+     * course without making database queries. May be of any type but should be short.
+     * @var mixed
+     */
+    public $customdata;
+
+    /**
+     * Extra CSS class or classes to be added when this activity is displayed on the main page;
+     * space-separated string
+     * @var string
+     */
+    public $extraclasses;
+
+    /**
+     * Content of onclick JavaScript; escaped HTML to be inserted as attribute value
+     * @var string
+     */
+    public $onclick;
+}
\ No newline at end of file
index dd9bbef..643af07 100644 (file)
@@ -352,6 +352,8 @@ define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
 /** True if module has custom completion rules */
 define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
 
+/** True if module has no 'view' page (like label) */
+define('FEATURE_NO_VIEW_LINK', 'viewlink');
 /** True if module supports outcomes */
 define('FEATURE_IDNUMBER', 'idnumber');
 /** True if module supports groups */
@@ -2312,6 +2314,14 @@ function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $
             if ($cm->course != $course->id) {
                 throw new coding_exception('course and cm parameters in require_login() call do not match!!');
             }
+            // make sure we have a $cm from get_fast_modinfo as this contains activity access details
+            if ($cm instanceof cm_info) {
+                // note: nearly all pages call get_fast_modinfo anyway and it does not make any
+                // db queries so this is not really a performance concern, however it is obviously
+                // better if you use get_fast_modinfo to get the cm before calling this.
+                $modinfo = get_fast_modinfo($course);
+                $cm = $modinfo->get_cm($cm->id);
+            }
             $PAGE->set_cm($cm, $course); // set's up global $COURSE
             $PAGE->set_pagelayout('incourse');
         } else {
@@ -2583,47 +2593,15 @@ function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $
         }
     }
 
-    // test visibility
-    if ($cm && !$cm->visible && !has_capability('moodle/course:viewhiddenactivities', $cmcontext)) {
+    // Check visibility of activity to current user; includes visible flag, groupmembersonly,
+    // conditional availability, etc
+    if ($cm && !$cm->uservisible) {
         if ($preventredirect) {
             throw new require_login_exception('Activity is hidden');
         }
         redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
     }
 
-    // groupmembersonly access control
-    if (!empty($CFG->enablegroupmembersonly) and $cm and $cm->groupmembersonly and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
-        if (isguestuser() or !groups_has_membership($cm)) {
-            if ($preventredirect) {
-                throw new require_login_exception('Not member of a group');
-            }
-            print_error('groupmembersonlyerror', 'group', $CFG->wwwroot.'/course/view.php?id='.$cm->course);
-        }
-    }
-
-    // Conditional activity access control
-    if (!empty($CFG->enableavailability) and $cm) {
-        // TODO: this is going to work with login-as-user, sorry!
-        // We cache conditional access in session
-        if (!isset($SESSION->conditionaccessok)) {
-            $SESSION->conditionaccessok = array();
-        }
-        // If you have been allowed into the module once then you are allowed
-        // in for rest of session, no need to do conditional checks
-        if (!array_key_exists($cm->id, $SESSION->conditionaccessok)) {
-            // Get condition info (does a query for the availability table)
-            require_once($CFG->libdir.'/conditionlib.php');
-            $ci = new condition_info($cm, CONDITION_MISSING_EXTRATABLE);
-            // Check condition for user (this will do a query if the availability
-            // information depends on grade or completion information)
-            if ($ci->is_available($junk) || has_capability('moodle/course:viewhiddenactivities', $cmcontext)) {
-                $SESSION->conditionaccessok[$cm->id] = true;
-            } else {
-                print_error('activityiscurrentlyhidden');
-            }
-        }
-    }
-
     // Finally access granted, update lastaccess times
     user_accesstime_log($course->id);
 }
@@ -2674,16 +2652,29 @@ function require_logout() {
  */
 function require_course_login($courseorid, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
     global $CFG, $PAGE, $SITE;
+    $issite = (is_object($courseorid) and $courseorid->id == SITEID)
+          or (!is_object($courseorid) and $courseorid == SITEID);
+    if ($issite && !($cm instanceof cm_info)) {
+        // note: nearly all pages call get_fast_modinfo anyway and it does not make any
+        // db queries so this is not really a performance concern, however it is obviously
+        // better if you use get_fast_modinfo to get the cm before calling this.
+        if (is_object($courseorid)) {
+            $course = $courseorid;
+        } else {
+            $course = clone($SITE);
+        }
+        $modinfo = get_fast_modinfo($course);
+        $cm = $modinfo->get_cm($cm->id);
+    }
     if (!empty($CFG->forcelogin)) {
         // login required for both SITE and courses
         require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
 
-    } else if (!empty($cm) and !$cm->visible) {
+    } else if ($issite && !empty($cm) and !$cm->uservisible) {
         // always login for hidden activities
         require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
 
-    } else if ((is_object($courseorid) and $courseorid->id == SITEID)
-          or (!is_object($courseorid) and $courseorid == SITEID)) {
+    } else if ($issite) {
               //login for SITE not required
         if ($cm and empty($cm->visible)) {
             // hidden activities are not accessible without login
@@ -3006,207 +2997,6 @@ function reset_login_count() {
     $SESSION->logincount = 0;
 }
 
-/**
- * Returns reference to full info about modules in course (including visibility).
- * Cached and as fast as possible (0 or 1 db query).
- *
- * @global object
- * @global object
- * @global object
- * @uses CONTEXT_MODULE
- * @uses MAX_MODINFO_CACHE_SIZE
- * @param mixed $course object or 'reset' string to reset caches, modinfo may be updated in db
- * @param int $userid Defaults to current user id
- * @return mixed courseinfo object or nothing if resetting
- */
-function &get_fast_modinfo(&$course, $userid=0) {
-    global $CFG, $USER, $DB;
-    require_once($CFG->dirroot.'/course/lib.php');
-
-    if (!empty($CFG->enableavailability)) {
-        require_once($CFG->libdir.'/conditionlib.php');
-    }
-
-    static $cache = array();
-
-    if ($course === 'reset') {
-        $cache = array();
-        $nothing = null;
-        return $nothing; // we must return some reference
-    }
-
-    if (empty($userid)) {
-        $userid = $USER->id;
-    }
-
-    if (array_key_exists($course->id, $cache) and $cache[$course->id]->userid == $userid) {
-        return $cache[$course->id];
-    }
-
-    if (!property_exists($course, 'modinfo')) {
-        debugging('Coding problem - missing course modinfo property in get_fast_modinfo() call');
-    }
-
-    if (empty($course->modinfo)) {
-        // no modinfo yet - load it
-        rebuild_course_cache($course->id);
-        $course->modinfo = $DB->get_field('course', 'modinfo', array('id'=>$course->id));
-    }
-
-    $modinfo = new stdClass();
-    $modinfo->courseid  = $course->id;
-    $modinfo->userid    = $userid;
-    $modinfo->sections  = array();
-    $modinfo->cms       = array();
-    $modinfo->instances = array();
-    $modinfo->groups    = null; // loaded only when really needed - the only one db query
-
-    $info = unserialize($course->modinfo);
-    if (!is_array($info)) {
-        // hmm, something is wrong - lets try to fix it
-        rebuild_course_cache($course->id);
-        $course->modinfo = $DB->get_field('course', 'modinfo', array('id'=>$course->id));
-        $info = unserialize($course->modinfo);
-        if (!is_array($info)) {
-            return $modinfo;
-        }
-    }
-
-    if ($info) {
-        // detect if upgrade required
-        $first = reset($info);
-        if (!isset($first->id)) {
-            rebuild_course_cache($course->id);
-            $course->modinfo = $DB->get_field('course', 'modinfo', array('id'=>$course->id));
-            $info = unserialize($course->modinfo);
-            if (!is_array($info)) {
-                return $modinfo;
-            }
-        }
-    }
-
-    $modlurals = array();
-
-    // If we haven't already preloaded contexts for the course, do it now
-    preload_course_contexts($course->id);
-
-    foreach ($info as $mod) {
-        if (empty($mod->name)) {
-            // something is wrong here
-            continue;
-        }
-        // reconstruct minimalistic $cm
-        $cm = new stdClass();
-        $cm->id               = $mod->cm;
-        $cm->instance         = $mod->id;
-        $cm->course           = $course->id;
-        $cm->modname          = $mod->mod;
-        $cm->idnumber         = $mod->idnumber;
-        $cm->name             = $mod->name;
-        $cm->visible          = $mod->visible;
-        $cm->sectionnum       = $mod->section;
-        $cm->groupmode        = $mod->groupmode;
-        $cm->groupingid       = $mod->groupingid;
-        $cm->groupmembersonly = $mod->groupmembersonly;
-        $cm->indent           = $mod->indent;
-        $cm->completion       = $mod->completion;
-        $cm->extra            = isset($mod->extra) ? $mod->extra : '';
-        $cm->icon             = isset($mod->icon) ? $mod->icon : '';
-        $cm->iconcomponent    = isset($mod->iconcomponent) ? $mod->iconcomponent : '';
-        $cm->uservisible      = true;
-        if (!empty($CFG->enableavailability)) {
-            // We must have completion information from modinfo. If it's not
-            // there, cache needs rebuilding
-            if(!isset($mod->availablefrom)) {
-                debugging('enableavailability option was changed; rebuilding '.
-                    'cache for course '.$course->id);
-                rebuild_course_cache($course->id,true);
-                // Re-enter this routine to do it all properly
-                return get_fast_modinfo($course, $userid);
-            }
-            $cm->availablefrom    = $mod->availablefrom;
-            $cm->availableuntil   = $mod->availableuntil;
-            $cm->showavailability = $mod->showavailability;
-            $cm->conditionscompletion = $mod->conditionscompletion;
-            $cm->conditionsgrade  = $mod->conditionsgrade;
-        }
-
-        // preload long names plurals and also check module is installed properly
-        if (!isset($modlurals[$cm->modname])) {
-            if (!file_exists("$CFG->dirroot/mod/$cm->modname/lib.php")) {
-                continue;
-            }
-            $modlurals[$cm->modname] = get_string('modulenameplural', $cm->modname);
-        }
-        $cm->modplural = $modlurals[$cm->modname];
-        $modcontext = get_context_instance(CONTEXT_MODULE,$cm->id);
-
-        if (!empty($CFG->enableavailability)) {
-            // Unfortunately the next call really wants to call
-            // get_fast_modinfo, but that would be recursive, so we fake up a
-            // modinfo for it already
-            if (empty($minimalmodinfo)) { //TODO: this is suspicious (skodak)
-                $minimalmodinfo = new stdClass();
-                $minimalmodinfo->cms = array();
-                foreach($info as $mod) {
-                    if (empty($mod->name)) {
-                        // something is wrong here
-                        continue;
-                    }
-                    $minimalcm = new stdClass();
-                    $minimalcm->id = $mod->cm;
-                    $minimalcm->name = $mod->name;
-                    $minimalmodinfo->cms[$minimalcm->id]=$minimalcm;
-                }
-            }
-
-            // Get availability information
-            $ci = new condition_info($cm);
-            $cm->available = $ci->is_available($cm->availableinfo, true, $userid, $minimalmodinfo);
-        } else {
-            $cm->available = true;
-        }
-        if ((!$cm->visible or !$cm->available) and !has_capability('moodle/course:viewhiddenactivities', $modcontext, $userid)) {
-            $cm->uservisible = false;
-
-        } else if (!empty($CFG->enablegroupmembersonly) and !empty($cm->groupmembersonly)
-                and !has_capability('moodle/site:accessallgroups', $modcontext, $userid)) {
-            if (is_null($modinfo->groups)) {
-                $modinfo->groups = groups_get_user_groups($course->id, $userid);
-            }
-            if (empty($modinfo->groups[$cm->groupingid])) {
-                $cm->uservisible = false;
-            }
-        }
-
-        if (!isset($modinfo->instances[$cm->modname])) {
-            $modinfo->instances[$cm->modname] = array();
-        }
-        $modinfo->instances[$cm->modname][$cm->instance] =& $cm;
-        $modinfo->cms[$cm->id] =& $cm;
-
-        // reconstruct sections
-        if (!isset($modinfo->sections[$cm->sectionnum])) {
-            $modinfo->sections[$cm->sectionnum] = array();
-        }
-        $modinfo->sections[$cm->sectionnum][] = $cm->id;
-
-        unset($cm);
-    }
-
-    unset($cache[$course->id]); // prevent potential reference problems when switching users
-    $cache[$course->id] = $modinfo;
-
-    // Ensure cache does not use too much RAM
-    if (count($cache) > MAX_MODINFO_CACHE_SIZE) {
-        reset($cache);
-        $key = key($cache);
-        unset($cache[$key]);
-    }
-
-    return $cache[$course->id];
-}
-
 /**
  * Determines if the currently logged in user is in editing mode.
  * Note: originally this function had $userid parameter - it was not usable anyway
index 3a62e6a..df0cfde 100644 (file)
@@ -1026,10 +1026,14 @@ class global_navigation extends navigation_node {
                 if ($course->id !== SITEID) {
                     // Find the section for the $CM associated with the page and collect
                     // its section number.
-                    foreach ($sections as $section) {
-                        if ($section->id == $cm->section) {
-                            $cm->sectionnumber = $section->section;
-                            break;
+                    if (isset($cm->sectionnum)) {
+                        $cm->sectionnumber = $cm->sectionnum;
+                    } else {
+                        foreach ($sections as $section) {
+                            if ($section->id == $cm->section) {
+                                $cm->sectionnumber = $section->section;
+                                break;
+                            }
                         }
                     }
 
@@ -1429,10 +1433,10 @@ class global_navigation extends navigation_node {
      *
      * @param navigation_node $sectionnode
      * @param int $sectionnumber
-     * @param stdClass $modinfo Object returned from {@see get_fast_modinfo()}
+     * @param course_modinfo $modinfo Object returned from {@see get_fast_modinfo()}
      * @return array Array of activity nodes
      */
-    protected function load_section_activities(navigation_node $sectionnode, $sectionnumber, $modinfo) {
+    protected function load_section_activities(navigation_node $sectionnode, $sectionnumber, course_modinfo $modinfo) {
         if (!array_key_exists($sectionnumber, $modinfo->sections)) {
             return true;
         }
@@ -1449,11 +1453,12 @@ class global_navigation extends navigation_node {
             } else {
                 $icon = new pix_icon('icon', get_string('modulename', $cm->modname), $cm->modname);
             }
-            $url = new moodle_url('/mod/'.$cm->modname.'/view.php', array('id'=>$cm->id));
+            $url = $cm->get_url();
             $activitynode = $sectionnode->add(format_string($cm->name), $url, navigation_node::TYPE_ACTIVITY, null, $cm->id, $icon);
             $activitynode->title(get_string('modulename', $cm->modname));
             $activitynode->hidden = (!$cm->visible);
-            if ($cm->modname == 'label') {
+            if (!$url) {
+                // Do not show activities that don't have links!
                 $activitynode->display = false;
             } else if ($this->module_extends_navigation($cm->modname)) {
                 $activitynode->nodetype = navigation_node::NODETYPE_BRANCH;
@@ -1482,11 +1487,12 @@ class global_navigation extends navigation_node {
         } else {
             $icon = new pix_icon('icon', get_string('modulename', $cm->modname), $cm->modname);
         }
-        $url = new moodle_url('/mod/'.$cm->modname.'/view.php', array('id'=>$cm->id));
+        $url = $cm->get_url();
         $activitynode = $coursenode->add(format_string($cm->name), $url, navigation_node::TYPE_ACTIVITY, null, $cm->id, $icon);
         $activitynode->title(get_string('modulename', $cm->modname));
         $activitynode->hidden = (!$cm->visible);
-        if ($cm->modname == 'label') {
+        if (!$url) {
+            // Don't show activities that don't have links!
             $activitynode->display = false;
         } else if ($this->module_extends_navigation($cm->modname)) {
             $activitynode->nodetype = navigation_node::NODETYPE_BRANCH;
@@ -1509,7 +1515,7 @@ class global_navigation extends navigation_node {
      * @param navigation_node $activity
      * @return bool
      */
-    protected function load_activity(stdClass $cm, stdClass $course, navigation_node $activity) {
+    protected function load_activity($cm, stdClass $course, navigation_node $activity) {
         global $CFG, $DB;
 
         $activity->make_active();
index e677754..de6f9ff 100644 (file)
@@ -414,6 +414,7 @@ require_once($CFG->libdir .'/grouplib.php');        // Groups functions
 require_once($CFG->libdir .'/sessionlib.php');      // All session and cookie related stuff
 require_once($CFG->libdir .'/editorlib.php');       // All text editor related functions and classes
 require_once($CFG->libdir .'/messagelib.php');      // Messagelib functions
+require_once($CFG->libdir .'/modinfolib.php');      // Cached information on course-module instances
 
 // make sure PHP is not severly misconfigured
 setup_validate_php_configuration();
index 2537afc..45ac4e0 100644 (file)
@@ -2174,7 +2174,8 @@ function navmenulist($course, $sections, $modinfo, $strsection, $strjumpto, $wid
 
     $menu[] = '<ul class="navmenulist"><li class="jumpto section"><span>'.$strjumpto.'</span><ul>';
     foreach ($modinfo->cms as $mod) {
-        if ($mod->modname == 'label') {
+        if (!$mod->has_view()) {
+            // Don't show modules which you can't link to!
             continue;
         }
 
index 8ffaaa9..64f3cea 100644 (file)
@@ -7791,3 +7791,35 @@ class forum_existing_subscriber_selector extends forum_subscriber_selector_base
     }
 
 }
+
+/**
+ * Adds information about unread messages, that is only required for the course view page (and
+ * similar), to the course-module object.
+ * @param cm_info $cm Course-module object
+ */
+function forum_cm_info_view(cm_info $cm) {
+    global $CFG;
+
+    // Get tracking status (once per request)
+    static $initialised;
+    static $usetracking, $strunreadpostsone;
+    if (!isset($initialised)) {
+        if ($usetracking = forum_tp_can_track_forums()) {
+            $strunreadpostsone = get_string('unreadpostsone', 'forum');
+        }
+        $initialised = true;
+    }
+
+    if ($usetracking) {
+        if ($unread = forum_tp_count_forum_unread_posts($cm, $cm->get_course())) {
+            $out = '<span class="unread"> <a href="' . $cm->get_url() . '">';
+            if ($unread == 1) {
+                $out .= $strunreadpostsone;
+            } else {
+                $out .= get_string('unreadpostsnumber', 'forum', $unread);
+            }
+            $out .= '</a></span>';
+            $cm->set_after_link($out);
+        }
+    }
+}
index 1f77822..2e21e13 100644 (file)
@@ -209,6 +209,7 @@ function label_supports($feature) {
         case FEATURE_GRADE_OUTCOMES:          return false;
         case FEATURE_MOD_ARCHETYPE:           return MOD_ARCHETYPE_RESOURCE;
         case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_NO_VIEW_LINK:            return true;
 
         default: return null;
     }