Merge branch 'MDL-33375' of git://github.com/danpoltawski/moodle
[moodle.git] / course / format / renderer.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Base renderer for outputting course formats.
19  *
20  * @package core
21  * @copyright 2012 Dan Poltawski
22  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  * @since Moodle 2.3
24  */
26 defined('MOODLE_INTERNAL') || die();
29 /**
30  * This is a convenience renderer which can be used by section based formats
31  * to reduce code duplication. It is not necessary for all course formats to
32  * use this and its likely to change in future releases.
33  *
34  * @package core
35  * @copyright 2012 Dan Poltawski
36  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  * @since Moodle 2.3
38  */
39 abstract class format_section_renderer_base extends plugin_renderer_base {
41     /**
42      * Generate the starting container html for a list of sections
43      * @return string HTML to output.
44      */
45     abstract protected function start_section_list();
47     /**
48      * Generate the closing container html for a list of sections
49      * @return string HTML to output.
50      */
51     abstract protected function end_section_list();
53     /**
54      * Generate the title for this section page
55      * @return string the page title
56      */
57     abstract protected function page_title();
59     /**
60      * Generate the section title
61      *
62      * @param stdClass $section The course_section entry from DB
63      * @param stdClass $course The course entry from DB
64      * @return string HTML to output.
65      */
66     public function section_title($section, $course) {
67         $title = get_section_name($course, $section);
68         if ($section->section != 0 && $course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
69             $title = html_writer::link(course_get_url($course, $section->section), $title);
70         }
71         return $title;
72     }
74     /**
75      * Generate the content to displayed on the right part of a section
76      * before course modules are included
77      *
78      * @param stdClass $section The course_section entry from DB
79      * @param stdClass $course The course entry from DB
80      * @param bool $onsectionpage true if being printed on a section page
81      * @return string HTML to output.
82      */
83     protected function section_right_content($section, $course, $onsectionpage) {
84         $o = $this->output->spacer();
86         if ($section->section != 0) {
87             $controls = $this->section_edit_controls($course, $section, $onsectionpage);
88             if (!empty($controls)) {
89                 $o = implode('<br />', $controls);
90             }
91         }
93         return $o;
94     }
96     /**
97      * Generate the content to displayed on the left part of a section
98      * before course modules are included
99      *
100      * @param stdClass $section The course_section entry from DB
101      * @param stdClass $course The course entry from DB
102      * @param bool $onsectionpage true if being printed on a section page
103      * @return string HTML to output.
104      */
105     protected function section_left_content($section, $course, $onsectionpage) {
106         $o = $this->output->spacer();
108         if ($section->section != 0) {
109             // Only in the non-general sections.
110             if ($this->is_section_current($section, $course)) {
111                 $o = get_accesshide(get_string('currentsection', 'format_'.$course->format));
112             }
113         }
115         return $o;
116     }
118     /**
119      * Generate the display of the header part of a section before
120      * course modules are included
121      *
122      * @param stdClass $section The course_section entry from DB
123      * @param stdClass $course The course entry from DB
124      * @param bool $onsectionpage true if being printed on a single-section page
125      * @return string HTML to output.
126      */
127     protected function section_header($section, $course, $onsectionpage) {
128         global $PAGE;
130         $o = '';
131         $currenttext = '';
132         $sectionstyle = '';
134         if ($section->section != 0) {
135             // Only in the non-general sections.
136             if (!$section->visible) {
137                 $sectionstyle = ' hidden';
138             } else if ($this->is_section_current($section, $course)) {
139                 $sectionstyle = ' current';
140             }
141         }
143         $o.= html_writer::start_tag('li', array('id' => 'section-'.$section->section,
144             'class' => 'section main clearfix'.$sectionstyle));
146         $leftcontent = $this->section_left_content($section, $course, $onsectionpage);
147         $o.= html_writer::tag('div', $leftcontent, array('class' => 'left side'));
149         $rightcontent = $this->section_right_content($section, $course, $onsectionpage);
150         $o.= html_writer::tag('div', $rightcontent, array('class' => 'right side'));
151         $o.= html_writer::start_tag('div', array('class' => 'content'));
153         if (!$onsectionpage) {
154             $o.= $this->output->heading($this->section_title($section, $course), 3, 'sectionname');
155         }
157         $o.= html_writer::start_tag('div', array('class' => 'summary'));
158         $o.= $this->format_summary_text($section);
160         $context = context_course::instance($course->id);
161         if ($PAGE->user_is_editing() && has_capability('moodle/course:update', $context)) {
162             $url = new moodle_url('/course/editsection.php', array('id'=>$section->id));
164             if ($onsectionpage) {
165                 $url->param('sectionreturn', 1);
166             }
168             $o.= html_writer::link($url,
169                 html_writer::empty_tag('img', array('src' => $this->output->pix_url('t/edit'), 'class' => 'iconsmall edit')),
170                 array('title' => get_string('editsummary')));
171         }
172         $o.= html_writer::end_tag('div');
174         $o .= $this->section_availability_message($section);
176         return $o;
177     }
179     /**
180      * Generate the display of the footer part of a section
181      *
182      * @return string HTML to output.
183      */
184     protected function section_footer() {
185         $o = html_writer::end_tag('div');
186         $o.= html_writer::end_tag('li');
188         return $o;
189     }
191     /**
192      * Generate the edit controls of a section
193      *
194      * @param stdClass $course The course entry from DB
195      * @param stdClass $section The course_section entry from DB
196      * @param bool $onsectionpage true if being printed on a section page
197      * @return array of links with edit controls
198      */
199     protected function section_edit_controls($course, $section, $onsectionpage = false) {
200         global $PAGE;
202         if (!$PAGE->user_is_editing()) {
203             return array();
204         }
206         if (!has_capability('moodle/course:update', context_course::instance($course->id))) {
207             return array();
208         }
210         if ($onsectionpage) {
211             $baseurl = course_get_url($course, $section->section);
212         } else {
213             $baseurl = course_get_url($course);
214         }
215         $baseurl->param('sesskey', sesskey());
217         $controls = array();
219         $url = clone($baseurl);
220         if ($section->visible) { // Show the hide/show eye.
221             $strhidefromothers = get_string('hidefromothers', 'format_'.$course->format);
222             $url->param('hide', $section->section);
223             $controls[] = html_writer::link($url,
224                 html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/hide'),
225                 'class' => 'icon hide', 'alt' => $strhidefromothers)),
226                 array('title' => $strhidefromothers, 'class' => 'editing_showhide'));
227         } else {
228             $strshowfromothers = get_string('showfromothers', 'format_'.$course->format);
229             $url->param('show',  $section->section);
230             $controls[] = html_writer::link($url,
231                 html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/show'),
232                 'class' => 'icon hide', 'alt' => $strshowfromothers)),
233                 array('title' => $strshowfromothers, 'class' => 'editing_showhide'));
234         }
236         if (!$onsectionpage) {
237             $url = clone($baseurl);
238             if ($section->section > 1) { // Add a arrow to move section up.
239                 $url->param('section', $section->section);
240                 $url->param('move', -1);
241                 $strmoveup = get_string('moveup');
243                 $controls[] = html_writer::link($url,
244                     html_writer::empty_tag('img', array('src' => $this->output->pix_url('t/up'),
245                     'class' => 'icon up', 'alt' => $strmoveup)),
246                     array('title' => $strmoveup, 'class' => 'moveup'));
247             }
249             $url = clone($baseurl);
250             if ($section->section < $course->numsections) { // Add a arrow to move section down.
251                 $url->param('section', $section->section);
252                 $url->param('move', 1);
253                 $strmovedown =  get_string('movedown');
255                 $controls[] = html_writer::link($url,
256                     html_writer::empty_tag('img', array('src' => $this->output->pix_url('t/down'),
257                     'class' => 'icon down', 'alt' => $strmovedown)),
258                     array('title' => $strmovedown, 'class' => 'movedown'));
259             }
260         }
262         return $controls;
263     }
265     /**
266      * Generate a summary of a section for display on the 'coruse index page'
267      *
268      * @param stdClass $section The course_section entry from DB
269      * @param stdClass $course The course entry from DB
270      * @return string HTML to output.
271      */
272     protected function section_summary($section, $course) {
273         // If section is hidden then display grey section link
274         $classattr = 'section-summary clearfix';
275         If (!$section->visible) {
276             $classattr .= ' dimmed_text';
277         }
279         $o = '';
280         $o.= html_writer::start_tag('li', array('id' => 'section-'.$section->section,
281             'class' => $classattr));
283         $title = get_section_name($course, $section);
284         $o.= html_writer::start_tag('a', array('href' => course_get_url($course, $section->section)));
285         $o.= $this->output->heading($title, 3, 'header section-title');
286         $o.= html_writer::end_tag('a');
288         $o.= html_writer::start_tag('div', array('class' => 'summarytext'));
289         $o.= $this->format_summary_text($section);
290         $o.= html_writer::end_tag('div');
292         $o .= $this->section_availability_message($section);
294         $o.= html_writer::end_tag('li');
296         return $o;
297     }
299     /**
300      * If section is not visible to current user, display the message about that
301      * ('Not available until...', that sort of thing). Otherwise, returns blank.
302      *
303      * @param stdClass $section The course_section entry from DB
304      * @return string HTML to output
305      */
306     protected function section_availability_message($section) {
307         $o = '';
308         if (!$section->uservisible || $section->availableinfo) {
309             $o .= html_writer::start_tag('div', array('class' => 'availabilityinfo'));
310             if (!empty($section->availableinfo)) {
311                 $o .= $section->availableinfo;
312             } else {
313                 $o .= get_string('notavailable');
314             }
315             $o .= html_writer::end_tag('div');
316         }
317         return $o;
318     }
320     /**
321      * Show if something is on on the course clipboard (moving around)
322      *
323      * @param stdClass $course The course entry from DB
324      * @param int $sectionno The section number in the coruse which is being dsiplayed
325      * @return string HTML to output.
326      */
327     protected function course_activity_clipboard($course, $sectionno = 0) {
328         global $USER;
330         $o = '';
331         // If currently moving a file then show the current clipboard.
332         if (ismoving($course->id)) {
333             $url = new moodle_url('/course/mod.php',
334                 array('sesskey' => sesskey(),
335                       'cancelcopy' => true,
336                       'sr' => $sectionno,
337                 )
338             );
340             $strcancel= get_string('cancel');
342             $o.= html_writer::start_tag('li', array('class' => 'clipboard'));
343             $o.= strip_tags(get_string('activityclipboard', '', $USER->activitycopyname));
344             $o.= ' ('.html_writer::link($url, get_string('cancel')).')';
345             $o.= html_writer::end_tag('li');
346         }
348         return $o;
349     }
351     /**
352      * Generate next/previous section links for naviation
353      *
354      * @param stdClass $course The course entry from DB
355      * @param array $sections The course_sections entries from the DB
356      * @param int $sectionno The section number in the coruse which is being dsiplayed
357      * @return array associative array with previous and next section link
358      */
359     protected function get_nav_links($course, $sections, $sectionno) {
360         // FIXME: This is really evil and should by using the navigation API.
361         $canviewhidden = has_capability('moodle/course:viewhiddensections', context_course::instance($course->id))
362             or !$course->hiddensections;
364         $links = array('previous' => '', 'next' => '');
365         $back = $sectionno - 1;
366         while ($back > 0 and empty($links['previous'])) {
367             if ($canviewhidden || $sections[$back]->visible) {
368                 $params = array();
369                 if (!$sections[$back]->visible) {
370                     $params = array('class' => 'dimmed_text');
371                 }
372                 $previouslink = html_writer::tag('span', $this->output->larrow(), array('class' => 'larrow'));
373                 $previouslink .= get_section_name($course, $sections[$back]);
374                 $links['previous'] = html_writer::link(course_get_url($course, $back), $previouslink, $params);
375             }
376             $back--;
377         }
379         $forward = $sectionno + 1;
380         while ($forward <= $course->numsections and empty($links['next'])) {
381             if ($canviewhidden || $sections[$forward]->visible) {
382                 $params = array();
383                 if (!$sections[$forward]->visible) {
384                     $params = array('class' => 'dimmed_text');
385                 }
386                 $nextlink = get_section_name($course, $sections[$forward]);
387                 $nextlink .= html_writer::tag('span', $this->output->rarrow(), array('class' => 'rarrow'));
388                 $links['next'] = html_writer::link(course_get_url($course, $forward), $nextlink, $params);
389             }
390             $forward++;
391         }
393         return $links;
394     }
396     /**
397      * Generate the header html of a stealth section
398      *
399      * @param int $sectionno The section number in the coruse which is being dsiplayed
400      * @return string HTML to output.
401      */
402     protected function stealth_section_header($sectionno) {
403         $o = '';
404         $o.= html_writer::start_tag('li', array('id' => 'section-'.$sectionno, 'class' => 'section main clearfix orphaned hidden'));
405         $o.= html_writer::tag('div', '', array('class' => 'left side'));
406         $o.= html_writer::tag('div', '', array('class' => 'right side'));
407         $o.= html_writer::start_tag('div', array('class' => 'content'));
408         $o.= $this->output->heading(get_string('orphanedactivities'), 3, 'sectionname');
409         return $o;
410     }
412     /**
413      * Generate footer html of a stealth section
414      *
415      * @return string HTML to output.
416      */
417     protected function stealth_section_footer() {
418         $o = html_writer::end_tag('div');
419         $o.= html_writer::end_tag('li');
420         return $o;
421     }
423     /**
424      * Generate the html for a hidden section
425      *
426      * @param int $sectionno The section number in the coruse which is being dsiplayed
427      * @return string HTML to output.
428      */
429     protected function section_hidden($sectionno) {
430         $o = '';
431         $o.= html_writer::start_tag('li', array('id' => 'section-'.$sectionno, 'class' => 'section main clearfix hidden'));
432         $o.= html_writer::tag('div', '', array('class' => 'left side'));
433         $o.= html_writer::tag('div', '', array('class' => 'right side'));
434         $o.= html_writer::start_tag('div', array('class' => 'content'));
435         $o.= get_string('notavailable');
436         $o.= html_writer::end_tag('div');
437         $o.= html_writer::end_tag('li');
438         return $o;
439     }
441     /**
442      * Output the html for a single section page .
443      *
444      * @param stdClass $course The course entry from DB
445      * @param array $sections The course_sections entries from the DB
446      * @param array $mods used for print_section()
447      * @param array $modnames used for print_section()
448      * @param array $modnamesused used for print_section()
449      * @param int $displaysection The section number in the course which is being displayed
450      */
451     public function print_single_section_page($course, $sections, $mods, $modnames, $modnamesused, $displaysection) {
452         global $PAGE;
454         // Can we view the section in question?
455         $context = context_course::instance($course->id);
456         $canviewhidden = has_capability('moodle/course:viewhiddensections', $context);
458         if (!isset($sections[$displaysection])) {
459             // This section doesn't exist
460             print_error('unknowncoursesection', 'error', null, $course->fullname);
461             return;
462         }
464         if (!$sections[$displaysection]->visible && !$canviewhidden) {
465             if (!$course->hiddensections) {
466                 echo $this->start_section_list();
467                 echo $this->section_hidden($displaysection);
468                 echo $this->end_section_list();
469             }
470             // Can't view this section.
471             return;
472         }
474         // General section if non-empty.
475         $thissection = $sections[0];
476         if ($thissection->summary or $thissection->sequence or $PAGE->user_is_editing()) {
477             echo $this->start_section_list();
478             echo $this->section_header($thissection, $course, true);
479             print_section($course, $thissection, $mods, $modnamesused, true);
480             if ($PAGE->user_is_editing()) {
481                 print_section_add_menus($course, 0, $modnames);
482             }
483             echo $this->section_footer();
484             echo $this->end_section_list();
485         }
487         // Start single-section div
488         echo html_writer::start_tag('div', array('class' => 'single-section'));
490         // Title with section navigation links.
491         $sectionnavlinks = $this->get_nav_links($course, $sections, $displaysection);
492         $sectiontitle = '';
493         $sectiontitle .= html_writer::start_tag('div', array('class' => 'section-navigation header headingblock'));
494         $sectiontitle .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
495         $sectiontitle .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
496         // Title attributes
497         $titleattr = 'mdl-align title';
498         if (!$sections[$displaysection]->visible) {
499             $titleattr .= ' dimmed_text';
500         }
501         $sectiontitle .= html_writer::tag('div', get_section_name($course, $sections[$displaysection]), array('class' => $titleattr));
502         $sectiontitle .= html_writer::end_tag('div');
503         echo $sectiontitle;
505         // Copy activity clipboard..
506         echo $this->course_activity_clipboard($course, $displaysection);
508         // Now the list of sections..
509         echo $this->start_section_list();
511         // The requested section page.
512         $thissection = $sections[$displaysection];
513         echo $this->section_header($thissection, $course, true);
514         // Show completion help icon.
515         $completioninfo = new completion_info($course);
516         echo $completioninfo->display_help_icon();
518         print_section($course, $thissection, $mods, $modnamesused, true);
519         if ($PAGE->user_is_editing()) {
520             print_section_add_menus($course, $displaysection, $modnames);
521         }
522         echo $this->section_footer();
523         echo $this->end_section_list();
525         // Display section bottom navigation.
526         $courselink = html_writer::link(course_get_url($course), get_string('returntomaincoursepage'));
527         $sectionbottomnav = '';
528         $sectionbottomnav .= html_writer::start_tag('div', array('class' => 'section-navigation mdl-bottom'));
529         $sectionbottomnav .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
530         $sectionbottomnav .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
531         $sectionbottomnav .= html_writer::tag('div', $courselink, array('class' => 'mdl-align'));
532         $sectionbottomnav .= html_writer::end_tag('div');
533         echo $sectionbottomnav;
535         // close single-section div.
536         echo html_writer::end_tag('div');
537     }
539     /**
540      * Output the html for a multiple section page
541      *
542      * @param stdClass $course The course entry from DB
543      * @param array $sections The course_sections entries from the DB
544      * @param array $mods used for print_section()
545      * @param array $modnames used for print_section()
546      * @param array $modnamesused used for print_section()
547      */
548     public function print_multiple_section_page($course, $sections, $mods, $modnames, $modnamesused) {
549         global $PAGE;
551         $context = context_course::instance($course->id);
552         // Title with completion help icon.
553         $completioninfo = new completion_info($course);
554         echo $completioninfo->display_help_icon();
555         echo $this->output->heading($this->page_title(), 2, 'accesshide');
557         // Copy activity clipboard..
558         echo $this->course_activity_clipboard($course);
560         // Now the list of sections..
561         echo $this->start_section_list();
563         // General section if non-empty.
564         $thissection = $sections[0];
565         unset($sections[0]);
566         if ($thissection->summary or $thissection->sequence or $PAGE->user_is_editing()) {
567             echo $this->section_header($thissection, $course, true);
568             print_section($course, $thissection, $mods, $modnamesused, true);
569             if ($PAGE->user_is_editing()) {
570                 print_section_add_menus($course, 0, $modnames);
571             }
572             echo $this->section_footer();
573         }
575         $canviewhidden = has_capability('moodle/course:viewhiddensections', $context);
576         for ($section = 1; $section <= $course->numsections; $section++) {
577             if (!empty($sections[$section])) {
578                 $thissection = $sections[$section];
579             } else {
580                 // This will create a course section if it doesn't exist..
581                 $thissection = get_course_section($section, $course->id);
583                 // The returned section is only a bare database object rather than
584                 // a section_info object - we will need at least the uservisible
585                 // field in it.
586                 $thissection->uservisible = true;
587                 $thissection->availableinfo = null;
588                 $thissection->showavailability = 0;
589             }
590             // Show the section if the user is permitted to access it, OR if it's not available
591             // but showavailability is turned on
592             $showsection = $thissection->uservisible ||
593                     ($thissection->visible && !$thissection->available && $thissection->showavailability);
594             if (!$showsection) {
595                 // Hidden section message is overridden by 'unavailable' control
596                 // (showavailability option).
597                 if (!$course->hiddensections && $thissection->available) {
598                     echo $this->section_hidden($section);
599                 }
601                 unset($sections[$section]);
602                 continue;
603             }
605             if (!$PAGE->user_is_editing() && $course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
606                 // Display section summary only.
607                 echo $this->section_summary($thissection, $course);
608             } else {
609                 echo $this->section_header($thissection, $course, false);
610                 if ($thissection->uservisible) {
611                     print_section($course, $thissection, $mods, $modnamesused);
612                     if ($PAGE->user_is_editing()) {
613                         print_section_add_menus($course, $section, $modnames);
614                     }
615                 }
616                 echo $this->section_footer();
617             }
619             unset($sections[$section]);
620         }
622         if ($PAGE->user_is_editing() and has_capability('moodle/course:update', $context)) {
623             // Print stealth sections if present.
624             $modinfo = get_fast_modinfo($course);
625             foreach ($sections as $section => $thissection) {
626                 if (empty($modinfo->sections[$section])) {
627                     continue;
628                 }
629                 echo $this->stealth_section_header($section);
630                 print_section($course, $thissection, $mods, $modnamesused);
631                 echo $this->stealth_section_footer();
632             }
634             echo $this->end_section_list();
636             echo html_writer::start_tag('div', array('id' => 'changenumsections', 'class' => 'mdl-right'));
638             // Increase number of sections.
639             $straddsection = get_string('increasesections', 'moodle');
640             $url = new moodle_url('/course/changenumsections.php',
641                 array('courseid' => $course->id,
642                       'increase' => true,
643                       'sesskey' => sesskey()));
644             $icon = $this->output->pix_icon('t/switch_plus', $straddsection);
645             echo html_writer::link($url, $icon.get_accesshide($straddsection), array('class' => 'increase-sections'));
647             if ($course->numsections > 0) {
648                 // Reduce number of sections sections.
649                 $strremovesection = get_string('reducesections', 'moodle');
650                 $url = new moodle_url('/course/changenumsections.php',
651                     array('courseid' => $course->id,
652                           'increase' => false,
653                           'sesskey' => sesskey()));
654                 $icon = $this->output->pix_icon('t/switch_minus', $strremovesection);
655                 echo html_writer::link($url, $icon.get_accesshide($strremovesection), array('class' => 'reduce-sections'));
656             }
658             echo html_writer::end_tag('div');
659         } else {
660             echo $this->end_section_list();
661         }
663     }
665     /**
666      * Generate html for a section summary text
667      *
668      * @param stdClass $section The course_section entry from DB
669      * @return string HTML to output.
670      */
671     protected function format_summary_text($section) {
672         $context = context_course::instance($section->course);
673         $summarytext = file_rewrite_pluginfile_urls($section->summary, 'pluginfile.php',
674             $context->id, 'course', 'section', $section->id);
676         $options = new stdClass();
677         $options->noclean = true;
678         $options->overflowdiv = true;
679         return format_text($summarytext, $section->summaryformat, $options);
680     }
682     /**
683      * Is the section passed in the current section? (Note this isn't strictly
684      * a renderering method, but neater here).
685      *
686      * @param stdClass $course The course entry from DB
687      * @param stdClass $section The course_section entry from the DB
688      * @return bool true if the section is current
689      */
690     protected function is_section_current($section, $course) {
691         return ($course->marker == $section->section);
692     }