MDL-37009 function print_course_search() is moved to deprecatedlib.php
[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 ||
aa2b293e
MG
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')) {
a03dfb7d 73 // too late or we are on site page or we could not find the course settings node
aa2b293e 74 // or we are not allowed to edit
a03dfb7d
MG
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);
24e27ac0
SH
99 }
100
cb76fec0
PS
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;
109
110 $context = context_course::instance($course->id);
111
112 $content = '';
113 $content .= $this->output->box_start('generalbox info');
114
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) {
49ee37d3 120 if ($users = get_role_users($roleid, $context, true, '', null, false)) {
cb76fec0
PS
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 }
133
134 if (!empty($namesarray)) {
135 $content .= "<ul class=\"teachers\">\n<li>";
136 $content .= implode('</li><li>', $namesarray);
137 $content .= "</li></ul>";
138 }
139 }
140
141 $content .= $this->output->box_end();
142
143 return $content;
144 }
145
24e27ac0
SH
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) {
24e27ac0
SH
157 $this->strings->summary = get_string('summary');
158
159 // Generate an id and the required JS call to make this a nice widget
160 $id = html_writer::random_id('course_category_tree');
c8ffba95 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'));
24e27ac0
SH
162
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');
df997f84 173
24e27ac0
SH
174 // Return the course category tree HTML
175 return $content;
176 }
177
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 = '';
3754f4f3
ARN
187 $hassubcategories = (isset($category->categories) && count($category->categories)>0);
188 $hascourses = (isset($category->courses) && count($category->courses)>0);
24e27ac0
SH
189 $classes = array('category');
190 if ($category->parent != 0) {
191 $classes[] = 'subcategory';
192 }
d703f226
SH
193 if (empty($category->visible)) {
194 $classes[] = 'dimmed_category';
195 }
24e27ac0
SH
196 if ($hassubcategories || $hascourses) {
197 $classes[] = 'with_children';
198 if ($depth > 1) {
199 $classes[] = 'collapsed';
200 }
201 }
9a5e297b 202 $categoryname = format_string($category->name, true, array('context' => context_coursecat::instance($category->id)));
63390481 203
24e27ac0
SH
204 $content .= html_writer::start_tag('div', array('class'=>join(' ', $classes)));
205 $content .= html_writer::start_tag('div', array('class'=>'category_label'));
63390481 206 $content .= html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $categoryname, array('class'=>'category_link'));
24e27ac0
SH
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;
6a1273cb 218 $strinfo = new lang_string('info');
24e27ac0
SH
219 foreach ($category->courses as $course) {
220 $classes = array('course');
d2fd38be
PS
221 $linkclass = 'course_link';
222 if (!$course->visible) {
223 $linkclass .= ' dimmed';
224 }
24e27ac0
SH
225 $coursecount ++;
226 $classes[] = ($coursecount%2)?'odd':'even';
227 $content .= html_writer::start_tag('div', array('class'=>join(' ', $classes)));
f4c23f03 228 $content .= html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_string($course->fullname), array('class'=>$linkclass));
24e27ac0
SH
229 $content .= html_writer::start_tag('div', array('class'=>'course_info clearfix'));
230
bf423bb1
PS
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 }
237
24e27ac0 238 if ($course->summary) {
6a1273cb 239 $url = new moodle_url('/course/info.php', array('id' => $course->id));
24e27ac0 240 $image = html_writer::empty_tag('img', array('src'=>$this->output->pix_url('i/info'), 'alt'=>$this->strings->summary));
6a1273cb 241 $content .= $this->action_link($url, $image, new popup_action('click', $url, 'courseinfo'), array('title' => $this->strings->summary));
24e27ac0
SH
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 }
01e0e704
ARN
251
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) {
697ff999
MG
261 static $isdisplayed = false;
262 if ($isdisplayed) {
263 return '';
264 }
265 $isdisplayed = true;
266
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');
01e0e704
ARN
277
278 // Add the header
1edff8c7 279 $header = html_writer::tag('div', get_string('addresourceoractivity', 'moodle'),
255dd8d1 280 array('class' => 'hd choosertitle'));
01e0e704
ARN
281
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', '',
0a2fb910 288 array('type' => 'hidden', 'class' => 'jump', 'name' => 'jump', 'value' => ''));
01e0e704
ARN
289 $formcontent .= html_writer::tag('input', '', array('type' => 'hidden', 'name' => 'sesskey',
290 'value' => sesskey()));
291 $formcontent .= html_writer::end_tag('div');
292
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'));
299
5035ed63
MA
300 // Activities
301 $activities = array_filter($modules, function($mod) {
302 return ($mod->archetype !== MOD_ARCHETYPE_RESOURCE && $mod->archetype !== MOD_ARCHETYPE_SYSTEM);
303 });
01e0e704
ARN
304 if (count($activities)) {
305 $formcontent .= $this->course_modchooser_title('activities');
306 $formcontent .= $this->course_modchooser_module_types($activities);
307 }
308
463ccee1 309 // Resources
5035ed63
MA
310 $resources = array_filter($modules, function($mod) {
311 return ($mod->archetype === MOD_ARCHETYPE_RESOURCE);
312 });
48a5e55e
ARN
313 if (count($resources)) {
314 $formcontent .= $this->course_modchooser_title('resources');
315 $formcontent .= $this->course_modchooser_module_types($resources);
316 }
317
01e0e704
ARN
318 $formcontent .= html_writer::end_tag('div'); // modoptions
319 $formcontent .= html_writer::end_tag('div'); // types
320
321 $formcontent .= html_writer::start_tag('div', array('class' => 'submitbuttons'));
af75421c 322 $formcontent .= html_writer::tag('input', '',
0a2fb910 323 array('type' => 'submit', 'name' => 'submitbutton', 'class' => 'submitbutton', 'value' => get_string('add')));
8ff6c5ee 324 $formcontent .= html_writer::tag('input', '',
0a2fb910 325 array('type' => 'submit', 'name' => 'addcancel', 'class' => 'addcancel', 'value' => get_string('cancel')));
01e0e704
ARN
326 $formcontent .= html_writer::end_tag('div');
327 $formcontent .= html_writer::end_tag('form');
328
329 // Wrap the whole form in a div
330 $formcontent = html_writer::tag('div', $formcontent, array('id' => 'chooseform'));
331
332 // Put all of the content together
333 $content = $formcontent;
334
255dd8d1
ARN
335 $content = html_writer::tag('div', $content, array('class' => 'choosercontainer'));
336 return $header . html_writer::tag('div', $content, array('class' => 'chooserdialoguebody'));
01e0e704
ARN
337 }
338
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 }
360
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 }
380
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');
387
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 }
393
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');
407
408 return $output;
409 }
410
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 }
f558b291
MG
419
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 }
9a6aa5c1
MG
440
441 /**
442 * Renders HTML for the menus to add activities and resources to the current course
443 *
a03dfb7d
MG
444 * Note, if theme overwrites this function and it does not use modchooser,
445 * see also {@link core_course_renderer::add_modchoosertoggle()}
446 *
9a6aa5c1
MG
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;
456
457 $vertical = !empty($displayoptions['inblock']);
458
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 }
465
466 // Retrieve all modules with associated metadata
467 $modules = get_module_metadata($course, $modnames, $sectionreturn);
468 $urlparams = array('section' => $section);
469
470 // We'll sort resources and activities into two lists
471 $activities = array(MOD_CLASS_ACTIVITY => array(), MOD_CLASS_RESOURCE => array());
472
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 }
484
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 }
499
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);
505
506 $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
507
508 if (!$vertical) {
509 $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
510 }
511
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 }
518
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 }
525
526 if (!$vertical) {
527 $output .= html_writer::end_tag('div');
528 }
529
530 $output .= html_writer::end_tag('div');
531
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');
543
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 }
556
557 return $output;
558 }
7e29340f 559
f4b571ab
MG
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 }
573
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 }
588
589 $strsearchcourses= get_string("searchcourses");
590 $searchurl = new moodle_url('/course/search.php');
591
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');
601
602 return $output;
603 }
604
7e29340f
MG
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 }
633
634 $completiondata = $completioninfo->get_data($mod, true);
635 $completionicon = '';
636
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...
673
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),
f0989c3b
RT
697 'alt' => $imgalt, 'title' => $imgtitle,
698 'aria-live' => 'polite'));
7e29340f
MG
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 }
ed513fad
MG
711
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 }
732
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 }
758
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 }
774
775 $conditionalhidden = $this->is_cm_conditionally_hidden($mod);
776 $accessiblebutdim = !$mod->visible || $conditionalhidden;
777
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 }
793
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);
797
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 }
804
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 }
820
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 }
862
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) {
db82b130 895 return '<div class="availabilityinfo '.$hidinfoclass.'">'.get_string($mod->showavailability
ed513fad
MG
896 ? 'userrestriction_visible'
897 : 'userrestriction_hidden','condition',
898 $fullinfo).'</div>';
899 }
900 }
901 }
902 return '';
903 }
c58a25d6
MG
904
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()}
33919cca 919 *
c58a25d6
MG
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 }
944
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));
953
954 // Start the div for the activity title, excluding the edit icons.
955 $output .= html_writer::start_tag('div', array('class' => 'activityinstance'));
956
957 // Display the link to the module (or do nothing if module has no url)
958 $output .= $this->course_section_cm_name($mod, $displayoptions);
959
960 // Module can put text after the link (e.g. forum unread)
961 $output .= $mod->get_after_link();
962
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
965
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 }
977
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 }
983
984 $output .= $this->course_section_cm_completion($course, $completioninfo, $mod, $displayoptions);
985
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 }
991
992 // show availability info (if module is not available)
993 $output .= $this->course_section_cm_availability($mod, $displayoptions);
994
995 $output .= html_writer::end_tag('div'); // $indentclasses
996 return $output;
997 }
998
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;
1013
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);
1022
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 }
1029
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];
1035
1036 if ($ismoving and $mod->id == $USER->activitycopy) {
1037 // do not display moving mod
1038 continue;
1039 }
1040
1041 if ($modulehtml = $this->course_section_cm($course,
1042 $completioninfo, $mod, $sectionreturn, $displayoptions)) {
1043 $moduleshtml[$modnumber] = $modulehtml;
1044 }
1045 }
1046 }
1047
1048 if (!empty($moduleshtml) || $ismoving) {
1049
1050 $output .= html_writer::start_tag('ul', array('class' => 'section img-text'));
1051
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 }
1058
1059 $mod = $modinfo->cms[$modnumber];
8df0765a 1060 $modclasses = 'activity '. $mod->modname. ' modtype_'.$mod->modname. ' '. $mod->get_extra_classes();
c58a25d6
MG
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 }
1065
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 }
1071
1072 $output .= html_writer::end_tag('ul'); // .section
1073 }
1074
1075 return $output;
1076 }
f4c23f03 1077}