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