MDL-37207 Add modchoosertoggle in renderer to allow themes to override
[moodle.git] / course / renderer.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Renderer for use with the course section and all the goodness that falls
20  * within it.
21  *
22  * This renderer should contain methods useful to courses, and categories.
23  *
24  * @package   moodlecore
25  * @copyright 2010 Sam Hemelryk
26  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
29 /**
30  * The core course renderer
31  *
32  * Can be retrieved with the following:
33  * $renderer = $PAGE->get_renderer('core','course');
34  */
35 class core_course_renderer extends plugin_renderer_base {
37     /**
38      * A cache of strings
39      * @var stdClass
40      */
41     protected $strings;
43     /**
44      * Override the constructor so that we can initialise the string cache
45      *
46      * @param moodle_page $page
47      * @param string $target
48      */
49     public function __construct(moodle_page $page, $target) {
50         $this->strings = new stdClass;
51         parent::__construct($page, $target);
52         $this->add_modchoosertoggle();
53     }
55     /**
56      * Adds the item in course settings navigation to toggle modchooser
57      *
58      * Theme can overwrite as an empty function to exclude it (for example if theme does not
59      * use modchooser at all)
60      */
61     protected function add_modchoosertoggle() {
62         global $CFG;
63         static $modchoosertoggleadded = false;
64         // Add the module chooser toggle to the course page
65         if ($modchoosertoggleadded || $this->page->state > moodle_page::STATE_PRINTING_HEADER ||
66                 $this->page->course->id == SITEID ||
67                 !($coursenode = $this->page->settingsnav->find('courseadmin', navigation_node::TYPE_COURSE))) {
68             // too late or we are on site page or we could not find the course settings node
69             return;
70         }
71         $modchoosertoggleadded = true;
72         if ($this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
73             // We are on the course page, retain the current page params e.g. section.
74             $modchoosertoggleurl = clone($this->page->url);
75         } else {
76             // Edit on the main course page.
77             $modchoosertoggleurl = new moodle_url('/course/view.php', array('id' => $this->page->course->id,
78                 'return' => $this->page->url->out_as_local_url(false)));
79         }
80         $modchoosertoggleurl->param('sesskey', sesskey());
81         if ($usemodchooser = get_user_preferences('usemodchooser', $CFG->modchooserdefault)) {
82             $modchoosertogglestring = get_string('modchooserdisable', 'moodle');
83             $modchoosertoggleurl->param('modchooser', 'off');
84         } else {
85             $modchoosertogglestring = get_string('modchooserenable', 'moodle');
86             $modchoosertoggleurl->param('modchooser', 'on');
87         }
88         $modchoosertoggle = navigation_node::create($modchoosertogglestring, $modchoosertoggleurl, navigation_node::TYPE_SETTING);
89         $coursenode->add_node($modchoosertoggle, 'editsettings');
90         $modchoosertoggle->add_class('modchoosertoggle');
91         $modchoosertoggle->add_class('visibleifjs');
92         user_preference_allow_ajax_update('usemodchooser', PARAM_BOOL);
93     }
95     /**
96      * Renders course info box.
97      *
98      * @param stdClass $course
99      * @return string
100      */
101     public function course_info_box(stdClass $course) {
102         global $CFG;
104         $context = context_course::instance($course->id);
106         $content = '';
107         $content .= $this->output->box_start('generalbox info');
109         $summary = file_rewrite_pluginfile_urls($course->summary, 'pluginfile.php', $context->id, 'course', 'summary', null);
110         $content .= format_text($summary, $course->summaryformat, array('overflowdiv'=>true), $course->id);
111         if (!empty($CFG->coursecontact)) {
112             $coursecontactroles = explode(',', $CFG->coursecontact);
113             foreach ($coursecontactroles as $roleid) {
114                 if ($users = get_role_users($roleid, $context, true)) {
115                     foreach ($users as $teacher) {
116                         $role = new stdClass();
117                         $role->id = $teacher->roleid;
118                         $role->name = $teacher->rolename;
119                         $role->shortname = $teacher->roleshortname;
120                         $role->coursealias = $teacher->rolecoursealias;
121                         $fullname = fullname($teacher, has_capability('moodle/site:viewfullnames', $context));
122                         $namesarray[] = role_get_name($role, $context).': <a href="'.$CFG->wwwroot.'/user/view.php?id='.
123                             $teacher->id.'&amp;course='.SITEID.'">'.$fullname.'</a>';
124                     }
125                 }
126             }
128             if (!empty($namesarray)) {
129                 $content .= "<ul class=\"teachers\">\n<li>";
130                 $content .= implode('</li><li>', $namesarray);
131                 $content .= "</li></ul>";
132             }
133         }
135         $content .= $this->output->box_end();
137         return $content;
138     }
140     /**
141      * Renderers a structured array of courses and categories into a nice
142      * XHTML tree structure.
143      *
144      * This method was designed initially to display the front page course/category
145      * combo view. The structure can be retrieved by get_course_category_tree()
146      *
147      * @param array $structure
148      * @return string
149      */
150     public function course_category_tree(array $structure) {
151         $this->strings->summary = get_string('summary');
153         // Generate an id and the required JS call to make this a nice widget
154         $id = html_writer::random_id('course_category_tree');
155         $this->page->requires->js_init_call('M.util.init_toggle_class_on_click', array($id, '.category.with_children .category_label', 'collapsed', '.category.with_children'));
157         // Start content generation
158         $content = html_writer::start_tag('div', array('class'=>'course_category_tree', 'id'=>$id));
159         foreach ($structure as $category) {
160             $content .= $this->course_category_tree_category($category);
161         }
162         $content .= html_writer::start_tag('div', array('class'=>'controls'));
163         $content .= html_writer::tag('div', get_string('collapseall'), array('class'=>'addtoall expandall'));
164         $content .= html_writer::tag('div', get_string('expandall'), array('class'=>'removefromall collapseall'));
165         $content .= html_writer::end_tag('div');
166         $content .= html_writer::end_tag('div');
168         // Return the course category tree HTML
169         return $content;
170     }
172     /**
173      * Renderers a category for use with course_category_tree
174      *
175      * @param array $category
176      * @param int $depth
177      * @return string
178      */
179     protected function course_category_tree_category(stdClass $category, $depth=1) {
180         $content = '';
181         $hassubcategories = (isset($category->categories) && count($category->categories)>0);
182         $hascourses = (isset($category->courses) && count($category->courses)>0);
183         $classes = array('category');
184         if ($category->parent != 0) {
185             $classes[] = 'subcategory';
186         }
187         if (empty($category->visible)) {
188             $classes[] = 'dimmed_category';
189         }
190         if ($hassubcategories || $hascourses) {
191             $classes[] = 'with_children';
192             if ($depth > 1) {
193                 $classes[] = 'collapsed';
194             }
195         }
196         $categoryname = format_string($category->name, true, array('context' => context_coursecat::instance($category->id)));
198         $content .= html_writer::start_tag('div', array('class'=>join(' ', $classes)));
199         $content .= html_writer::start_tag('div', array('class'=>'category_label'));
200         $content .= html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $categoryname, array('class'=>'category_link'));
201         $content .= html_writer::end_tag('div');
202         if ($hassubcategories) {
203             $content .= html_writer::start_tag('div', array('class'=>'subcategories'));
204             foreach ($category->categories as $subcategory) {
205                 $content .= $this->course_category_tree_category($subcategory, $depth+1);
206             }
207             $content .= html_writer::end_tag('div');
208         }
209         if ($hascourses) {
210             $content .= html_writer::start_tag('div', array('class'=>'courses'));
211             $coursecount = 0;
212             $strinfo = new lang_string('info');
213             foreach ($category->courses as $course) {
214                 $classes = array('course');
215                 $linkclass = 'course_link';
216                 if (!$course->visible) {
217                     $linkclass .= ' dimmed';
218                 }
219                 $coursecount ++;
220                 $classes[] = ($coursecount%2)?'odd':'even';
221                 $content .= html_writer::start_tag('div', array('class'=>join(' ', $classes)));
222                 $content .= html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_string($course->fullname), array('class'=>$linkclass));
223                 $content .= html_writer::start_tag('div', array('class'=>'course_info clearfix'));
225                 // print enrol info
226                 if ($icons = enrol_get_course_info_icons($course)) {
227                     foreach ($icons as $pix_icon) {
228                         $content .= $this->render($pix_icon);
229                     }
230                 }
232                 if ($course->summary) {
233                     $url = new moodle_url('/course/info.php', array('id' => $course->id));
234                     $image = html_writer::empty_tag('img', array('src'=>$this->output->pix_url('i/info'), 'alt'=>$this->strings->summary));
235                     $content .= $this->action_link($url, $image, new popup_action('click', $url, 'courseinfo'), array('title' => $this->strings->summary));
236                 }
237                 $content .= html_writer::end_tag('div');
238                 $content .= html_writer::end_tag('div');
239             }
240             $content .= html_writer::end_tag('div');
241         }
242         $content .= html_writer::end_tag('div');
243         return $content;
244     }
246     /**
247      * Build the HTML for the module chooser javascript popup
248      *
249      * @param array $modules A set of modules as returned form @see
250      * get_module_metadata
251      * @param object $course The course that will be displayed
252      * @return string The composed HTML for the module
253      */
254     public function course_modchooser($modules, $course) {
255         static $isdisplayed = false;
256         if ($isdisplayed) {
257             return '';
258         }
259         $isdisplayed = true;
261         // Add the module chooser
262         $this->page->requires->yui_module('moodle-course-modchooser',
263         'M.course.init_chooser',
264         array(array('courseid' => $course->id, 'closeButtonTitle' => get_string('close', 'editor')))
265         );
266         $this->page->requires->strings_for_js(array(
267                 'addresourceoractivity',
268                 'modchooserenable',
269                 'modchooserdisable',
270         ), 'moodle');
272         // Add the header
273         $header = html_writer::tag('div', get_string('addresourceoractivity', 'moodle'),
274                 array('class' => 'hd choosertitle'));
276         $formcontent = html_writer::start_tag('form', array('action' => new moodle_url('/course/jumpto.php'),
277                 'id' => 'chooserform', 'method' => 'post'));
278         $formcontent .= html_writer::start_tag('div', array('id' => 'typeformdiv'));
279         $formcontent .= html_writer::tag('input', '', array('type' => 'hidden', 'id' => 'course',
280                 'name' => 'course', 'value' => $course->id));
281         $formcontent .= html_writer::tag('input', '',
282                 array('type' => 'hidden', 'class' => 'jump', 'name' => 'jump', 'value' => ''));
283         $formcontent .= html_writer::tag('input', '', array('type' => 'hidden', 'name' => 'sesskey',
284                 'value' => sesskey()));
285         $formcontent .= html_writer::end_tag('div');
287         // Put everything into one tag 'options'
288         $formcontent .= html_writer::start_tag('div', array('class' => 'options'));
289         $formcontent .= html_writer::tag('div', get_string('selectmoduletoviewhelp', 'moodle'),
290                 array('class' => 'instruction'));
291         // Put all options into one tag 'alloptions' to allow us to handle scrolling
292         $formcontent .= html_writer::start_tag('div', array('class' => 'alloptions'));
294          // Activities
295         $activities = array_filter($modules, function($mod) {
296             return ($mod->archetype !== MOD_ARCHETYPE_RESOURCE && $mod->archetype !== MOD_ARCHETYPE_SYSTEM);
297         });
298         if (count($activities)) {
299             $formcontent .= $this->course_modchooser_title('activities');
300             $formcontent .= $this->course_modchooser_module_types($activities);
301         }
303         // Resources
304         $resources = array_filter($modules, function($mod) {
305             return ($mod->archetype === MOD_ARCHETYPE_RESOURCE);
306         });
307         if (count($resources)) {
308             $formcontent .= $this->course_modchooser_title('resources');
309             $formcontent .= $this->course_modchooser_module_types($resources);
310         }
312         $formcontent .= html_writer::end_tag('div'); // modoptions
313         $formcontent .= html_writer::end_tag('div'); // types
315         $formcontent .= html_writer::start_tag('div', array('class' => 'submitbuttons'));
316         $formcontent .= html_writer::tag('input', '',
317                 array('type' => 'submit', 'name' => 'submitbutton', 'class' => 'submitbutton', 'value' => get_string('add')));
318         $formcontent .= html_writer::tag('input', '',
319                 array('type' => 'submit', 'name' => 'addcancel', 'class' => 'addcancel', 'value' => get_string('cancel')));
320         $formcontent .= html_writer::end_tag('div');
321         $formcontent .= html_writer::end_tag('form');
323         // Wrap the whole form in a div
324         $formcontent = html_writer::tag('div', $formcontent, array('id' => 'chooseform'));
326         // Put all of the content together
327         $content = $formcontent;
329         $content = html_writer::tag('div', $content, array('class' => 'choosercontainer'));
330         return $header . html_writer::tag('div', $content, array('class' => 'chooserdialoguebody'));
331     }
333     /**
334      * Build the HTML for a specified set of modules
335      *
336      * @param array $modules A set of modules as used by the
337      * course_modchooser_module function
338      * @return string The composed HTML for the module
339      */
340     protected function course_modchooser_module_types($modules) {
341         $return = '';
342         foreach ($modules as $module) {
343             if (!isset($module->types)) {
344                 $return .= $this->course_modchooser_module($module);
345             } else {
346                 $return .= $this->course_modchooser_module($module, array('nonoption'));
347                 foreach ($module->types as $type) {
348                     $return .= $this->course_modchooser_module($type, array('option', 'subtype'));
349                 }
350             }
351         }
352         return $return;
353     }
355     /**
356      * Return the HTML for the specified module adding any required classes
357      *
358      * @param object $module An object containing the title, and link. An
359      * icon, and help text may optionally be specified. If the module
360      * contains subtypes in the types option, then these will also be
361      * displayed.
362      * @param array $classes Additional classes to add to the encompassing
363      * div element
364      * @return string The composed HTML for the module
365      */
366     protected function course_modchooser_module($module, $classes = array('option')) {
367         $output = '';
368         $output .= html_writer::start_tag('div', array('class' => implode(' ', $classes)));
369         $output .= html_writer::start_tag('label', array('for' => 'module_' . $module->name));
370         if (!isset($module->types)) {
371             $output .= html_writer::tag('input', '', array('type' => 'radio',
372                     'name' => 'jumplink', 'id' => 'module_' . $module->name, 'value' => $module->link));
373         }
375         $output .= html_writer::start_tag('span', array('class' => 'modicon'));
376         if (isset($module->icon)) {
377             // Add an icon if we have one
378             $output .= $module->icon;
379         }
380         $output .= html_writer::end_tag('span');
382         $output .= html_writer::tag('span', $module->title, array('class' => 'typename'));
383         if (!isset($module->help)) {
384             // Add help if found
385             $module->help = get_string('nohelpforactivityorresource', 'moodle');
386         }
388         // Format the help text using markdown with the following options
389         $options = new stdClass();
390         $options->trusted = false;
391         $options->noclean = false;
392         $options->smiley = false;
393         $options->filter = false;
394         $options->para = true;
395         $options->newlines = false;
396         $options->overflowdiv = false;
397         $module->help = format_text($module->help, FORMAT_MARKDOWN, $options);
398         $output .= html_writer::tag('span', $module->help, array('class' => 'typesummary'));
399         $output .= html_writer::end_tag('label');
400         $output .= html_writer::end_tag('div');
402         return $output;
403     }
405     protected function course_modchooser_title($title, $identifier = null) {
406         $module = new stdClass();
407         $module->name = $title;
408         $module->types = array();
409         $module->title = get_string($title, $identifier);
410         $module->help = '';
411         return $this->course_modchooser_module($module, array('moduletypetitle'));
412     }
414     /**
415      * Renders HTML for displaying the sequence of course module editing buttons
416      *
417      * @see course_get_cm_edit_actions()
418      *
419      * @param array $actions array of action_link or pix_icon objects
420      * @return string
421      */
422     public function course_section_cm_edit_actions($actions) {
423         $output = html_writer::start_tag('span', array('class' => 'commands'));
424         foreach ($actions as $action) {
425             if ($action instanceof renderable) {
426                 $output .= $this->output->render($action);
427             } else {
428                 $output .= $action;
429             }
430         }
431         $output .= html_writer::end_tag('span');
432         return $output;
433     }
435     /**
436      * Renders HTML for the menus to add activities and resources to the current course
437      *
438      * Note, if theme overwrites this function and it does not use modchooser,
439      * see also {@link core_course_renderer::add_modchoosertoggle()}
440      *
441      * @param stdClass $course
442      * @param int $section relative section number (field course_sections.section)
443      * @param int $sectionreturn The section to link back to
444      * @param array $displayoptions additional display options, for example blocks add
445      *     option 'inblock' => true, suggesting to display controls vertically
446      * @return string
447      */
448     function course_section_add_cm_control($course, $section, $sectionreturn = null, $displayoptions = array()) {
449         global $CFG;
451         $vertical = !empty($displayoptions['inblock']);
453         // check to see if user can add menus and there are modules to add
454         if (!has_capability('moodle/course:manageactivities', context_course::instance($course->id))
455                 || !$this->page->user_is_editing()
456                 || !($modnames = get_module_types_names()) || empty($modnames)) {
457             return '';
458         }
460         // Retrieve all modules with associated metadata
461         $modules = get_module_metadata($course, $modnames, $sectionreturn);
462         $urlparams = array('section' => $section);
464         // We'll sort resources and activities into two lists
465         $activities = array(MOD_CLASS_ACTIVITY => array(), MOD_CLASS_RESOURCE => array());
467         foreach ($modules as $module) {
468             if (!array_key_exists($module->archetype, $activities)) {
469                 // System modules cannot be added by user, do not add to dropdown
470             } else if (isset($module->types)) {
471                 // This module has a subtype
472                 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
473                 $subtypes = array();
474                 foreach ($module->types as $subtype) {
475                     $link = $subtype->link->out(true, $urlparams);
476                     $subtypes[$link] = $subtype->title;
477                 }
479                 // Sort module subtypes into the list
480                 if (!empty($module->title)) {
481                     // This grouping has a name
482                     $activities[$module->archetype][] = array($module->title => $subtypes);
483                 } else {
484                     // This grouping does not have a name
485                     $activities[$module->archetype] = array_merge($activities[$module->archetype], $subtypes);
486                 }
487             } else {
488                 // This module has no subtypes
489                 $link = $module->link->out(true, $urlparams);
490                 $activities[$module->archetype][$link] = $module->title;
491             }
492         }
494         $straddactivity = get_string('addactivity');
495         $straddresource = get_string('addresource');
496         $sectionname = get_section_name($course, $section);
497         $strresourcelabel = get_string('addresourcetosection', null, $sectionname);
498         $stractivitylabel = get_string('addactivitytosection', null, $sectionname);
500         $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
502         if (!$vertical) {
503             $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
504         }
506         if (!empty($activities[MOD_CLASS_RESOURCE])) {
507             $select = new url_select($activities[MOD_CLASS_RESOURCE], '', array(''=>$straddresource), "ressection$section");
508             $select->set_help_icon('resources');
509             $select->set_label($strresourcelabel, array('class' => 'accesshide'));
510             $output .= $this->output->render($select);
511         }
513         if (!empty($activities[MOD_CLASS_ACTIVITY])) {
514             $select = new url_select($activities[MOD_CLASS_ACTIVITY], '', array(''=>$straddactivity), "section$section");
515             $select->set_help_icon('activities');
516             $select->set_label($stractivitylabel, array('class' => 'accesshide'));
517             $output .= $this->output->render($select);
518         }
520         if (!$vertical) {
521             $output .= html_writer::end_tag('div');
522         }
524         $output .= html_writer::end_tag('div');
526         if (course_ajax_enabled($course) && $course->id == $this->page->course->id) {
527             // modchooser can be added only for the current course set on the page!
528             $straddeither = get_string('addresourceoractivity');
529             // The module chooser link
530             $modchooser = html_writer::start_tag('div', array('class' => 'mdl-right'));
531             $modchooser.= html_writer::start_tag('div', array('class' => 'section-modchooser'));
532             $icon = $this->output->pix_icon('t/add', '');
533             $span = html_writer::tag('span', $straddeither, array('class' => 'section-modchooser-text'));
534             $modchooser .= html_writer::tag('span', $icon . $span, array('class' => 'section-modchooser-link'));
535             $modchooser.= html_writer::end_tag('div');
536             $modchooser.= html_writer::end_tag('div');
538             // Wrap the normal output in a noscript div
539             $usemodchooser = get_user_preferences('usemodchooser', $CFG->modchooserdefault);
540             if ($usemodchooser) {
541                 $output = html_writer::tag('div', $output, array('class' => 'hiddenifjs addresourcedropdown'));
542                 $modchooser = html_writer::tag('div', $modchooser, array('class' => 'visibleifjs addresourcemodchooser'));
543             } else {
544                 // If the module chooser is disabled, we need to ensure that the dropdowns are shown even if javascript is disabled
545                 $output = html_writer::tag('div', $output, array('class' => 'show addresourcedropdown'));
546                 $modchooser = html_writer::tag('div', $modchooser, array('class' => 'hide addresourcemodchooser'));
547             }
548             $output = $this->course_modchooser($modules, $course) . $modchooser . $output;
549         }
551         return $output;
552     }
554     /**
555      * Renders html for completion box on course page
556      *
557      * If completion is disabled, returns empty string
558      * If completion is automatic, returns an icon of the current completion state
559      * If completion is manual, returns a form (with an icon inside) that allows user to
560      * toggle completion
561      *
562      * @param stdClass $course course object
563      * @param completion_info $completioninfo completion info for the course, it is recommended
564      *     to fetch once for all modules in course/section for performance
565      * @param cm_info $mod module to show completion for
566      * @param array $displayoptions display options, not used in core
567      * @return string
568      */
569     public function course_section_cm_completion($course, &$completioninfo, cm_info $mod, $displayoptions = array()) {
570         global $CFG;
571         $output = '';
572         if (!empty($displayoptions['hidecompletion']) || !isloggedin() || isguestuser() || !$mod->uservisible) {
573             return $output;
574         }
575         if ($completioninfo === null) {
576             $completioninfo = new completion_info($course);
577         }
578         $completion = $completioninfo->is_enabled($mod);
579         if ($completion == COMPLETION_TRACKING_NONE) {
580             return $output;
581         }
583         $completiondata = $completioninfo->get_data($mod, true);
584         $completionicon = '';
586         if ($this->page->user_is_editing()) {
587             switch ($completion) {
588                 case COMPLETION_TRACKING_MANUAL :
589                     $completionicon = 'manual-enabled'; break;
590                 case COMPLETION_TRACKING_AUTOMATIC :
591                     $completionicon = 'auto-enabled'; break;
592             }
593         } else if ($completion == COMPLETION_TRACKING_MANUAL) {
594             switch($completiondata->completionstate) {
595                 case COMPLETION_INCOMPLETE:
596                     $completionicon = 'manual-n'; break;
597                 case COMPLETION_COMPLETE:
598                     $completionicon = 'manual-y'; break;
599             }
600         } else { // Automatic
601             switch($completiondata->completionstate) {
602                 case COMPLETION_INCOMPLETE:
603                     $completionicon = 'auto-n'; break;
604                 case COMPLETION_COMPLETE:
605                     $completionicon = 'auto-y'; break;
606                 case COMPLETION_COMPLETE_PASS:
607                     $completionicon = 'auto-pass'; break;
608                 case COMPLETION_COMPLETE_FAIL:
609                     $completionicon = 'auto-fail'; break;
610             }
611         }
612         if ($completionicon) {
613             $formattedname = $mod->get_formatted_name();
614             $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $formattedname);
615             if ($completion == COMPLETION_TRACKING_MANUAL && !$this->page->user_is_editing()) {
616                 $imgtitle = get_string('completion-title-' . $completionicon, 'completion', $formattedname);
617                 $newstate =
618                     $completiondata->completionstate == COMPLETION_COMPLETE
619                     ? COMPLETION_INCOMPLETE
620                     : COMPLETION_COMPLETE;
621                 // In manual mode the icon is a toggle form...
623                 // If this completion state is used by the
624                 // conditional activities system, we need to turn
625                 // off the JS.
626                 $extraclass = '';
627                 if (!empty($CFG->enableavailability) &&
628                         condition_info::completion_value_used_as_condition($course, $mod)) {
629                     $extraclass = ' preventjs';
630                 }
631                 $output .= html_writer::start_tag('form', array('method' => 'post',
632                     'action' => new moodle_url('/course/togglecompletion.php'),
633                     'class' => 'togglecompletion'. $extraclass));
634                 $output .= html_writer::start_tag('div');
635                 $output .= html_writer::empty_tag('input', array(
636                     'type' => 'hidden', 'name' => 'id', 'value' => $mod->id));
637                 $output .= html_writer::empty_tag('input', array(
638                     'type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
639                 $output .= html_writer::empty_tag('input', array(
640                     'type' => 'hidden', 'name' => 'modulename', 'value' => $mod->name));
641                 $output .= html_writer::empty_tag('input', array(
642                     'type' => 'hidden', 'name' => 'completionstate', 'value' => $newstate));
643                 $output .= html_writer::empty_tag('input', array(
644                     'type' => 'image',
645                     'src' => $this->output->pix_url('i/completion-'.$completionicon),
646                     'alt' => $imgalt, 'title' => $imgtitle));
647                 $output .= html_writer::end_tag('div');
648                 $output .= html_writer::end_tag('form');
649             } else {
650                 // In auto mode, or when editing, the icon is just an image
651                 $completionpixicon = new pix_icon('i/completion-'.$completionicon, $imgalt, '',
652                         array('title' => $imgalt));
653                 $output .= html_writer::tag('span', $this->output->render($completionpixicon),
654                         array('class' => 'autocompletion'));
655             }
656         }
657         return $output;
658     }
660     /**
661      * Checks if course module has any conditions that may make it unavailable for
662      * all or some of the students
663      *
664      * This function is internal and is only used to create CSS classes for the module name/text
665      *
666      * @param cm_info $mod
667      * @return bool
668      */
669     protected function is_cm_conditionally_hidden(cm_info $mod) {
670         global $CFG;
671         $conditionalhidden = false;
672         if (!empty($CFG->enableavailability)) {
673             $conditionalhidden = $mod->availablefrom > time() ||
674                 ($mod->availableuntil && $mod->availableuntil < time()) ||
675                 count($mod->conditionsgrade) > 0 ||
676                 count($mod->conditionscompletion) > 0;
677         }
678         return $conditionalhidden;
679     }
681     /**
682      * Renders html to display a name with the link to the course module on a course page
683      *
684      * If module is unavailable for user but still needs to be displayed
685      * in the list, just the name is returned without a link
686      *
687      * Note, that for course modules that never have separate pages (i.e. labels)
688      * this function return an empty string
689      *
690      * @param cm_info $mod
691      * @param array $displayoptions
692      * @return string
693      */
694     public function course_section_cm_name(cm_info $mod, $displayoptions = array()) {
695         global $CFG;
696         $output = '';
697         if (!$mod->uservisible &&
698                 (empty($mod->showavailability) || empty($mod->availableinfo))) {
699             // nothing to be displayed to the user
700             return $output;
701         }
702         $url = $mod->get_url();
703         if (!$url) {
704             return $output;
705         }
707         //Accessibility: for files get description via icon, this is very ugly hack!
708         $instancename = $mod->get_formatted_name();
709         $altname = '';
710         $altname = $mod->modfullname;
711         // Avoid unnecessary duplication: if e.g. a forum name already
712         // includes the word forum (or Forum, etc) then it is unhelpful
713         // to include that in the accessible description that is added.
714         if (false !== strpos(textlib::strtolower($instancename),
715                 textlib::strtolower($altname))) {
716             $altname = '';
717         }
718         // File type after name, for alphabetic lists (screen reader).
719         if ($altname) {
720             $altname = get_accesshide(' '.$altname);
721         }
723         $conditionalhidden = $this->is_cm_conditionally_hidden($mod);
724         $accessiblebutdim = !$mod->visible || $conditionalhidden;
726         $linkclasses = '';
727         $accesstext = '';
728         $textclasses = '';
729         if ($accessiblebutdim) {
730             $linkclasses .= ' dimmed';
731             $textclasses .= ' dimmed_text';
732             if ($conditionalhidden) {
733                 $linkclasses .= ' conditionalhidden';
734                 $textclasses .= ' conditionalhidden';
735             }
736             if ($mod->uservisible) {
737                 // show accessibility note only if user can access the module himself
738                 $accesstext = get_accesshide(get_string('hiddenfromstudents').': ');
739             }
740         }
742         // Get on-click attribute value if specified and decode the onclick - it
743         // has already been encoded for display (puke).
744         $onclick = htmlspecialchars_decode($mod->get_on_click(), ENT_QUOTES);
746         $groupinglabel = '';
747         if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', context_course::instance($mod->course))) {
748             $groupings = groups_get_all_groupings($mod->course);
749             $groupinglabel = html_writer::tag('span', '('.format_string($groupings[$mod->groupingid]->name).')',
750                     array('class' => 'groupinglabel '.$textclasses));
751         }
753         // Display link itself.
754         $activitylink = html_writer::empty_tag('img', array('src' => $mod->get_icon_url(),
755                 'class' => 'iconlarge activityicon', 'alt' => $mod->modfullname)) . $accesstext .
756                 html_writer::tag('span', $instancename . $altname, array('class' => 'instancename'));
757         if ($mod->uservisible) {
758             $output .= html_writer::link($url, $activitylink, array('class' => $linkclasses, 'onclick' => $onclick)) .
759                     $groupinglabel;
760         } else {
761             // We may be displaying this just in order to show information
762             // about visibility, without the actual link ($mod->uservisible)
763             $output .= html_writer::tag('div', $activitylink, array('class' => $textclasses)) .
764                     $groupinglabel;
765         }
766         return $output;
767     }
769     /**
770      * Renders html to display the module content on the course page (i.e. text of the labels)
771      *
772      * @param cm_info $mod
773      * @param array $displayoptions
774      * @return string
775      */
776     public function course_section_cm_text(cm_info $mod, $displayoptions = array()) {
777         $output = '';
778         if (!$mod->uservisible &&
779                 (empty($mod->showavailability) || empty($mod->availableinfo))) {
780             // nothing to be displayed to the user
781             return $output;
782         }
783         $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
784         $conditionalhidden = $this->is_cm_conditionally_hidden($mod);
785         $accessiblebutdim = !$mod->visible || $conditionalhidden;
786         $textclasses = '';
787         $accesstext = '';
788         if ($accessiblebutdim) {
789             $textclasses .= ' dimmed_text';
790             if ($conditionalhidden) {
791                 $textclasses .= ' conditionalhidden';
792             }
793             if ($mod->uservisible) {
794                 // show accessibility note only if user can access the module himself
795                 $accesstext = get_accesshide(get_string('hiddenfromstudents').': ');
796             }
797         }
798         if ($mod->get_url()) {
799             if ($content) {
800                 // If specified, display extra content after link.
801                 $output = html_writer::tag('div', $content, array('class' =>
802                         trim('contentafterlink ' . $textclasses)));
803             }
804         } else {
805             // No link, so display only content.
806             $output = html_writer::tag('div', $accesstext . $content, array('class' => $textclasses));
807         }
808         return $output;
809     }
811     /**
812      * Renders HTML to show course module availability information (for someone who isn't allowed
813      * to see the activity itself, or for staff)
814      *
815      * @param cm_info $mod
816      * @param array $displayoptions
817      * @return string
818      */
819     public function course_section_cm_availability(cm_info $mod, $displayoptions = array()) {
820         global $CFG;
821         if (!$mod->uservisible) {
822             // this is a student who is not allowed to see the module but might be allowed
823             // to see availability info (i.e. "Available from ...")
824             if (!empty($mod->showavailability) && !empty($mod->availableinfo)) {
825                 $output = html_writer::tag('div', $mod->availableinfo, array('class' => 'availabilityinfo'));
826             }
827             return $output;
828         }
829         // this is a teacher who is allowed to see module but still should see the
830         // information that module is not available to all/some students
831         $modcontext = context_module::instance($mod->id);
832         $canviewhidden = has_capability('moodle/course:viewhiddenactivities', $modcontext);
833         if ($canviewhidden && !empty($CFG->enableavailability)) {
834             // Don't add availability information if user is not editing and activity is hidden.
835             if ($mod->visible || $this->page->user_is_editing()) {
836                 $hidinfoclass = '';
837                 if (!$mod->visible) {
838                     $hidinfoclass = 'hide';
839                 }
840                 $ci = new condition_info($mod);
841                 $fullinfo = $ci->get_full_information();
842                 if($fullinfo) {
843                     echo '<div class="availabilityinfo '.$hidinfoclass.'">'.get_string($mod->showavailability
844                         ? 'userrestriction_visible'
845                         : 'userrestriction_hidden','condition',
846                         $fullinfo).'</div>';
847                 }
848             }
849         }
850         return '';
851     }
853     /**
854      * Renders HTML to display one course module in a course section
855      *
856      * This includes link, content, availability, completion info and additional information
857      * that module type wants to display (i.e. number of unread forum posts)
858      *
859      * This function calls:
860      * {@link core_course_renderer::course_section_cm_name()}
861      * {@link cm_info::get_after_link()}
862      * {@link core_course_renderer::course_section_cm_text()}
863      * {@link core_course_renderer::course_section_cm_availability()}
864      * {@link core_course_renderer::course_section_cm_completion()}
865      * {@link course_get_cm_edit_actions()}
866      * {@link core_course_renderer::course_section_cm_edit_actions()}
867      * 
868      * @param stdClass $course
869      * @param completion_info $completioninfo
870      * @param cm_info $mod
871      * @param int|null $sectionreturn
872      * @param array $displayoptions
873      * @return string
874      */
875     public function course_section_cm($course, &$completioninfo, cm_info $mod, $sectionreturn, $displayoptions = array()) {
876         $output = '';
877         // We return empty string (because course module will not be displayed at all)
878         // if:
879         // 1) The activity is not visible to users
880         // and
881         // 2a) The 'showavailability' option is not set (if that is set,
882         //     we need to display the activity so we can show
883         //     availability info)
884         // or
885         // 2b) The 'availableinfo' is empty, i.e. the activity was
886         //     hidden in a way that leaves no info, such as using the
887         //     eye icon.
888         if (!$mod->uservisible &&
889             (empty($mod->showavailability) || empty($mod->availableinfo))) {
890             return $output;
891         }
893         $indentclasses = 'mod-indent';
894         if (!empty($mod->indent)) {
895             $indentclasses .= ' mod-indent-'.$mod->indent;
896             if ($mod->indent > 15) {
897                 $indentclasses .= ' mod-indent-huge';
898             }
899         }
900         $output .= html_writer::start_tag('div', array('class' => $indentclasses));
902         // Start the div for the activity title, excluding the edit icons.
903         $output .= html_writer::start_tag('div', array('class' => 'activityinstance'));
905         // Display the link to the module (or do nothing if module has no url)
906         $output .= $this->course_section_cm_name($mod, $displayoptions);
908         // Module can put text after the link (e.g. forum unread)
909         $output .= $mod->get_after_link();
911         // Closing the tag which contains everything but edit icons. Content part of the module should not be part of this.
912         $output .= html_writer::end_tag('div'); // .activityinstance
914         // If there is content but NO link (eg label), then display the
915         // content here (BEFORE any icons). In this case cons must be
916         // displayed after the content so that it makes more sense visually
917         // and for accessibility reasons, e.g. if you have a one-line label
918         // it should work similarly (at least in terms of ordering) to an
919         // activity.
920         $contentpart = $this->course_section_cm_text($mod, $displayoptions);
921         $url = $mod->get_url();
922         if (empty($url)) {
923             $output .= $contentpart;
924         }
926         if ($this->page->user_is_editing()) {
927             $editactions = course_get_cm_edit_actions($mod, $mod->indent, $sectionreturn);
928             $output .= ' '. $this->course_section_cm_edit_actions($editactions);
929             $output .= $mod->get_after_edit_icons();
930         }
932         $output .= $this->course_section_cm_completion($course, $completioninfo, $mod, $displayoptions);
934         // If there is content AND a link, then display the content here
935         // (AFTER any icons). Otherwise it was displayed before
936         if (!empty($url)) {
937             $output .= $contentpart;
938         }
940         // show availability info (if module is not available)
941         $output .= $this->course_section_cm_availability($mod, $displayoptions);
943         $output .= html_writer::end_tag('div'); // $indentclasses
944         return $output;
945     }
947     /**
948      * Renders HTML to display a list of course modules in a course section
949      * Also displays "move here" controls in Javascript-disabled mode
950      *
951      * This function calls {@link core_course_renderer::course_section_cm()}
952      *
953      * @param stdClass $course course object
954      * @param int|stdClass|section_info $section relative section number or section object
955      * @param int $sectionreturn section number to return to
956      * @param int $displayoptions
957      * @return void
958      */
959     public function course_section_cm_list($course, $section, $sectionreturn = null, $displayoptions = array()) {
960         global $USER;
962         $output = '';
963         $modinfo = get_fast_modinfo($course);
964         if (is_object($section)) {
965             $section = $modinfo->get_section_info($section->section);
966         } else {
967             $section = $modinfo->get_section_info($section);
968         }
969         $completioninfo = new completion_info($course);
971         // check if we are currently in the process of moving a module with JavaScript disabled
972         $ismoving = $this->page->user_is_editing() && ismoving($course->id);
973         if ($ismoving) {
974             $movingpix = new pix_icon('movehere', get_string('movehere'), 'moodle', array('class' => 'movetarget'));
975             $strmovefull = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'"));
976         }
978         // Get the list of modules visible to user (excluding the module being moved if there is one)
979         $moduleshtml = array();
980         if (!empty($modinfo->sections[$section->section])) {
981             foreach ($modinfo->sections[$section->section] as $modnumber) {
982                 $mod = $modinfo->cms[$modnumber];
984                 if ($ismoving and $mod->id == $USER->activitycopy) {
985                     // do not display moving mod
986                     continue;
987                 }
989                 if ($modulehtml = $this->course_section_cm($course,
990                         $completioninfo, $mod, $sectionreturn, $displayoptions)) {
991                     $moduleshtml[$modnumber] = $modulehtml;
992                 }
993             }
994         }
996         if (!empty($moduleshtml) || $ismoving) {
998             $output .= html_writer::start_tag('ul', array('class' => 'section img-text'));
1000             foreach ($moduleshtml as $modnumber => $modulehtml) {
1001                 if ($ismoving) {
1002                     $movingurl = new moodle_url('/course/mod.php', array('moveto' => $modnumber, 'sesskey' => sesskey()));
1003                     $output .= html_writer::tag('li', html_writer::link($movingurl, $this->output->render($movingpix)),
1004                             array('class' => 'movehere', 'title' => $strmovefull));
1005                 }
1007                 $mod = $modinfo->cms[$modnumber];
1008                 $modclasses = 'activity '. $mod->modname. 'modtype_'.$mod->modname. ' '. $mod->get_extra_classes();
1009                 $output .= html_writer::start_tag('li', array('class' => $modclasses, 'id' => 'module-'. $mod->id));
1010                 $output .= $modulehtml;
1011                 $output .= html_writer::end_tag('li');
1012             }
1014             if ($ismoving) {
1015                 $movingurl = new moodle_url('/course/mod.php', array('movetosection' => $section->id, 'sesskey' => sesskey()));
1016                 $output .= html_writer::tag('li', html_writer::link($movingurl, $this->output->render($movingpix)),
1017                         array('class' => 'movehere', 'title' => $strmovefull));
1018             }
1020             $output .= html_writer::end_tag('ul'); // .section
1021         }
1023         return $output;
1024     }