Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   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  
  26  defined('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   */
  39  abstract 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          // Show completion help icon.
 812          $completioninfo = new completion_info($course);
 813          echo $completioninfo->display_help_icon();
 814  
 815          echo $this->courserenderer->course_section_cm_list($course, $thissection, $displaysection);
 816          echo $this->courserenderer->course_section_add_cm_control($course, $displaysection, $displaysection);
 817          echo $this->section_footer();
 818          echo $this->end_section_list();
 819  
 820          // Display section bottom navigation.
 821          $sectionbottomnav = '';
 822          $sectionbottomnav .= html_writer::start_tag('div', array('class' => 'section-navigation mdl-bottom'));
 823          $sectionbottomnav .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
 824          $sectionbottomnav .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
 825          $sectionbottomnav .= html_writer::tag('div', $this->section_nav_selection($course, $sections, $displaysection),
 826              array('class' => 'mdl-align'));
 827          $sectionbottomnav .= html_writer::end_tag('div');
 828          echo $sectionbottomnav;
 829  
 830          // Close single-section div.
 831          echo html_writer::end_tag('div');
 832      }
 833  
 834      /**
 835       * Output the html for a multiple section page
 836       *
 837       * @param stdClass $course The course entry from DB
 838       * @param array $sections (argument not used)
 839       * @param array $mods (argument not used)
 840       * @param array $modnames (argument not used)
 841       * @param array $modnamesused (argument not used)
 842       */
 843      public function print_multiple_section_page($course, $sections, $mods, $modnames, $modnamesused) {
 844          $modinfo = get_fast_modinfo($course);
 845          $course = course_get_format($course)->get_course();
 846  
 847          $context = context_course::instance($course->id);
 848          // Title with completion help icon.
 849          $completioninfo = new completion_info($course);
 850          echo $completioninfo->display_help_icon();
 851          echo $this->output->heading($this->page_title(), 2, 'accesshide');
 852  
 853          // Copy activity clipboard..
 854          echo $this->course_activity_clipboard($course, 0);
 855  
 856          // Now the list of sections..
 857          echo $this->start_section_list();
 858          $numsections = course_get_format($course)->get_last_section_number();
 859  
 860          foreach ($modinfo->get_section_info_all() as $section => $thissection) {
 861              if ($section == 0) {
 862                  // 0-section is displayed a little different then the others
 863                  if ($thissection->summary or !empty($modinfo->sections[0]) or $this->page->user_is_editing()) {
 864                      echo $this->section_header($thissection, $course, false, 0);
 865                      echo $this->courserenderer->course_section_cm_list($course, $thissection, 0);
 866                      echo $this->courserenderer->course_section_add_cm_control($course, 0, 0);
 867                      echo $this->section_footer();
 868                  }
 869                  continue;
 870              }
 871              if ($section > $numsections) {
 872                  // activities inside this section are 'orphaned', this section will be printed as 'stealth' below
 873                  continue;
 874              }
 875              // Show the section if the user is permitted to access it, OR if it's not available
 876              // but there is some available info text which explains the reason & should display,
 877              // OR it is hidden but the course has a setting to display hidden sections as unavilable.
 878              $showsection = $thissection->uservisible ||
 879                      ($thissection->visible && !$thissection->available && !empty($thissection->availableinfo)) ||
 880                      (!$thissection->visible && !$course->hiddensections);
 881              if (!$showsection) {
 882                  continue;
 883              }
 884  
 885              if (!$this->page->user_is_editing() && $course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
 886                  // Display section summary only.
 887                  echo $this->section_summary($thissection, $course, null);
 888              } else {
 889                  echo $this->section_header($thissection, $course, false, 0);
 890                  if ($thissection->uservisible) {
 891                      echo $this->courserenderer->course_section_cm_list($course, $thissection, 0);
 892                      echo $this->courserenderer->course_section_add_cm_control($course, $section, 0);
 893                  }
 894                  echo $this->section_footer();
 895              }
 896          }
 897  
 898          if ($this->page->user_is_editing() and has_capability('moodle/course:update', $context)) {
 899              // Print stealth sections if present.
 900              foreach ($modinfo->get_section_info_all() as $section => $thissection) {
 901                  if ($section <= $numsections or empty($modinfo->sections[$section])) {
 902                      // this is not stealth section or it is empty
 903                      continue;
 904                  }
 905                  echo $this->stealth_section_header($section);
 906                  echo $this->courserenderer->course_section_cm_list($course, $thissection, 0);
 907                  echo $this->stealth_section_footer();
 908              }
 909  
 910              echo $this->end_section_list();
 911  
 912              echo $this->change_number_sections($course, 0);
 913          } else {
 914              echo $this->end_section_list();
 915          }
 916  
 917      }
 918  
 919      /**
 920       * Returns controls in the bottom of the page to increase/decrease number of sections
 921       *
 922       * @param stdClass $course
 923       * @param int|null $sectionreturn
 924       * @return string
 925       */
 926      protected function change_number_sections($course, $sectionreturn = null) {
 927          $coursecontext = context_course::instance($course->id);
 928          if (!has_capability('moodle/course:update', $coursecontext)) {
 929              return '';
 930          }
 931  
 932          $format = course_get_format($course);
 933          $options = $format->get_format_options();
 934          $maxsections = $format->get_max_sections();
 935          $lastsection = $format->get_last_section_number();
 936          $supportsnumsections = array_key_exists('numsections', $options);
 937  
 938          if ($supportsnumsections) {
 939              // Current course format has 'numsections' option, which is very confusing and we suggest course format
 940              // developers to get rid of it (see MDL-57769 on how to do it).
 941              // Display "Increase section" / "Decrease section" links.
 942  
 943              echo html_writer::start_tag('div', array('id' => 'changenumsections', 'class' => 'mdl-right'));
 944  
 945              // Increase number of sections.
 946              if ($lastsection < $maxsections) {
 947                  $straddsection = get_string('increasesections', 'moodle');
 948                  $url = new moodle_url('/course/changenumsections.php',
 949                      array('courseid' => $course->id,
 950                            'increase' => true,
 951                            'sesskey' => sesskey()));
 952                  $icon = $this->output->pix_icon('t/switch_plus', $straddsection);
 953                  echo html_writer::link($url, $icon.get_accesshide($straddsection), array('class' => 'increase-sections'));
 954              }
 955  
 956              if ($course->numsections > 0) {
 957                  // Reduce number of sections sections.
 958                  $strremovesection = get_string('reducesections', 'moodle');
 959                  $url = new moodle_url('/course/changenumsections.php',
 960                      array('courseid' => $course->id,
 961                            'increase' => false,
 962                            'sesskey' => sesskey()));
 963                  $icon = $this->output->pix_icon('t/switch_minus', $strremovesection);
 964                  echo html_writer::link($url, $icon.get_accesshide($strremovesection), array('class' => 'reduce-sections'));
 965              }
 966  
 967              echo html_writer::end_tag('div');
 968  
 969          } else if (course_get_format($course)->uses_sections()) {
 970              if ($lastsection >= $maxsections) {
 971                  // Don't allow more sections if we already hit the limit.
 972                  return;
 973              }
 974              // Current course format does not have 'numsections' option but it has multiple sections suppport.
 975              // Display the "Add section" link that will insert a section in the end.
 976              // Note to course format developers: inserting sections in the other positions should check both
 977              // capabilities 'moodle/course:update' and 'moodle/course:movesections'.
 978              echo html_writer::start_tag('div', array('id' => 'changenumsections', 'class' => 'mdl-right'));
 979              if (get_string_manager()->string_exists('addsections', 'format_'.$course->format)) {
 980                  $straddsections = get_string('addsections', 'format_'.$course->format);
 981              } else {
 982                  $straddsections = get_string('addsections');
 983              }
 984              $url = new moodle_url('/course/changenumsections.php',
 985                  ['courseid' => $course->id, 'insertsection' => 0, 'sesskey' => sesskey()]);
 986              if ($sectionreturn !== null) {
 987                  $url->param('sectionreturn', $sectionreturn);
 988              }
 989              $icon = $this->output->pix_icon('t/add', '');
 990              $newsections = $maxsections - $lastsection;
 991              echo html_writer::link($url, $icon . $straddsections,
 992                  array('class' => 'add-sections', 'data-add-sections' => $straddsections, 'data-new-sections' => $newsections));
 993              echo html_writer::end_tag('div');
 994          }
 995      }
 996  
 997      /**
 998       * Generate html for a section summary text
 999       *
1000       * @param stdClass $section The course_section entry from DB
1001       * @return string HTML to output.
1002       */
1003      protected function format_summary_text($section) {
1004          $context = context_course::instance($section->course);
1005          $summarytext = file_rewrite_pluginfile_urls($section->summary, 'pluginfile.php',
1006              $context->id, 'course', 'section', $section->id);
1007  
1008          $options = new stdClass();
1009          $options->noclean = true;
1010          $options->overflowdiv = true;
1011          return format_text($summarytext, $section->summaryformat, $options);
1012      }
1013  }