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