Merge branch 'MDL-68189' of https://github.com/NeillM/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     /** @var core_course_renderer contains instance of core course renderer */
42     protected $courserenderer;
44     /**
45      * Constructor method, calls the parent constructor
46      *
47      * @param moodle_page $page
48      * @param string $target one of rendering target constants
49      */
50     public function __construct(moodle_page $page, $target) {
51         parent::__construct($page, $target);
52         $this->courserenderer = $this->page->get_renderer('core', 'course');
53     }
55     /**
56      * Generate the starting container html for a list of sections
57      * @return string HTML to output.
58      */
59     abstract protected function start_section_list();
61     /**
62      * Generate the closing container html for a list of sections
63      * @return string HTML to output.
64      */
65     abstract protected function end_section_list();
67     /**
68      * Generate the title for this section page
69      * @return string the page title
70      */
71     abstract protected function page_title();
73     /**
74      * Generate the section title, wraps it in a link to the section page if page is to be displayed on a separate page
75      *
76      * @param stdClass $section The course_section entry from DB
77      * @param stdClass $course The course entry from DB
78      * @return string HTML to output.
79      */
80     public function section_title($section, $course) {
81         $title = get_section_name($course, $section);
82         $url = course_get_url($course, $section->section, array('navigation' => true));
83         if ($url) {
84             $title = html_writer::link($url, $title);
85         }
86         return $title;
87     }
89     /**
90      * Generate the section title to be displayed on the section page, without a link
91      *
92      * @param stdClass $section The course_section entry from DB
93      * @param stdClass $course The course entry from DB
94      * @return string HTML to output.
95      */
96     public function section_title_without_link($section, $course) {
97         return get_section_name($course, $section);
98     }
100     /**
101      * Generate the edit control action menu
102      *
103      * @param array $controls The edit control items from section_edit_control_items
104      * @param stdClass $course The course entry from DB
105      * @param stdClass $section The course_section entry from DB
106      * @return string HTML to output.
107      */
108     protected function section_edit_control_menu($controls, $course, $section) {
109         $o = "";
110         if (!empty($controls)) {
111             $menu = new action_menu();
112             $menu->set_menu_trigger(get_string('edit'));
113             $menu->attributes['class'] .= ' section-actions';
114             foreach ($controls as $value) {
115                 $url = empty($value['url']) ? '' : $value['url'];
116                 $icon = empty($value['icon']) ? '' : $value['icon'];
117                 $name = empty($value['name']) ? '' : $value['name'];
118                 $attr = empty($value['attr']) ? array() : $value['attr'];
119                 $class = empty($value['pixattr']['class']) ? '' : $value['pixattr']['class'];
120                 $al = new action_menu_link_secondary(
121                     new moodle_url($url),
122                     new pix_icon($icon, '', null, array('class' => "smallicon " . $class)),
123                     $name,
124                     $attr
125                 );
126                 $menu->add($al);
127             }
129             $o .= html_writer::div($this->render($menu), 'section_action_menu',
130                 array('data-sectionid' => $section->id));
131         }
133         return $o;
134     }
136     /**
137      * Generate the content to displayed on the right part of a section
138      * before course modules are included
139      *
140      * @param stdClass $section The course_section entry from DB
141      * @param stdClass $course The course entry from DB
142      * @param bool $onsectionpage true if being printed on a section page
143      * @return string HTML to output.
144      */
145     protected function section_right_content($section, $course, $onsectionpage) {
146         $o = $this->output->spacer();
148         $controls = $this->section_edit_control_items($course, $section, $onsectionpage);
149         $o .= $this->section_edit_control_menu($controls, $course, $section);
151         return $o;
152     }
154     /**
155      * Generate the content to displayed on the left part of a section
156      * before course modules are included
157      *
158      * @param stdClass $section The course_section entry from DB
159      * @param stdClass $course The course entry from DB
160      * @param bool $onsectionpage true if being printed on a section page
161      * @return string HTML to output.
162      */
163     protected function section_left_content($section, $course, $onsectionpage) {
164         $o = '';
166         if ($section->section != 0) {
167             // Only in the non-general sections.
168             if (course_get_format($course)->is_section_current($section)) {
169                 $o = get_accesshide(get_string('currentsection', 'format_'.$course->format));
170             }
171         }
173         return $o;
174     }
176     /**
177      * Generate the display of the header part of a section before
178      * course modules are included
179      *
180      * @param stdClass $section The course_section entry from DB
181      * @param stdClass $course The course entry from DB
182      * @param bool $onsectionpage true if being printed on a single-section page
183      * @param int $sectionreturn The section to return to after an action
184      * @return string HTML to output.
185      */
186     protected function section_header($section, $course, $onsectionpage, $sectionreturn=null) {
187         $o = '';
188         $currenttext = '';
189         $sectionstyle = '';
191         if ($section->section != 0) {
192             // Only in the non-general sections.
193             if (!$section->visible) {
194                 $sectionstyle = ' hidden';
195             }
196             if (course_get_format($course)->is_section_current($section)) {
197                 $sectionstyle = ' current';
198             }
199         }
201         $o.= html_writer::start_tag('li', array('id' => 'section-'.$section->section,
202             'class' => 'section main clearfix'.$sectionstyle, 'role'=>'region',
203             'aria-labelledby' => "sectionid-{$section->id}-title"));
205         $leftcontent = $this->section_left_content($section, $course, $onsectionpage);
206         $o.= html_writer::tag('div', $leftcontent, array('class' => 'left side'));
208         $rightcontent = $this->section_right_content($section, $course, $onsectionpage);
209         $o.= html_writer::tag('div', $rightcontent, array('class' => 'right side'));
210         $o.= html_writer::start_tag('div', array('class' => 'content'));
212         // When not on a section page, we display the section titles except the general section if null
213         $hasnamenotsecpg = (!$onsectionpage && ($section->section != 0 || !is_null($section->name)));
215         // When on a section page, we only display the general section title, if title is not the default one
216         $hasnamesecpg = ($onsectionpage && ($section->section == 0 && !is_null($section->name)));
218         $classes = ' accesshide';
219         if ($hasnamenotsecpg || $hasnamesecpg) {
220             $classes = '';
221         }
222         $sectionname = html_writer::tag('span', $this->section_title($section, $course));
223         $o .= $this->output->heading($sectionname, 3, 'sectionname' . $classes, "sectionid-{$section->id}-title");
225         $o .= $this->section_availability($section);
227         $o .= html_writer::start_tag('div', array('class' => 'summary'));
228         if ($section->uservisible || $section->visible) {
229             // Show summary if section is available or has availability restriction information.
230             // Do not show summary if section is hidden but we still display it because of course setting
231             // "Hidden sections are shown in collapsed form".
232             $o .= $this->format_summary_text($section);
233         }
234         $o .= html_writer::end_tag('div');
236         return $o;
237     }
239     /**
240      * Generate the display of the footer part of a section
241      *
242      * @return string HTML to output.
243      */
244     protected function section_footer() {
245         $o = html_writer::end_tag('div');
246         $o.= html_writer::end_tag('li');
248         return $o;
249     }
251     /**
252      * @deprecated since Moodle 3.0 MDL-48947 - Use format_section_renderer_base::section_edit_control_items() instead
253      */
254     protected function section_edit_controls() {
255         throw new coding_exception('section_edit_controls() can not be used anymore. Please use ' .
256             'section_edit_control_items() instead.');
257     }
259     /**
260      * Generate the edit control items of a section
261      *
262      * @param stdClass $course The course entry from DB
263      * @param stdClass $section The course_section entry from DB
264      * @param bool $onsectionpage true if being printed on a section page
265      * @return array of edit control items
266      */
267     protected function section_edit_control_items($course, $section, $onsectionpage = false) {
268         if (!$this->page->user_is_editing()) {
269             return array();
270         }
272         $sectionreturn = $onsectionpage ? $section->section : null;
274         $coursecontext = context_course::instance($course->id);
275         $numsections = course_get_format($course)->get_last_section_number();
276         $isstealth = $section->section > $numsections;
278         $baseurl = course_get_url($course, $sectionreturn);
279         $baseurl->param('sesskey', sesskey());
281         $controls = array();
283         if (!$isstealth && has_capability('moodle/course:update', $coursecontext)) {
284             if ($section->section > 0
285                 && get_string_manager()->string_exists('editsection', 'format_'.$course->format)) {
286                 $streditsection = get_string('editsection', 'format_'.$course->format);
287             } else {
288                 $streditsection = get_string('editsection');
289             }
291             $controls['edit'] = array(
292                 'url'   => new moodle_url('/course/editsection.php', array('id' => $section->id, 'sr' => $sectionreturn)),
293                 'icon' => 'i/settings',
294                 'name' => $streditsection,
295                 'pixattr' => array('class' => ''),
296                 'attr' => array('class' => 'icon edit'));
297         }
299         if ($section->section) {
300             $url = clone($baseurl);
301             if (!$isstealth) {
302                 if (has_capability('moodle/course:sectionvisibility', $coursecontext)) {
303                     if ($section->visible) { // Show the hide/show eye.
304                         $strhidefromothers = get_string('hidefromothers', 'format_'.$course->format);
305                         $url->param('hide', $section->section);
306                         $controls['visiblity'] = array(
307                             'url' => $url,
308                             'icon' => 'i/hide',
309                             'name' => $strhidefromothers,
310                             'pixattr' => array('class' => ''),
311                             'attr' => array('class' => 'icon editing_showhide',
312                                 'data-sectionreturn' => $sectionreturn, 'data-action' => 'hide'));
313                     } else {
314                         $strshowfromothers = get_string('showfromothers', 'format_'.$course->format);
315                         $url->param('show',  $section->section);
316                         $controls['visiblity'] = array(
317                             'url' => $url,
318                             'icon' => 'i/show',
319                             'name' => $strshowfromothers,
320                             'pixattr' => array('class' => ''),
321                             'attr' => array('class' => 'icon editing_showhide',
322                                 'data-sectionreturn' => $sectionreturn, 'data-action' => 'show'));
323                     }
324                 }
326                 if (!$onsectionpage) {
327                     if (has_capability('moodle/course:movesections', $coursecontext)) {
328                         $url = clone($baseurl);
329                         if ($section->section > 1) { // Add a arrow to move section up.
330                             $url->param('section', $section->section);
331                             $url->param('move', -1);
332                             $strmoveup = get_string('moveup');
333                             $controls['moveup'] = array(
334                                 'url' => $url,
335                                 'icon' => 'i/up',
336                                 'name' => $strmoveup,
337                                 'pixattr' => array('class' => ''),
338                                 'attr' => array('class' => 'icon moveup'));
339                         }
341                         $url = clone($baseurl);
342                         if ($section->section < $numsections) { // Add a arrow to move section down.
343                             $url->param('section', $section->section);
344                             $url->param('move', 1);
345                             $strmovedown = get_string('movedown');
346                             $controls['movedown'] = array(
347                                 'url' => $url,
348                                 'icon' => 'i/down',
349                                 'name' => $strmovedown,
350                                 'pixattr' => array('class' => ''),
351                                 'attr' => array('class' => 'icon movedown'));
352                         }
353                     }
354                 }
355             }
357             if (course_can_delete_section($course, $section)) {
358                 if (get_string_manager()->string_exists('deletesection', 'format_'.$course->format)) {
359                     $strdelete = get_string('deletesection', 'format_'.$course->format);
360                 } else {
361                     $strdelete = get_string('deletesection');
362                 }
363                 $url = new moodle_url('/course/editsection.php', array(
364                     'id' => $section->id,
365                     'sr' => $sectionreturn,
366                     'delete' => 1,
367                     'sesskey' => sesskey()));
368                 $controls['delete'] = array(
369                     'url' => $url,
370                     'icon' => 'i/delete',
371                     'name' => $strdelete,
372                     'pixattr' => array('class' => ''),
373                     'attr' => array('class' => 'icon editing_delete'));
374             }
375         }
377         return $controls;
378     }
380     /**
381      * Generate a summary of a section for display on the 'course index page'
382      *
383      * @param stdClass $section The course_section entry from DB
384      * @param stdClass $course The course entry from DB
385      * @param array    $mods (argument not used)
386      * @return string HTML to output.
387      */
388     protected function section_summary($section, $course, $mods) {
389         $classattr = 'section main section-summary clearfix';
390         $linkclasses = '';
392         // If section is hidden then display grey section link
393         if (!$section->visible) {
394             $classattr .= ' hidden';
395             $linkclasses .= ' dimmed_text';
396         } else if (course_get_format($course)->is_section_current($section)) {
397             $classattr .= ' current';
398         }
400         $title = get_section_name($course, $section);
401         $o = '';
402         $o .= html_writer::start_tag('li', array('id' => 'section-'.$section->section,
403             'class' => $classattr, 'role'=>'region', 'aria-label'=> $title));
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'));
409         if ($section->uservisible) {
410             $title = html_writer::tag('a', $title,
411                     array('href' => course_get_url($course, $section->section), 'class' => $linkclasses));
412         }
413         $o .= $this->output->heading($title, 3, 'section-title');
415         $o .= $this->section_availability($section);
416         $o.= html_writer::start_tag('div', array('class' => 'summarytext'));
418         if ($section->uservisible || $section->visible) {
419             // Show summary if section is available or has availability restriction information.
420             // Do not show summary if section is hidden but we still display it because of course setting
421             // "Hidden sections are shown in collapsed form".
422             $o .= $this->format_summary_text($section);
423         }
424         $o.= html_writer::end_tag('div');
425         $o.= $this->section_activity_summary($section, $course, null);
427         $o .= html_writer::end_tag('div');
428         $o .= html_writer::end_tag('li');
430         return $o;
431     }
433     /**
434      * Generate a summary of the activites in a section
435      *
436      * @param stdClass $section The course_section entry from DB
437      * @param stdClass $course the course record from DB
438      * @param array    $mods (argument not used)
439      * @return string HTML to output.
440      */
441     protected function section_activity_summary($section, $course, $mods) {
442         $modinfo = get_fast_modinfo($course);
443         if (empty($modinfo->sections[$section->section])) {
444             return '';
445         }
447         // Generate array with count of activities in this section:
448         $sectionmods = array();
449         $total = 0;
450         $complete = 0;
451         $cancomplete = isloggedin() && !isguestuser();
452         $completioninfo = new completion_info($course);
453         foreach ($modinfo->sections[$section->section] as $cmid) {
454             $thismod = $modinfo->cms[$cmid];
456             if ($thismod->uservisible) {
457                 if (isset($sectionmods[$thismod->modname])) {
458                     $sectionmods[$thismod->modname]['name'] = $thismod->modplural;
459                     $sectionmods[$thismod->modname]['count']++;
460                 } else {
461                     $sectionmods[$thismod->modname]['name'] = $thismod->modfullname;
462                     $sectionmods[$thismod->modname]['count'] = 1;
463                 }
464                 if ($cancomplete && $completioninfo->is_enabled($thismod) != COMPLETION_TRACKING_NONE) {
465                     $total++;
466                     $completiondata = $completioninfo->get_data($thismod, true);
467                     if ($completiondata->completionstate == COMPLETION_COMPLETE ||
468                             $completiondata->completionstate == COMPLETION_COMPLETE_PASS) {
469                         $complete++;
470                     }
471                 }
472             }
473         }
475         if (empty($sectionmods)) {
476             // No sections
477             return '';
478         }
480         // Output section activities summary:
481         $o = '';
482         $o.= html_writer::start_tag('div', array('class' => 'section-summary-activities pr-2 mdl-right'));
483         foreach ($sectionmods as $mod) {
484             $o.= html_writer::start_tag('span', array('class' => 'activity-count'));
485             $o.= $mod['name'].': '.$mod['count'];
486             $o.= html_writer::end_tag('span');
487         }
488         $o.= html_writer::end_tag('div');
490         // Output section completion data
491         if ($total > 0) {
492             $a = new stdClass;
493             $a->complete = $complete;
494             $a->total = $total;
496             $o.= html_writer::start_tag('div', array('class' => 'section-summary-activities pr-2 mdl-right'));
497             $o.= html_writer::tag('span', get_string('progresstotal', 'completion', $a), array('class' => 'activity-count'));
498             $o.= html_writer::end_tag('div');
499         }
501         return $o;
502     }
504     /**
505      * If section is not visible, display the message about that ('Not available
506      * until...', that sort of thing). Otherwise, returns blank.
507      *
508      * For users with the ability to view hidden sections, it shows the
509      * information even though you can view the section and also may include
510      * slightly fuller information (so that teachers can tell when sections
511      * are going to be unavailable etc). This logic is the same as for
512      * activities.
513      *
514      * @param section_info $section The course_section entry from DB
515      * @param bool $canviewhidden True if user can view hidden sections
516      * @return string HTML to output
517      */
518     protected function section_availability_message($section, $canviewhidden) {
519         global $CFG;
520         $o = '';
521         if (!$section->visible) {
522             if ($canviewhidden) {
523                 $o .= $this->courserenderer->availability_info(get_string('hiddenfromstudents'), 'ishidden');
524             } else {
525                 // We are here because of the setting "Hidden sections are shown in collapsed form".
526                 // Student can not see the section contents but can see its name.
527                 $o .= $this->courserenderer->availability_info(get_string('notavailable'), 'ishidden');
528             }
529         } else if (!$section->uservisible) {
530             if ($section->availableinfo) {
531                 // Note: We only get to this function if availableinfo is non-empty,
532                 // so there is definitely something to print.
533                 $formattedinfo = \core_availability\info::format_info(
534                         $section->availableinfo, $section->course);
535                 $o .= $this->courserenderer->availability_info($formattedinfo, 'isrestricted');
536             }
537         } else if ($canviewhidden && !empty($CFG->enableavailability)) {
538             // Check if there is an availability restriction.
539             $ci = new \core_availability\info_section($section);
540             $fullinfo = $ci->get_full_information();
541             if ($fullinfo) {
542                 $formattedinfo = \core_availability\info::format_info(
543                         $fullinfo, $section->course);
544                 $o .= $this->courserenderer->availability_info($formattedinfo, 'isrestricted isfullinfo');
545             }
546         }
547         return $o;
548     }
550     /**
551      * Displays availability information for the section (hidden, not available unles, etc.)
552      *
553      * @param section_info $section
554      * @return string
555      */
556     public function section_availability($section) {
557         $context = context_course::instance($section->course);
558         $canviewhidden = has_capability('moodle/course:viewhiddensections', $context);
559         return html_writer::div($this->section_availability_message($section, $canviewhidden), 'section_availability');
560     }
562     /**
563      * Show if something is on on the course clipboard (moving around)
564      *
565      * @param stdClass $course The course entry from DB
566      * @param int $sectionno The section number in the course which is being displayed
567      * @return string HTML to output.
568      */
569     protected function course_activity_clipboard($course, $sectionno = null) {
570         global $USER;
572         $o = '';
573         // If currently moving a file then show the current clipboard.
574         if (ismoving($course->id)) {
575             $url = new moodle_url('/course/mod.php',
576                 array('sesskey' => sesskey(),
577                       'cancelcopy' => true,
578                       'sr' => $sectionno,
579                 )
580             );
582             $o.= html_writer::start_tag('div', array('class' => 'clipboard'));
583             $o.= strip_tags(get_string('activityclipboard', '', $USER->activitycopyname));
584             $o.= ' ('.html_writer::link($url, get_string('cancel')).')';
585             $o.= html_writer::end_tag('div');
586         }
588         return $o;
589     }
591     /**
592      * Generate next/previous section links for naviation
593      *
594      * @param stdClass $course The course entry from DB
595      * @param array $sections The course_sections entries from the DB
596      * @param int $sectionno The section number in the course which is being displayed
597      * @return array associative array with previous and next section link
598      */
599     protected function get_nav_links($course, $sections, $sectionno) {
600         // FIXME: This is really evil and should by using the navigation API.
601         $course = course_get_format($course)->get_course();
602         $canviewhidden = has_capability('moodle/course:viewhiddensections', context_course::instance($course->id))
603             or !$course->hiddensections;
605         $links = array('previous' => '', 'next' => '');
606         $back = $sectionno - 1;
607         while ($back > 0 and empty($links['previous'])) {
608             if ($canviewhidden || $sections[$back]->uservisible) {
609                 $params = array();
610                 if (!$sections[$back]->visible) {
611                     $params = array('class' => 'dimmed_text');
612                 }
613                 $previouslink = html_writer::tag('span', $this->output->larrow(), array('class' => 'larrow'));
614                 $previouslink .= get_section_name($course, $sections[$back]);
615                 $links['previous'] = html_writer::link(course_get_url($course, $back), $previouslink, $params);
616             }
617             $back--;
618         }
620         $forward = $sectionno + 1;
621         $numsections = course_get_format($course)->get_last_section_number();
622         while ($forward <= $numsections and empty($links['next'])) {
623             if ($canviewhidden || $sections[$forward]->uservisible) {
624                 $params = array();
625                 if (!$sections[$forward]->visible) {
626                     $params = array('class' => 'dimmed_text');
627                 }
628                 $nextlink = get_section_name($course, $sections[$forward]);
629                 $nextlink .= html_writer::tag('span', $this->output->rarrow(), array('class' => 'rarrow'));
630                 $links['next'] = html_writer::link(course_get_url($course, $forward), $nextlink, $params);
631             }
632             $forward++;
633         }
635         return $links;
636     }
638     /**
639      * Generate the header html of a stealth section
640      *
641      * @param int $sectionno The section number in the course which is being displayed
642      * @return string HTML to output.
643      */
644     protected function stealth_section_header($sectionno) {
645         $o = '';
646         $o.= html_writer::start_tag('li', array('id' => 'section-'.$sectionno, 'class' => 'section main clearfix orphaned hidden'));
647         $o.= html_writer::tag('div', '', array('class' => 'left side'));
648         $course = course_get_format($this->page->course)->get_course();
649         $section = course_get_format($this->page->course)->get_section($sectionno);
650         $rightcontent = $this->section_right_content($section, $course, false);
651         $o.= html_writer::tag('div', $rightcontent, array('class' => 'right side'));
652         $o.= html_writer::start_tag('div', array('class' => 'content'));
653         $o.= $this->output->heading(get_string('orphanedactivitiesinsectionno', '', $sectionno), 3, 'sectionname');
654         return $o;
655     }
657     /**
658      * Generate footer html of a stealth section
659      *
660      * @return string HTML to output.
661      */
662     protected function stealth_section_footer() {
663         $o = html_writer::end_tag('div');
664         $o.= html_writer::end_tag('li');
665         return $o;
666     }
668     /**
669      * Generate the html for a hidden section
670      *
671      * @param int $sectionno The section number in the course which is being displayed
672      * @param int|stdClass $courseorid The course to get the section name for (object or just course id)
673      * @return string HTML to output.
674      */
675     protected function section_hidden($sectionno, $courseorid = null) {
676         if ($courseorid) {
677             $sectionname = get_section_name($courseorid, $sectionno);
678             $strnotavailable = get_string('notavailablecourse', '', $sectionname);
679         } else {
680             $strnotavailable = get_string('notavailable');
681         }
683         $o = '';
684         $o.= html_writer::start_tag('li', array('id' => 'section-'.$sectionno, 'class' => 'section main clearfix hidden'));
685         $o.= html_writer::tag('div', '', array('class' => 'left side'));
686         $o.= html_writer::tag('div', '', array('class' => 'right side'));
687         $o.= html_writer::start_tag('div', array('class' => 'content'));
688         $o.= html_writer::tag('div', $strnotavailable);
689         $o.= html_writer::end_tag('div');
690         $o.= html_writer::end_tag('li');
691         return $o;
692     }
694     /**
695      * Generate the html for the 'Jump to' menu on a single section page.
696      *
697      * @param stdClass $course The course entry from DB
698      * @param array $sections The course_sections entries from the DB
699      * @param $displaysection the current displayed section number.
700      *
701      * @return string HTML to output.
702      */
703     protected function section_nav_selection($course, $sections, $displaysection) {
704         global $CFG;
705         $o = '';
706         $sectionmenu = array();
707         $sectionmenu[course_get_url($course)->out(false)] = get_string('maincoursepage');
708         $modinfo = get_fast_modinfo($course);
709         $section = 1;
710         $numsections = course_get_format($course)->get_last_section_number();
711         while ($section <= $numsections) {
712             $thissection = $modinfo->get_section_info($section);
713             $showsection = $thissection->uservisible or !$course->hiddensections;
714             if (($showsection) && ($section != $displaysection) && ($url = course_get_url($course, $section))) {
715                 $sectionmenu[$url->out(false)] = get_section_name($course, $section);
716             }
717             $section++;
718         }
720         $select = new url_select($sectionmenu, '', array('' => get_string('jumpto')));
721         $select->class = 'jumpmenu';
722         $select->formid = 'sectionmenu';
723         $o .= $this->output->render($select);
725         return $o;
726     }
728     /**
729      * Output the html for a single section page .
730      *
731      * @param stdClass $course The course entry from DB
732      * @param array $sections (argument not used)
733      * @param array $mods (argument not used)
734      * @param array $modnames (argument not used)
735      * @param array $modnamesused (argument not used)
736      * @param int $displaysection The section number in the course which is being displayed
737      */
738     public function print_single_section_page($course, $sections, $mods, $modnames, $modnamesused, $displaysection) {
739         $modinfo = get_fast_modinfo($course);
740         $course = course_get_format($course)->get_course();
742         // Can we view the section in question?
743         if (!($sectioninfo = $modinfo->get_section_info($displaysection)) || !$sectioninfo->uservisible) {
744             // This section doesn't exist or is not available for the user.
745             // We actually already check this in course/view.php but just in case exit from this function as well.
746             print_error('unknowncoursesection', 'error', course_get_url($course),
747                 format_string($course->fullname));
748         }
750         // Copy activity clipboard..
751         echo $this->course_activity_clipboard($course, $displaysection);
752         $thissection = $modinfo->get_section_info(0);
753         if ($thissection->summary or !empty($modinfo->sections[0]) or $this->page->user_is_editing()) {
754             echo $this->start_section_list();
755             echo $this->section_header($thissection, $course, true, $displaysection);
756             echo $this->courserenderer->course_section_cm_list($course, $thissection, $displaysection);
757             echo $this->courserenderer->course_section_add_cm_control($course, 0, $displaysection);
758             echo $this->section_footer();
759             echo $this->end_section_list();
760         }
762         // Start single-section div
763         echo html_writer::start_tag('div', array('class' => 'single-section'));
765         // The requested section page.
766         $thissection = $modinfo->get_section_info($displaysection);
768         // Title with section navigation links.
769         $sectionnavlinks = $this->get_nav_links($course, $modinfo->get_section_info_all(), $displaysection);
770         $sectiontitle = '';
771         $sectiontitle .= html_writer::start_tag('div', array('class' => 'section-navigation navigationtitle'));
772         $sectiontitle .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
773         $sectiontitle .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
774         // Title attributes
775         $classes = 'sectionname';
776         if (!$thissection->visible) {
777             $classes .= ' dimmed_text';
778         }
779         $sectionname = html_writer::tag('span', $this->section_title_without_link($thissection, $course));
780         $sectiontitle .= $this->output->heading($sectionname, 3, $classes);
782         $sectiontitle .= html_writer::end_tag('div');
783         echo $sectiontitle;
785         // Now the list of sections..
786         echo $this->start_section_list();
788         echo $this->section_header($thissection, $course, true, $displaysection);
789         // Show completion help icon.
790         $completioninfo = new completion_info($course);
791         echo $completioninfo->display_help_icon();
793         echo $this->courserenderer->course_section_cm_list($course, $thissection, $displaysection);
794         echo $this->courserenderer->course_section_add_cm_control($course, $displaysection, $displaysection);
795         echo $this->section_footer();
796         echo $this->end_section_list();
798         // Display section bottom navigation.
799         $sectionbottomnav = '';
800         $sectionbottomnav .= html_writer::start_tag('div', array('class' => 'section-navigation mdl-bottom'));
801         $sectionbottomnav .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
802         $sectionbottomnav .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
803         $sectionbottomnav .= html_writer::tag('div', $this->section_nav_selection($course, $sections, $displaysection),
804             array('class' => 'mdl-align'));
805         $sectionbottomnav .= html_writer::end_tag('div');
806         echo $sectionbottomnav;
808         // Close single-section div.
809         echo html_writer::end_tag('div');
810     }
812     /**
813      * Output the html for a multiple section page
814      *
815      * @param stdClass $course The course entry from DB
816      * @param array $sections (argument not used)
817      * @param array $mods (argument not used)
818      * @param array $modnames (argument not used)
819      * @param array $modnamesused (argument not used)
820      */
821     public function print_multiple_section_page($course, $sections, $mods, $modnames, $modnamesused) {
822         $modinfo = get_fast_modinfo($course);
823         $course = course_get_format($course)->get_course();
825         $context = context_course::instance($course->id);
826         // Title with completion help icon.
827         $completioninfo = new completion_info($course);
828         echo $completioninfo->display_help_icon();
829         echo $this->output->heading($this->page_title(), 2, 'accesshide');
831         // Copy activity clipboard..
832         echo $this->course_activity_clipboard($course, 0);
834         // Now the list of sections..
835         echo $this->start_section_list();
836         $numsections = course_get_format($course)->get_last_section_number();
838         foreach ($modinfo->get_section_info_all() as $section => $thissection) {
839             if ($section == 0) {
840                 // 0-section is displayed a little different then the others
841                 if ($thissection->summary or !empty($modinfo->sections[0]) or $this->page->user_is_editing()) {
842                     echo $this->section_header($thissection, $course, false, 0);
843                     echo $this->courserenderer->course_section_cm_list($course, $thissection, 0);
844                     echo $this->courserenderer->course_section_add_cm_control($course, 0, 0);
845                     echo $this->section_footer();
846                 }
847                 continue;
848             }
849             if ($section > $numsections) {
850                 // activities inside this section are 'orphaned', this section will be printed as 'stealth' below
851                 continue;
852             }
853             // Show the section if the user is permitted to access it, OR if it's not available
854             // but there is some available info text which explains the reason & should display,
855             // OR it is hidden but the course has a setting to display hidden sections as unavilable.
856             $showsection = $thissection->uservisible ||
857                     ($thissection->visible && !$thissection->available && !empty($thissection->availableinfo)) ||
858                     (!$thissection->visible && !$course->hiddensections);
859             if (!$showsection) {
860                 continue;
861             }
863             if (!$this->page->user_is_editing() && $course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
864                 // Display section summary only.
865                 echo $this->section_summary($thissection, $course, null);
866             } else {
867                 echo $this->section_header($thissection, $course, false, 0);
868                 if ($thissection->uservisible) {
869                     echo $this->courserenderer->course_section_cm_list($course, $thissection, 0);
870                     echo $this->courserenderer->course_section_add_cm_control($course, $section, 0);
871                 }
872                 echo $this->section_footer();
873             }
874         }
876         if ($this->page->user_is_editing() and has_capability('moodle/course:update', $context)) {
877             // Print stealth sections if present.
878             foreach ($modinfo->get_section_info_all() as $section => $thissection) {
879                 if ($section <= $numsections or empty($modinfo->sections[$section])) {
880                     // this is not stealth section or it is empty
881                     continue;
882                 }
883                 echo $this->stealth_section_header($section);
884                 echo $this->courserenderer->course_section_cm_list($course, $thissection, 0);
885                 echo $this->stealth_section_footer();
886             }
888             echo $this->end_section_list();
890             echo $this->change_number_sections($course, 0);
891         } else {
892             echo $this->end_section_list();
893         }
895     }
897     /**
898      * Returns controls in the bottom of the page to increase/decrease number of sections
899      *
900      * @param stdClass $course
901      * @param int|null $sectionreturn
902      * @return string
903      */
904     protected function change_number_sections($course, $sectionreturn = null) {
905         $coursecontext = context_course::instance($course->id);
906         if (!has_capability('moodle/course:update', $coursecontext)) {
907             return '';
908         }
910         $format = course_get_format($course);
911         $options = $format->get_format_options();
912         $maxsections = $format->get_max_sections();
913         $lastsection = $format->get_last_section_number();
914         $supportsnumsections = array_key_exists('numsections', $options);
916         if ($supportsnumsections) {
917             // Current course format has 'numsections' option, which is very confusing and we suggest course format
918             // developers to get rid of it (see MDL-57769 on how to do it).
919             // Display "Increase section" / "Decrease section" links.
921             echo html_writer::start_tag('div', array('id' => 'changenumsections', 'class' => 'mdl-right'));
923             // Increase number of sections.
924             if ($lastsection < $maxsections) {
925                 $straddsection = get_string('increasesections', 'moodle');
926                 $url = new moodle_url('/course/changenumsections.php',
927                     array('courseid' => $course->id,
928                           'increase' => true,
929                           'sesskey' => sesskey()));
930                 $icon = $this->output->pix_icon('t/switch_plus', $straddsection);
931                 echo html_writer::link($url, $icon.get_accesshide($straddsection), array('class' => 'increase-sections'));
932             }
934             if ($course->numsections > 0) {
935                 // Reduce number of sections sections.
936                 $strremovesection = get_string('reducesections', 'moodle');
937                 $url = new moodle_url('/course/changenumsections.php',
938                     array('courseid' => $course->id,
939                           'increase' => false,
940                           'sesskey' => sesskey()));
941                 $icon = $this->output->pix_icon('t/switch_minus', $strremovesection);
942                 echo html_writer::link($url, $icon.get_accesshide($strremovesection), array('class' => 'reduce-sections'));
943             }
945             echo html_writer::end_tag('div');
947         } else if (course_get_format($course)->uses_sections()) {
948             if ($lastsection >= $maxsections) {
949                 // Don't allow more sections if we already hit the limit.
950                 return;
951             }
952             // Current course format does not have 'numsections' option but it has multiple sections suppport.
953             // Display the "Add section" link that will insert a section in the end.
954             // Note to course format developers: inserting sections in the other positions should check both
955             // capabilities 'moodle/course:update' and 'moodle/course:movesections'.
956             echo html_writer::start_tag('div', array('id' => 'changenumsections', 'class' => 'mdl-right'));
957             if (get_string_manager()->string_exists('addsections', 'format_'.$course->format)) {
958                 $straddsections = get_string('addsections', 'format_'.$course->format);
959             } else {
960                 $straddsections = get_string('addsections');
961             }
962             $url = new moodle_url('/course/changenumsections.php',
963                 ['courseid' => $course->id, 'insertsection' => 0, 'sesskey' => sesskey()]);
964             if ($sectionreturn !== null) {
965                 $url->param('sectionreturn', $sectionreturn);
966             }
967             $icon = $this->output->pix_icon('t/add', '');
968             $newsections = $maxsections - $lastsection;
969             echo html_writer::link($url, $icon . $straddsections,
970                 array('class' => 'add-sections', 'data-add-sections' => $straddsections, 'data-new-sections' => $newsections));
971             echo html_writer::end_tag('div');
972         }
973     }
975     /**
976      * Generate html for a section summary text
977      *
978      * @param stdClass $section The course_section entry from DB
979      * @return string HTML to output.
980      */
981     protected function format_summary_text($section) {
982         $context = context_course::instance($section->course);
983         $summarytext = file_rewrite_pluginfile_urls($section->summary, 'pluginfile.php',
984             $context->id, 'course', 'section', $section->id);
986         $options = new stdClass();
987         $options->noclean = true;
988         $options->overflowdiv = true;
989         return format_text($summarytext, $section->summaryformat, $options);
990     }