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