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