MDL-37207 Add modchoosertoggle in renderer to allow themes to override
[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);
a03dfb7d
MG
52 $this->add_modchoosertoggle();
53 }
54
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);
24e27ac0
SH
93 }
94
cb76fec0
PS
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;
103
104 $context = context_course::instance($course->id);
105
106 $content = '';
107 $content .= $this->output->box_start('generalbox info');
108
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 }
127
128 if (!empty($namesarray)) {
129 $content .= "<ul class=\"teachers\">\n<li>";
130 $content .= implode('</li><li>', $namesarray);
131 $content .= "</li></ul>";
132 }
133 }
134
135 $content .= $this->output->box_end();
136
137 return $content;
138 }
139
24e27ac0
SH
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) {
24e27ac0
SH
151 $this->strings->summary = get_string('summary');
152
153 // Generate an id and the required JS call to make this a nice widget
154 $id = html_writer::random_id('course_category_tree');
c8ffba95 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'));
24e27ac0
SH
156
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');
df997f84 167
24e27ac0
SH
168 // Return the course category tree HTML
169 return $content;
170 }
171
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 = '';
3754f4f3
ARN
181 $hassubcategories = (isset($category->categories) && count($category->categories)>0);
182 $hascourses = (isset($category->courses) && count($category->courses)>0);
24e27ac0
SH
183 $classes = array('category');
184 if ($category->parent != 0) {
185 $classes[] = 'subcategory';
186 }
d703f226
SH
187 if (empty($category->visible)) {
188 $classes[] = 'dimmed_category';
189 }
24e27ac0
SH
190 if ($hassubcategories || $hascourses) {
191 $classes[] = 'with_children';
192 if ($depth > 1) {
193 $classes[] = 'collapsed';
194 }
195 }
9a5e297b 196 $categoryname = format_string($category->name, true, array('context' => context_coursecat::instance($category->id)));
63390481 197
24e27ac0
SH
198 $content .= html_writer::start_tag('div', array('class'=>join(' ', $classes)));
199 $content .= html_writer::start_tag('div', array('class'=>'category_label'));
63390481 200 $content .= html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $categoryname, array('class'=>'category_link'));
24e27ac0
SH
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;
6a1273cb 212 $strinfo = new lang_string('info');
24e27ac0
SH
213 foreach ($category->courses as $course) {
214 $classes = array('course');
d2fd38be
PS
215 $linkclass = 'course_link';
216 if (!$course->visible) {
217 $linkclass .= ' dimmed';
218 }
24e27ac0
SH
219 $coursecount ++;
220 $classes[] = ($coursecount%2)?'odd':'even';
221 $content .= html_writer::start_tag('div', array('class'=>join(' ', $classes)));
f4c23f03 222 $content .= html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_string($course->fullname), array('class'=>$linkclass));
24e27ac0
SH
223 $content .= html_writer::start_tag('div', array('class'=>'course_info clearfix'));
224
bf423bb1
PS
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 }
231
24e27ac0 232 if ($course->summary) {
6a1273cb 233 $url = new moodle_url('/course/info.php', array('id' => $course->id));
24e27ac0 234 $image = html_writer::empty_tag('img', array('src'=>$this->output->pix_url('i/info'), 'alt'=>$this->strings->summary));
6a1273cb 235 $content .= $this->action_link($url, $image, new popup_action('click', $url, 'courseinfo'), array('title' => $this->strings->summary));
24e27ac0
SH
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 }
01e0e704
ARN
245
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) {
697ff999
MG
255 static $isdisplayed = false;
256 if ($isdisplayed) {
257 return '';
258 }
259 $isdisplayed = true;
260
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');
01e0e704
ARN
271
272 // Add the header
1edff8c7 273 $header = html_writer::tag('div', get_string('addresourceoractivity', 'moodle'),
255dd8d1 274 array('class' => 'hd choosertitle'));
01e0e704
ARN
275
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', '',
0a2fb910 282 array('type' => 'hidden', 'class' => 'jump', 'name' => 'jump', 'value' => ''));
01e0e704
ARN
283 $formcontent .= html_writer::tag('input', '', array('type' => 'hidden', 'name' => 'sesskey',
284 'value' => sesskey()));
285 $formcontent .= html_writer::end_tag('div');
286
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'));
293
5035ed63
MA
294 // Activities
295 $activities = array_filter($modules, function($mod) {
296 return ($mod->archetype !== MOD_ARCHETYPE_RESOURCE && $mod->archetype !== MOD_ARCHETYPE_SYSTEM);
297 });
01e0e704
ARN
298 if (count($activities)) {
299 $formcontent .= $this->course_modchooser_title('activities');
300 $formcontent .= $this->course_modchooser_module_types($activities);
301 }
302
463ccee1 303 // Resources
5035ed63
MA
304 $resources = array_filter($modules, function($mod) {
305 return ($mod->archetype === MOD_ARCHETYPE_RESOURCE);
306 });
48a5e55e
ARN
307 if (count($resources)) {
308 $formcontent .= $this->course_modchooser_title('resources');
309 $formcontent .= $this->course_modchooser_module_types($resources);
310 }
311
01e0e704
ARN
312 $formcontent .= html_writer::end_tag('div'); // modoptions
313 $formcontent .= html_writer::end_tag('div'); // types
314
315 $formcontent .= html_writer::start_tag('div', array('class' => 'submitbuttons'));
af75421c 316 $formcontent .= html_writer::tag('input', '',
0a2fb910 317 array('type' => 'submit', 'name' => 'submitbutton', 'class' => 'submitbutton', 'value' => get_string('add')));
8ff6c5ee 318 $formcontent .= html_writer::tag('input', '',
0a2fb910 319 array('type' => 'submit', 'name' => 'addcancel', 'class' => 'addcancel', 'value' => get_string('cancel')));
01e0e704
ARN
320 $formcontent .= html_writer::end_tag('div');
321 $formcontent .= html_writer::end_tag('form');
322
323 // Wrap the whole form in a div
324 $formcontent = html_writer::tag('div', $formcontent, array('id' => 'chooseform'));
325
326 // Put all of the content together
327 $content = $formcontent;
328
255dd8d1
ARN
329 $content = html_writer::tag('div', $content, array('class' => 'choosercontainer'));
330 return $header . html_writer::tag('div', $content, array('class' => 'chooserdialoguebody'));
01e0e704
ARN
331 }
332
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 }
354
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 }
374
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');
381
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 }
387
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');
401
402 return $output;
403 }
404
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 }
f558b291
MG
413
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 }
9a6aa5c1
MG
434
435 /**
436 * Renders HTML for the menus to add activities and resources to the current course
437 *
a03dfb7d
MG
438 * Note, if theme overwrites this function and it does not use modchooser,
439 * see also {@link core_course_renderer::add_modchoosertoggle()}
440 *
9a6aa5c1
MG
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;
450
451 $vertical = !empty($displayoptions['inblock']);
452
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 }
459
460 // Retrieve all modules with associated metadata
461 $modules = get_module_metadata($course, $modnames, $sectionreturn);
462 $urlparams = array('section' => $section);
463
464 // We'll sort resources and activities into two lists
465 $activities = array(MOD_CLASS_ACTIVITY => array(), MOD_CLASS_RESOURCE => array());
466
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 }
478
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 }
493
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);
499
500 $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
501
502 if (!$vertical) {
503 $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
504 }
505
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 }
512
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 }
519
520 if (!$vertical) {
521 $output .= html_writer::end_tag('div');
522 }
523
524 $output .= html_writer::end_tag('div');
525
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');
537
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 }
550
551 return $output;
552 }
7e29340f
MG
553
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 }
582
583 $completiondata = $completioninfo->get_data($mod, true);
584 $completionicon = '';
585
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...
622
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 }
ed513fad
MG
659
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 }
680
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 }
706
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 }
722
723 $conditionalhidden = $this->is_cm_conditionally_hidden($mod);
724 $accessiblebutdim = !$mod->visible || $conditionalhidden;
725
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 }
741
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);
745
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 }
752
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 }
768
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 }
810
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 }
c58a25d6
MG
852
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 }
892
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));
901
902 // Start the div for the activity title, excluding the edit icons.
903 $output .= html_writer::start_tag('div', array('class' => 'activityinstance'));
904
905 // Display the link to the module (or do nothing if module has no url)
906 $output .= $this->course_section_cm_name($mod, $displayoptions);
907
908 // Module can put text after the link (e.g. forum unread)
909 $output .= $mod->get_after_link();
910
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
913
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 }
925
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 }
931
932 $output .= $this->course_section_cm_completion($course, $completioninfo, $mod, $displayoptions);
933
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 }
939
940 // show availability info (if module is not available)
941 $output .= $this->course_section_cm_availability($mod, $displayoptions);
942
943 $output .= html_writer::end_tag('div'); // $indentclasses
944 return $output;
945 }
946
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;
961
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);
970
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 }
977
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];
983
984 if ($ismoving and $mod->id == $USER->activitycopy) {
985 // do not display moving mod
986 continue;
987 }
988
989 if ($modulehtml = $this->course_section_cm($course,
990 $completioninfo, $mod, $sectionreturn, $displayoptions)) {
991 $moduleshtml[$modnumber] = $modulehtml;
992 }
993 }
994 }
995
996 if (!empty($moduleshtml) || $ismoving) {
997
998 $output .= html_writer::start_tag('ul', array('class' => 'section img-text'));
999
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 }
1006
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 }
1013
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 }
1019
1020 $output .= html_writer::end_tag('ul'); // .section
1021 }
1022
1023 return $output;
1024 }
f4c23f03 1025}