"Fossies" - the Fresh Open Source Software Archive

Member "moodle/course/renderer.php" (6 Sep 2019, 117463 Bytes) of package /linux/www/moodle-3.6.6.tgz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "renderer.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.6.5_vs_3.6.6.

    1 <?php
    2 
    3 // This file is part of Moodle - http://moodle.org/
    4 //
    5 // Moodle is free software: you can redistribute it and/or modify
    6 // it under the terms of the GNU General Public License as published by
    7 // the Free Software Foundation, either version 3 of the License, or
    8 // (at your option) any later version.
    9 //
   10 // Moodle is distributed in the hope that it will be useful,
   11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
   12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13 // GNU General Public License for more details.
   14 //
   15 // You should have received a copy of the GNU General Public License
   16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
   17 
   18 /**
   19  * Renderer for use with the course section and all the goodness that falls
   20  * within it.
   21  *
   22  * This renderer should contain methods useful to courses, and categories.
   23  *
   24  * @package   moodlecore
   25  * @copyright 2010 Sam Hemelryk
   26  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
   27  */
   28 
   29 /**
   30  * The core course renderer
   31  *
   32  * Can be retrieved with the following:
   33  * $renderer = $PAGE->get_renderer('core','course');
   34  */
   35 class core_course_renderer extends plugin_renderer_base {
   36     const COURSECAT_SHOW_COURSES_NONE = 0; /* do not show courses at all */
   37     const COURSECAT_SHOW_COURSES_COUNT = 5; /* do not show courses but show number of courses next to category name */
   38     const COURSECAT_SHOW_COURSES_COLLAPSED = 10;
   39     const COURSECAT_SHOW_COURSES_AUTO = 15; /* will choose between collapsed and expanded automatically */
   40     const COURSECAT_SHOW_COURSES_EXPANDED = 20;
   41     const COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT = 30;
   42 
   43     const COURSECAT_TYPE_CATEGORY = 0;
   44     const COURSECAT_TYPE_COURSE = 1;
   45 
   46     /**
   47      * A cache of strings
   48      * @var stdClass
   49      */
   50     protected $strings;
   51 
   52     /**
   53      * Whether a category content is being initially rendered with children. This is mainly used by the
   54      * core_course_renderer::corsecat_tree() to render the appropriate action for the Expand/Collapse all link on
   55      * page load.
   56      * @var bool
   57      */
   58     protected $categoryexpandedonload = false;
   59 
   60     /**
   61      * Override the constructor so that we can initialise the string cache
   62      *
   63      * @param moodle_page $page
   64      * @param string $target
   65      */
   66     public function __construct(moodle_page $page, $target) {
   67         $this->strings = new stdClass;
   68         parent::__construct($page, $target);
   69     }
   70 
   71     /**
   72      * @deprecated since 3.2
   73      */
   74     protected function add_modchoosertoggle() {
   75         throw new coding_exception('core_course_renderer::add_modchoosertoggle() can not be used anymore.');
   76     }
   77 
   78     /**
   79      * Renders course info box.
   80      *
   81      * @param stdClass $course
   82      * @return string
   83      */
   84     public function course_info_box(stdClass $course) {
   85         $content = '';
   86         $content .= $this->output->box_start('generalbox info');
   87         $chelper = new coursecat_helper();
   88         $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED);
   89         $content .= $this->coursecat_coursebox($chelper, $course);
   90         $content .= $this->output->box_end();
   91         return $content;
   92     }
   93 
   94     /**
   95      * Renderers a structured array of courses and categories into a nice XHTML tree structure.
   96      *
   97      * @deprecated since 2.5
   98      *
   99      * Please see http://docs.moodle.org/dev/Courses_lists_upgrade_to_2.5
  100      *
  101      * @param array $ignored argument ignored
  102      * @return string
  103      */
  104     public final function course_category_tree(array $ignored) {
  105         debugging('Function core_course_renderer::course_category_tree() is deprecated, please use frontpage_combo_list()', DEBUG_DEVELOPER);
  106         return $this->frontpage_combo_list();
  107     }
  108 
  109     /**
  110      * Renderers a category for use with course_category_tree
  111      *
  112      * @deprecated since 2.5
  113      *
  114      * Please see http://docs.moodle.org/dev/Courses_lists_upgrade_to_2.5
  115      *
  116      * @param array $category
  117      * @param int $depth
  118      * @return string
  119      */
  120     protected final function course_category_tree_category(stdClass $category, $depth=1) {
  121         debugging('Function core_course_renderer::course_category_tree_category() is deprecated', DEBUG_DEVELOPER);
  122         return '';
  123     }
  124 
  125     /**
  126      * Render a modchooser.
  127      *
  128      * @param renderable $modchooser The chooser.
  129      * @return string
  130      */
  131     public function render_modchooser(renderable $modchooser) {
  132         return $this->render_from_template('core_course/modchooser', $modchooser->export_for_template($this));
  133     }
  134 
  135     /**
  136      * Build the HTML for the module chooser javascript popup
  137      *
  138      * @param array $modules A set of modules as returned form @see
  139      * get_module_metadata
  140      * @param object $course The course that will be displayed
  141      * @return string The composed HTML for the module
  142      */
  143     public function course_modchooser($modules, $course) {
  144         if (!$this->page->requires->should_create_one_time_item_now('core_course_modchooser')) {
  145             return '';
  146         }
  147         $modchooser = new \core_course\output\modchooser($course, $modules);
  148         return $this->render($modchooser);
  149     }
  150 
  151     /**
  152      * Build the HTML for a specified set of modules
  153      *
  154      * @param array $modules A set of modules as used by the
  155      * course_modchooser_module function
  156      * @return string The composed HTML for the module
  157      */
  158     protected function course_modchooser_module_types($modules) {
  159         debugging('Method core_course_renderer::course_modchooser_module_types() is deprecated, ' .
  160             'see core_course_renderer::render_modchooser().', DEBUG_DEVELOPER);
  161         return '';
  162     }
  163 
  164     /**
  165      * Return the HTML for the specified module adding any required classes
  166      *
  167      * @param object $module An object containing the title, and link. An
  168      * icon, and help text may optionally be specified. If the module
  169      * contains subtypes in the types option, then these will also be
  170      * displayed.
  171      * @param array $classes Additional classes to add to the encompassing
  172      * div element
  173      * @return string The composed HTML for the module
  174      */
  175     protected function course_modchooser_module($module, $classes = array('option')) {
  176         debugging('Method core_course_renderer::course_modchooser_module() is deprecated, ' .
  177             'see core_course_renderer::render_modchooser().', DEBUG_DEVELOPER);
  178         return '';
  179     }
  180 
  181     protected function course_modchooser_title($title, $identifier = null) {
  182         debugging('Method core_course_renderer::course_modchooser_title() is deprecated, ' .
  183             'see core_course_renderer::render_modchooser().', DEBUG_DEVELOPER);
  184         return '';
  185     }
  186 
  187     /**
  188      * Renders HTML for displaying the sequence of course module editing buttons
  189      *
  190      * @see course_get_cm_edit_actions()
  191      *
  192      * @param action_link[] $actions Array of action_link objects
  193      * @param cm_info $mod The module we are displaying actions for.
  194      * @param array $displayoptions additional display options:
  195      *     ownerselector => A JS/CSS selector that can be used to find an cm node.
  196      *         If specified the owning node will be given the class 'action-menu-shown' when the action
  197      *         menu is being displayed.
  198      *     constraintselector => A JS/CSS selector that can be used to find the parent node for which to constrain
  199      *         the action menu to when it is being displayed.
  200      *     donotenhance => If set to true the action menu that gets displayed won't be enhanced by JS.
  201      * @return string
  202      */
  203     public function course_section_cm_edit_actions($actions, cm_info $mod = null, $displayoptions = array()) {
  204         global $CFG;
  205 
  206         if (empty($actions)) {
  207             return '';
  208         }
  209 
  210         if (isset($displayoptions['ownerselector'])) {
  211             $ownerselector = $displayoptions['ownerselector'];
  212         } else if ($mod) {
  213             $ownerselector = '#module-'.$mod->id;
  214         } else {
  215             debugging('You should upgrade your call to '.__FUNCTION__.' and provide $mod', DEBUG_DEVELOPER);
  216             $ownerselector = 'li.activity';
  217         }
  218 
  219         if (isset($displayoptions['constraintselector'])) {
  220             $constraint = $displayoptions['constraintselector'];
  221         } else {
  222             $constraint = '.course-content';
  223         }
  224 
  225         $menu = new action_menu();
  226         $menu->set_owner_selector($ownerselector);
  227         $menu->set_constraint($constraint);
  228         $menu->set_alignment(action_menu::TR, action_menu::BR);
  229         $menu->set_menu_trigger(get_string('edit'));
  230 
  231         foreach ($actions as $action) {
  232             if ($action instanceof action_menu_link) {
  233                 $action->add_class('cm-edit-action');
  234             }
  235             $menu->add($action);
  236         }
  237         $menu->attributes['class'] .= ' section-cm-edit-actions commands';
  238 
  239         // Prioritise the menu ahead of all other actions.
  240         $menu->prioritise = true;
  241 
  242         return $this->render($menu);
  243     }
  244 
  245     /**
  246      * Renders HTML for the menus to add activities and resources to the current course
  247      *
  248      * @param stdClass $course
  249      * @param int $section relative section number (field course_sections.section)
  250      * @param int $sectionreturn The section to link back to
  251      * @param array $displayoptions additional display options, for example blocks add
  252      *     option 'inblock' => true, suggesting to display controls vertically
  253      * @return string
  254      */
  255     function course_section_add_cm_control($course, $section, $sectionreturn = null, $displayoptions = array()) {
  256         global $CFG;
  257 
  258         $vertical = !empty($displayoptions['inblock']);
  259 
  260         // check to see if user can add menus and there are modules to add
  261         if (!has_capability('moodle/course:manageactivities', context_course::instance($course->id))
  262                 || !$this->page->user_is_editing()
  263                 || !($modnames = get_module_types_names()) || empty($modnames)) {
  264             return '';
  265         }
  266 
  267         // Retrieve all modules with associated metadata
  268         $modules = get_module_metadata($course, $modnames, $sectionreturn);
  269         $urlparams = array('section' => $section);
  270 
  271         // We'll sort resources and activities into two lists
  272         $activities = array(MOD_CLASS_ACTIVITY => array(), MOD_CLASS_RESOURCE => array());
  273 
  274         foreach ($modules as $module) {
  275             $activityclass = MOD_CLASS_ACTIVITY;
  276             if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
  277                 $activityclass = MOD_CLASS_RESOURCE;
  278             } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
  279                 // System modules cannot be added by user, do not add to dropdown.
  280                 continue;
  281             }
  282             $link = $module->link->out(true, $urlparams);
  283             $activities[$activityclass][$link] = $module->title;
  284         }
  285 
  286         $straddactivity = get_string('addactivity');
  287         $straddresource = get_string('addresource');
  288         $sectionname = get_section_name($course, $section);
  289         $strresourcelabel = get_string('addresourcetosection', null, $sectionname);
  290         $stractivitylabel = get_string('addactivitytosection', null, $sectionname);
  291 
  292         $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
  293 
  294         if (!$vertical) {
  295             $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
  296         }
  297 
  298         if (!empty($activities[MOD_CLASS_RESOURCE])) {
  299             $select = new url_select($activities[MOD_CLASS_RESOURCE], '', array(''=>$straddresource), "ressection$section");
  300             $select->set_help_icon('resources');
  301             $select->set_label($strresourcelabel, array('class' => 'accesshide'));
  302             $output .= $this->output->render($select);
  303         }
  304 
  305         if (!empty($activities[MOD_CLASS_ACTIVITY])) {
  306             $select = new url_select($activities[MOD_CLASS_ACTIVITY], '', array(''=>$straddactivity), "section$section");
  307             $select->set_help_icon('activities');
  308             $select->set_label($stractivitylabel, array('class' => 'accesshide'));
  309             $output .= $this->output->render($select);
  310         }
  311 
  312         if (!$vertical) {
  313             $output .= html_writer::end_tag('div');
  314         }
  315 
  316         $output .= html_writer::end_tag('div');
  317 
  318         if (course_ajax_enabled($course) && $course->id == $this->page->course->id) {
  319             // modchooser can be added only for the current course set on the page!
  320             $straddeither = get_string('addresourceoractivity');
  321             // The module chooser link
  322             $modchooser = html_writer::start_tag('div', array('class' => 'mdl-right'));
  323             $modchooser.= html_writer::start_tag('div', array('class' => 'section-modchooser'));
  324             $icon = $this->output->pix_icon('t/add', '');
  325             $span = html_writer::tag('span', $straddeither, array('class' => 'section-modchooser-text'));
  326             $modchooser .= html_writer::tag('span', $icon . $span, array('class' => 'section-modchooser-link'));
  327             $modchooser.= html_writer::end_tag('div');
  328             $modchooser.= html_writer::end_tag('div');
  329 
  330             // Wrap the normal output in a noscript div
  331             $usemodchooser = get_user_preferences('usemodchooser', $CFG->modchooserdefault);
  332             if ($usemodchooser) {
  333                 $output = html_writer::tag('div', $output, array('class' => 'hiddenifjs addresourcedropdown'));
  334                 $modchooser = html_writer::tag('div', $modchooser, array('class' => 'visibleifjs addresourcemodchooser'));
  335             } else {
  336                 // If the module chooser is disabled, we need to ensure that the dropdowns are shown even if javascript is disabled
  337                 $output = html_writer::tag('div', $output, array('class' => 'show addresourcedropdown'));
  338                 $modchooser = html_writer::tag('div', $modchooser, array('class' => 'hide addresourcemodchooser'));
  339             }
  340             $output = $this->course_modchooser($modules, $course) . $modchooser . $output;
  341         }
  342 
  343         return $output;
  344     }
  345 
  346     /**
  347      * Renders html to display a course search form
  348      *
  349      * @param string $value default value to populate the search field
  350      * @param string $format display format - 'plain' (default), 'short' or 'navbar'
  351      * @return string
  352      */
  353     function course_search_form($value = '', $format = 'plain') {
  354         static $count = 0;
  355         $formid = 'coursesearch';
  356         if ((++$count) > 1) {
  357             $formid .= $count;
  358         }
  359 
  360         switch ($format) {
  361             case 'navbar' :
  362                 $formid = 'coursesearchnavbar';
  363                 $inputid = 'navsearchbox';
  364                 $inputsize = 20;
  365                 break;
  366             case 'short' :
  367                 $inputid = 'shortsearchbox';
  368                 $inputsize = 12;
  369                 break;
  370             default :
  371                 $inputid = 'coursesearchbox';
  372                 $inputsize = 30;
  373         }
  374 
  375         $strsearchcourses= get_string("searchcourses");
  376         $searchurl = new moodle_url('/course/search.php');
  377 
  378         $output = html_writer::start_tag('form', array('id' => $formid, 'action' => $searchurl, 'method' => 'get'));
  379         $output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset'));
  380         $output .= html_writer::tag('label', $strsearchcourses.': ', array('for' => $inputid));
  381         $output .= html_writer::empty_tag('input', array('type' => 'text', 'id' => $inputid,
  382             'size' => $inputsize, 'name' => 'search', 'value' => s($value)));
  383         $output .= html_writer::empty_tag('input', array('type' => 'submit',
  384             'value' => get_string('go')));
  385         $output .= html_writer::end_tag('fieldset');
  386         if ($format != 'navbar') {
  387             $output .= $this->output->help_icon("coursesearch", "core");
  388         }
  389         $output .= html_writer::end_tag('form');
  390 
  391         return $output;
  392     }
  393 
  394     /**
  395      * Renders html for completion box on course page
  396      *
  397      * If completion is disabled, returns empty string
  398      * If completion is automatic, returns an icon of the current completion state
  399      * If completion is manual, returns a form (with an icon inside) that allows user to
  400      * toggle completion
  401      *
  402      * @param stdClass $course course object
  403      * @param completion_info $completioninfo completion info for the course, it is recommended
  404      *     to fetch once for all modules in course/section for performance
  405      * @param cm_info $mod module to show completion for
  406      * @param array $displayoptions display options, not used in core
  407      * @return string
  408      */
  409     public function course_section_cm_completion($course, &$completioninfo, cm_info $mod, $displayoptions = array()) {
  410         global $CFG, $DB;
  411         $output = '';
  412         if (!empty($displayoptions['hidecompletion']) || !isloggedin() || isguestuser() || !$mod->uservisible) {
  413             return $output;
  414         }
  415         if ($completioninfo === null) {
  416             $completioninfo = new completion_info($course);
  417         }
  418         $completion = $completioninfo->is_enabled($mod);
  419         if ($completion == COMPLETION_TRACKING_NONE) {
  420             if ($this->page->user_is_editing()) {
  421                 $output .= html_writer::span('&nbsp;', 'filler');
  422             }
  423             return $output;
  424         }
  425 
  426         $completiondata = $completioninfo->get_data($mod, true);
  427         $completionicon = '';
  428 
  429         if ($this->page->user_is_editing()) {
  430             switch ($completion) {
  431                 case COMPLETION_TRACKING_MANUAL :
  432                     $completionicon = 'manual-enabled'; break;
  433                 case COMPLETION_TRACKING_AUTOMATIC :
  434                     $completionicon = 'auto-enabled'; break;
  435             }
  436         } else if ($completion == COMPLETION_TRACKING_MANUAL) {
  437             switch($completiondata->completionstate) {
  438                 case COMPLETION_INCOMPLETE:
  439                     $completionicon = 'manual-n' . ($completiondata->overrideby ? '-override' : '');
  440                     break;
  441                 case COMPLETION_COMPLETE:
  442                     $completionicon = 'manual-y' . ($completiondata->overrideby ? '-override' : '');
  443                     break;
  444             }
  445         } else { // Automatic
  446             switch($completiondata->completionstate) {
  447                 case COMPLETION_INCOMPLETE:
  448                     $completionicon = 'auto-n' . ($completiondata->overrideby ? '-override' : '');
  449                     break;
  450                 case COMPLETION_COMPLETE:
  451                     $completionicon = 'auto-y' . ($completiondata->overrideby ? '-override' : '');
  452                     break;
  453                 case COMPLETION_COMPLETE_PASS:
  454                     $completionicon = 'auto-pass'; break;
  455                 case COMPLETION_COMPLETE_FAIL:
  456                     $completionicon = 'auto-fail'; break;
  457             }
  458         }
  459         if ($completionicon) {
  460             $formattedname = html_entity_decode($mod->get_formatted_name(), ENT_QUOTES, 'UTF-8');
  461             if ($completiondata->overrideby) {
  462                 $args = new stdClass();
  463                 $args->modname = $formattedname;
  464                 $overridebyuser = \core_user::get_user($completiondata->overrideby, '*', MUST_EXIST);
  465                 $args->overrideuser = fullname($overridebyuser);
  466                 $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $args);
  467             } else {
  468                 $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $formattedname);
  469             }
  470 
  471             if ($this->page->user_is_editing() || !has_capability('moodle/course:togglecompletion', $mod->context)) {
  472                 // When editing, the icon is just an image.
  473                 $completionpixicon = new pix_icon('i/completion-'.$completionicon, $imgalt, '',
  474                         array('title' => $imgalt, 'class' => 'iconsmall'));
  475                 $output .= html_writer::tag('span', $this->output->render($completionpixicon),
  476                         array('class' => 'autocompletion'));
  477             } else if ($completion == COMPLETION_TRACKING_MANUAL) {
  478                 $newstate =
  479                     $completiondata->completionstate == COMPLETION_COMPLETE
  480                     ? COMPLETION_INCOMPLETE
  481                     : COMPLETION_COMPLETE;
  482                 // In manual mode the icon is a toggle form...
  483 
  484                 // If this completion state is used by the
  485                 // conditional activities system, we need to turn
  486                 // off the JS.
  487                 $extraclass = '';
  488                 if (!empty($CFG->enableavailability) &&
  489                         core_availability\info::completion_value_used($course, $mod->id)) {
  490                     $extraclass = ' preventjs';
  491                 }
  492                 $output .= html_writer::start_tag('form', array('method' => 'post',
  493                     'action' => new moodle_url('/course/togglecompletion.php'),
  494                     'class' => 'togglecompletion'. $extraclass));
  495                 $output .= html_writer::start_tag('div');
  496                 $output .= html_writer::empty_tag('input', array(
  497                     'type' => 'hidden', 'name' => 'id', 'value' => $mod->id));
  498                 $output .= html_writer::empty_tag('input', array(
  499                     'type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
  500                 $output .= html_writer::empty_tag('input', array(
  501                     'type' => 'hidden', 'name' => 'modulename', 'value' => $formattedname));
  502                 $output .= html_writer::empty_tag('input', array(
  503                     'type' => 'hidden', 'name' => 'completionstate', 'value' => $newstate));
  504                 $output .= html_writer::tag('button',
  505                     $this->output->pix_icon('i/completion-' . $completionicon, $imgalt),
  506                         array('class' => 'btn btn-link', 'aria-live' => 'assertive'));
  507                 $output .= html_writer::end_tag('div');
  508                 $output .= html_writer::end_tag('form');
  509             } else {
  510                 // In auto mode, the icon is just an image.
  511                 $completionpixicon = new pix_icon('i/completion-'.$completionicon, $imgalt, '',
  512                         array('title' => $imgalt));
  513                 $output .= html_writer::tag('span', $this->output->render($completionpixicon),
  514                         array('class' => 'autocompletion'));
  515             }
  516         }
  517         return $output;
  518     }
  519 
  520     /**
  521      * Checks if course module has any conditions that may make it unavailable for
  522      * all or some of the students
  523      *
  524      * This function is internal and is only used to create CSS classes for the module name/text
  525      *
  526      * @param cm_info $mod
  527      * @return bool
  528      */
  529     protected function is_cm_conditionally_hidden(cm_info $mod) {
  530         global $CFG;
  531         $conditionalhidden = false;
  532         if (!empty($CFG->enableavailability)) {
  533             $info = new \core_availability\info_module($mod);
  534             $conditionalhidden = !$info->is_available_for_all();
  535         }
  536         return $conditionalhidden;
  537     }
  538 
  539     /**
  540      * Renders html to display a name with the link to the course module on a course page
  541      *
  542      * If module is unavailable for user but still needs to be displayed
  543      * in the list, just the name is returned without a link
  544      *
  545      * Note, that for course modules that never have separate pages (i.e. labels)
  546      * this function return an empty string
  547      *
  548      * @param cm_info $mod
  549      * @param array $displayoptions
  550      * @return string
  551      */
  552     public function course_section_cm_name(cm_info $mod, $displayoptions = array()) {
  553         if (!$mod->is_visible_on_course_page() || !$mod->url) {
  554             // Nothing to be displayed to the user.
  555             return '';
  556         }
  557 
  558         list($linkclasses, $textclasses) = $this->course_section_cm_classes($mod);
  559         $groupinglabel = $mod->get_grouping_label($textclasses);
  560 
  561         // Render element that allows to edit activity name inline. It calls {@link course_section_cm_name_title()}
  562         // to get the display title of the activity.
  563         $tmpl = new \core_course\output\course_module_name($mod, $this->page->user_is_editing(), $displayoptions);
  564         return $this->output->render_from_template('core/inplace_editable', $tmpl->export_for_template($this->output)) .
  565             $groupinglabel;
  566     }
  567 
  568     /**
  569      * Returns the CSS classes for the activity name/content
  570      *
  571      * For items which are hidden, unavailable or stealth but should be displayed
  572      * to current user ($mod->is_visible_on_course_page()), we show those as dimmed.
  573      * Students will also see as dimmed activities names that are not yet available
  574      * but should still be displayed (without link) with availability info.
  575      *
  576      * @param cm_info $mod
  577      * @return array array of two elements ($linkclasses, $textclasses)
  578      */
  579     protected function course_section_cm_classes(cm_info $mod) {
  580         $linkclasses = '';
  581         $textclasses = '';
  582         if ($mod->uservisible) {
  583             $conditionalhidden = $this->is_cm_conditionally_hidden($mod);
  584             $accessiblebutdim = (!$mod->visible || $conditionalhidden) &&
  585                 has_capability('moodle/course:viewhiddenactivities', $mod->context);
  586             if ($accessiblebutdim) {
  587                 $linkclasses .= ' dimmed';
  588                 $textclasses .= ' dimmed_text';
  589                 if ($conditionalhidden) {
  590                     $linkclasses .= ' conditionalhidden';
  591                     $textclasses .= ' conditionalhidden';
  592                 }
  593             }
  594             if ($mod->is_stealth()) {
  595                 // Stealth activity is the one that is not visible on course page.
  596                 // It still may be displayed to the users who can manage it.
  597                 $linkclasses .= ' stealth';
  598                 $textclasses .= ' stealth';
  599             }
  600         } else {
  601             $linkclasses .= ' dimmed';
  602             $textclasses .= ' dimmed dimmed_text';
  603         }
  604         return array($linkclasses, $textclasses);
  605     }
  606 
  607     /**
  608      * Renders html to display a name with the link to the course module on a course page
  609      *
  610      * If module is unavailable for user but still needs to be displayed
  611      * in the list, just the name is returned without a link
  612      *
  613      * Note, that for course modules that never have separate pages (i.e. labels)
  614      * this function return an empty string
  615      *
  616      * @param cm_info $mod
  617      * @param array $displayoptions
  618      * @return string
  619      */
  620     public function course_section_cm_name_title(cm_info $mod, $displayoptions = array()) {
  621         $output = '';
  622         $url = $mod->url;
  623         if (!$mod->is_visible_on_course_page() || !$url) {
  624             // Nothing to be displayed to the user.
  625             return $output;
  626         }
  627 
  628         //Accessibility: for files get description via icon, this is very ugly hack!
  629         $instancename = $mod->get_formatted_name();
  630         $altname = $mod->modfullname;
  631         // Avoid unnecessary duplication: if e.g. a forum name already
  632         // includes the word forum (or Forum, etc) then it is unhelpful
  633         // to include that in the accessible description that is added.
  634         if (false !== strpos(core_text::strtolower($instancename),
  635                 core_text::strtolower($altname))) {
  636             $altname = '';
  637         }
  638         // File type after name, for alphabetic lists (screen reader).
  639         if ($altname) {
  640             $altname = get_accesshide(' '.$altname);
  641         }
  642 
  643         list($linkclasses, $textclasses) = $this->course_section_cm_classes($mod);
  644 
  645         // Get on-click attribute value if specified and decode the onclick - it
  646         // has already been encoded for display (puke).
  647         $onclick = htmlspecialchars_decode($mod->onclick, ENT_QUOTES);
  648 
  649         // Display link itself.
  650         $activitylink = html_writer::empty_tag('img', array('src' => $mod->get_icon_url(),
  651                 'class' => 'iconlarge activityicon', 'alt' => '', 'role' => 'presentation', 'aria-hidden' => 'true')) .
  652                 html_writer::tag('span', $instancename . $altname, array('class' => 'instancename'));
  653         if ($mod->uservisible) {
  654             $output .= html_writer::link($url, $activitylink, array('class' => $linkclasses, 'onclick' => $onclick));
  655         } else {
  656             // We may be displaying this just in order to show information
  657             // about visibility, without the actual link ($mod->is_visible_on_course_page()).
  658             $output .= html_writer::tag('div', $activitylink, array('class' => $textclasses));
  659         }
  660         return $output;
  661     }
  662 
  663     /**
  664      * Renders html to display the module content on the course page (i.e. text of the labels)
  665      *
  666      * @param cm_info $mod
  667      * @param array $displayoptions
  668      * @return string
  669      */
  670     public function course_section_cm_text(cm_info $mod, $displayoptions = array()) {
  671         $output = '';
  672         if (!$mod->is_visible_on_course_page()) {
  673             // nothing to be displayed to the user
  674             return $output;
  675         }
  676         $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
  677         list($linkclasses, $textclasses) = $this->course_section_cm_classes($mod);
  678         if ($mod->url && $mod->uservisible) {
  679             if ($content) {
  680                 // If specified, display extra content after link.
  681                 $output = html_writer::tag('div', $content, array('class' =>
  682                         trim('contentafterlink ' . $textclasses)));
  683             }
  684         } else {
  685             $groupinglabel = $mod->get_grouping_label($textclasses);
  686 
  687             // No link, so display only content.
  688             $output = html_writer::tag('div', $content . $groupinglabel,
  689                     array('class' => 'contentwithoutlink ' . $textclasses));
  690         }
  691         return $output;
  692     }
  693 
  694     /**
  695      * Displays availability info for a course section or course module
  696      *
  697      * @param string $text
  698      * @param string $additionalclasses
  699      * @return string
  700      */
  701     public function availability_info($text, $additionalclasses = '') {
  702 
  703         $data = ['text' => $text, 'classes' => $additionalclasses];
  704         $additionalclasses = array_filter(explode(' ', $additionalclasses));
  705 
  706         if (in_array('ishidden', $additionalclasses)) {
  707             $data['ishidden'] = 1;
  708 
  709         } else if (in_array('isstealth', $additionalclasses)) {
  710             $data['isstealth'] = 1;
  711 
  712         } else if (in_array('isrestricted', $additionalclasses)) {
  713             $data['isrestricted'] = 1;
  714 
  715             if (in_array('isfullinfo', $additionalclasses)) {
  716                 $data['isfullinfo'] = 1;
  717             }
  718         }
  719 
  720         return $this->render_from_template('core/availability_info', $data);
  721     }
  722 
  723     /**
  724      * Renders HTML to show course module availability information (for someone who isn't allowed
  725      * to see the activity itself, or for staff)
  726      *
  727      * @param cm_info $mod
  728      * @param array $displayoptions
  729      * @return string
  730      */
  731     public function course_section_cm_availability(cm_info $mod, $displayoptions = array()) {
  732         global $CFG;
  733         $output = '';
  734         if (!$mod->is_visible_on_course_page()) {
  735             return $output;
  736         }
  737         if (!$mod->uservisible) {
  738             // this is a student who is not allowed to see the module but might be allowed
  739             // to see availability info (i.e. "Available from ...")
  740             if (!empty($mod->availableinfo)) {
  741                 $formattedinfo = \core_availability\info::format_info(
  742                         $mod->availableinfo, $mod->get_course());
  743                 $output = $this->availability_info($formattedinfo, 'isrestricted');
  744             }
  745             return $output;
  746         }
  747         // this is a teacher who is allowed to see module but still should see the
  748         // information that module is not available to all/some students
  749         $modcontext = context_module::instance($mod->id);
  750         $canviewhidden = has_capability('moodle/course:viewhiddenactivities', $modcontext);
  751         if ($canviewhidden && !$mod->visible) {
  752             // This module is hidden but current user has capability to see it.
  753             // Do not display the availability info if the whole section is hidden.
  754             if ($mod->get_section_info()->visible) {
  755                 $output .= $this->availability_info(get_string('hiddenfromstudents'), 'ishidden');
  756             }
  757         } else if ($mod->is_stealth()) {
  758             // This module is available but is normally not displayed on the course page
  759             // (this user can see it because they can manage it).
  760             $output .= $this->availability_info(get_string('hiddenoncoursepage'), 'isstealth');
  761         }
  762         if ($canviewhidden && !empty($CFG->enableavailability)) {
  763             // Display information about conditional availability.
  764             // Don't add availability information if user is not editing and activity is hidden.
  765             if ($mod->visible || $this->page->user_is_editing()) {
  766                 $hidinfoclass = 'isrestricted isfullinfo';
  767                 if (!$mod->visible) {
  768                     $hidinfoclass .= ' hide';
  769                 }
  770                 $ci = new \core_availability\info_module($mod);
  771                 $fullinfo = $ci->get_full_information();
  772                 if ($fullinfo) {
  773                     $formattedinfo = \core_availability\info::format_info(
  774                             $fullinfo, $mod->get_course());
  775                     $output .= $this->availability_info($formattedinfo, $hidinfoclass);
  776                 }
  777             }
  778         }
  779         return $output;
  780     }
  781 
  782     /**
  783      * Renders HTML to display one course module for display within a section.
  784      *
  785      * This function calls:
  786      * {@link core_course_renderer::course_section_cm()}
  787      *
  788      * @param stdClass $course
  789      * @param completion_info $completioninfo
  790      * @param cm_info $mod
  791      * @param int|null $sectionreturn
  792      * @param array $displayoptions
  793      * @return String
  794      */
  795     public function course_section_cm_list_item($course, &$completioninfo, cm_info $mod, $sectionreturn, $displayoptions = array()) {
  796         $output = '';
  797         if ($modulehtml = $this->course_section_cm($course, $completioninfo, $mod, $sectionreturn, $displayoptions)) {
  798             $modclasses = 'activity ' . $mod->modname . ' modtype_' . $mod->modname . ' ' . $mod->extraclasses;
  799             $output .= html_writer::tag('li', $modulehtml, array('class' => $modclasses, 'id' => 'module-' . $mod->id));
  800         }
  801         return $output;
  802     }
  803 
  804     /**
  805      * Renders HTML to display one course module in a course section
  806      *
  807      * This includes link, content, availability, completion info and additional information
  808      * that module type wants to display (i.e. number of unread forum posts)
  809      *
  810      * This function calls:
  811      * {@link core_course_renderer::course_section_cm_name()}
  812      * {@link core_course_renderer::course_section_cm_text()}
  813      * {@link core_course_renderer::course_section_cm_availability()}
  814      * {@link core_course_renderer::course_section_cm_completion()}
  815      * {@link course_get_cm_edit_actions()}
  816      * {@link core_course_renderer::course_section_cm_edit_actions()}
  817      *
  818      * @param stdClass $course
  819      * @param completion_info $completioninfo
  820      * @param cm_info $mod
  821      * @param int|null $sectionreturn
  822      * @param array $displayoptions
  823      * @return string
  824      */
  825     public function course_section_cm($course, &$completioninfo, cm_info $mod, $sectionreturn, $displayoptions = array()) {
  826         $output = '';
  827         // We return empty string (because course module will not be displayed at all)
  828         // if:
  829         // 1) The activity is not visible to users
  830         // and
  831         // 2) The 'availableinfo' is empty, i.e. the activity was
  832         //     hidden in a way that leaves no info, such as using the
  833         //     eye icon.
  834         if (!$mod->is_visible_on_course_page()) {
  835             return $output;
  836         }
  837 
  838         $indentclasses = 'mod-indent';
  839         if (!empty($mod->indent)) {
  840             $indentclasses .= ' mod-indent-'.$mod->indent;
  841             if ($mod->indent > 15) {
  842                 $indentclasses .= ' mod-indent-huge';
  843             }
  844         }
  845 
  846         $output .= html_writer::start_tag('div');
  847 
  848         if ($this->page->user_is_editing()) {
  849             $output .= course_get_cm_move($mod, $sectionreturn);
  850         }
  851 
  852         $output .= html_writer::start_tag('div', array('class' => 'mod-indent-outer'));
  853 
  854         // This div is used to indent the content.
  855         $output .= html_writer::div('', $indentclasses);
  856 
  857         // Start a wrapper for the actual content to keep the indentation consistent
  858         $output .= html_writer::start_tag('div');
  859 
  860         // Display the link to the module (or do nothing if module has no url)
  861         $cmname = $this->course_section_cm_name($mod, $displayoptions);
  862 
  863         if (!empty($cmname)) {
  864             // Start the div for the activity title, excluding the edit icons.
  865             $output .= html_writer::start_tag('div', array('class' => 'activityinstance'));
  866             $output .= $cmname;
  867 
  868 
  869             // Module can put text after the link (e.g. forum unread)
  870             $output .= $mod->afterlink;
  871 
  872             // Closing the tag which contains everything but edit icons. Content part of the module should not be part of this.
  873             $output .= html_writer::end_tag('div'); // .activityinstance
  874         }
  875 
  876         // If there is content but NO link (eg label), then display the
  877         // content here (BEFORE any icons). In this case cons must be
  878         // displayed after the content so that it makes more sense visually
  879         // and for accessibility reasons, e.g. if you have a one-line label
  880         // it should work similarly (at least in terms of ordering) to an
  881         // activity.
  882         $contentpart = $this->course_section_cm_text($mod, $displayoptions);
  883         $url = $mod->url;
  884         if (empty($url)) {
  885             $output .= $contentpart;
  886         }
  887 
  888         $modicons = '';
  889         if ($this->page->user_is_editing()) {
  890             $editactions = course_get_cm_edit_actions($mod, $mod->indent, $sectionreturn);
  891             $modicons .= ' '. $this->course_section_cm_edit_actions($editactions, $mod, $displayoptions);
  892             $modicons .= $mod->afterediticons;
  893         }
  894 
  895         $modicons .= $this->course_section_cm_completion($course, $completioninfo, $mod, $displayoptions);
  896 
  897         if (!empty($modicons)) {
  898             $output .= html_writer::span($modicons, 'actions');
  899         }
  900 
  901         // Show availability info (if module is not available).
  902         $output .= $this->course_section_cm_availability($mod, $displayoptions);
  903 
  904         // If there is content AND a link, then display the content here
  905         // (AFTER any icons). Otherwise it was displayed before
  906         if (!empty($url)) {
  907             $output .= $contentpart;
  908         }
  909 
  910         $output .= html_writer::end_tag('div'); // $indentclasses
  911 
  912         // End of indentation div.
  913         $output .= html_writer::end_tag('div');
  914 
  915         $output .= html_writer::end_tag('div');
  916         return $output;
  917     }
  918 
  919     /**
  920      * Message displayed to the user when they try to access unavailable activity following URL
  921      *
  922      * This method is a very simplified version of {@link course_section_cm()} to be part of the error
  923      * notification only. It also does not check if module is visible on course page or not.
  924      *
  925      * The message will be displayed inside notification!
  926      *
  927      * @param cm_info $cm
  928      * @return string
  929      */
  930     public function course_section_cm_unavailable_error_message(cm_info $cm) {
  931         if ($cm->uservisible) {
  932             return null;
  933         }
  934         if (!$cm->availableinfo) {
  935             return get_string('activityiscurrentlyhidden');
  936         }
  937 
  938         $altname = get_accesshide(' ' . $cm->modfullname);
  939         $name = html_writer::empty_tag('img', array('src' => $cm->get_icon_url(),
  940                 'class' => 'iconlarge activityicon', 'alt' => ' ', 'role' => 'presentation')) .
  941             html_writer::tag('span', ' '.$cm->get_formatted_name() . $altname, array('class' => 'instancename'));
  942         $formattedinfo = \core_availability\info::format_info($cm->availableinfo, $cm->get_course());
  943         return html_writer::div($name, 'activityinstance-error') .
  944         html_writer::div($formattedinfo, 'availabilityinfo-error');
  945     }
  946 
  947     /**
  948      * Renders HTML to display a list of course modules in a course section
  949      * Also displays "move here" controls in Javascript-disabled mode
  950      *
  951      * This function calls {@link core_course_renderer::course_section_cm()}
  952      *
  953      * @param stdClass $course course object
  954      * @param int|stdClass|section_info $section relative section number or section object
  955      * @param int $sectionreturn section number to return to
  956      * @param int $displayoptions
  957      * @return void
  958      */
  959     public function course_section_cm_list($course, $section, $sectionreturn = null, $displayoptions = array()) {
  960         global $USER;
  961 
  962         $output = '';
  963         $modinfo = get_fast_modinfo($course);
  964         if (is_object($section)) {
  965             $section = $modinfo->get_section_info($section->section);
  966         } else {
  967             $section = $modinfo->get_section_info($section);
  968         }
  969         $completioninfo = new completion_info($course);
  970 
  971         // check if we are currently in the process of moving a module with JavaScript disabled
  972         $ismoving = $this->page->user_is_editing() && ismoving($course->id);
  973         if ($ismoving) {
  974             $movingpix = new pix_icon('movehere', get_string('movehere'), 'moodle', array('class' => 'movetarget'));
  975             $strmovefull = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'"));
  976         }
  977 
  978         // Get the list of modules visible to user (excluding the module being moved if there is one)
  979         $moduleshtml = array();
  980         if (!empty($modinfo->sections[$section->section])) {
  981             foreach ($modinfo->sections[$section->section] as $modnumber) {
  982                 $mod = $modinfo->cms[$modnumber];
  983 
  984                 if ($ismoving and $mod->id == $USER->activitycopy) {
  985                     // do not display moving mod
  986                     continue;
  987                 }
  988 
  989                 if ($modulehtml = $this->course_section_cm_list_item($course,
  990                         $completioninfo, $mod, $sectionreturn, $displayoptions)) {
  991                     $moduleshtml[$modnumber] = $modulehtml;
  992                 }
  993             }
  994         }
  995 
  996         $sectionoutput = '';
  997         if (!empty($moduleshtml) || $ismoving) {
  998             foreach ($moduleshtml as $modnumber => $modulehtml) {
  999                 if ($ismoving) {
 1000                     $movingurl = new moodle_url('/course/mod.php', array('moveto' => $modnumber, 'sesskey' => sesskey()));
 1001                     $sectionoutput .= html_writer::tag('li',
 1002                             html_writer::link($movingurl, $this->output->render($movingpix), array('title' => $strmovefull)),
 1003                             array('class' => 'movehere'));
 1004                 }
 1005 
 1006                 $sectionoutput .= $modulehtml;
 1007             }
 1008 
 1009             if ($ismoving) {
 1010                 $movingurl = new moodle_url('/course/mod.php', array('movetosection' => $section->id, 'sesskey' => sesskey()));
 1011                 $sectionoutput .= html_writer::tag('li',
 1012                         html_writer::link($movingurl, $this->output->render($movingpix), array('title' => $strmovefull)),
 1013                         array('class' => 'movehere'));
 1014             }
 1015         }
 1016 
 1017         // Always output the section module list.
 1018         $output .= html_writer::tag('ul', $sectionoutput, array('class' => 'section img-text'));
 1019 
 1020         return $output;
 1021     }
 1022 
 1023     /**
 1024      * Displays a custom list of courses with paging bar if necessary
 1025      *
 1026      * If $paginationurl is specified but $totalcount is not, the link 'View more'
 1027      * appears under the list.
 1028      *
 1029      * If both $paginationurl and $totalcount are specified, and $totalcount is
 1030      * bigger than count($courses), a paging bar is displayed above and under the
 1031      * courses list.
 1032      *
 1033      * @param array $courses array of course records (or instances of core_course_list_element) to show on this page
 1034      * @param bool $showcategoryname whether to add category name to the course description
 1035      * @param string $additionalclasses additional CSS classes to add to the div.courses
 1036      * @param moodle_url $paginationurl url to view more or url to form links to the other pages in paging bar
 1037      * @param int $totalcount total number of courses on all pages, if omitted $paginationurl will be displayed as 'View more' link
 1038      * @param int $page current page number (defaults to 0 referring to the first page)
 1039      * @param int $perpage number of records per page (defaults to $CFG->coursesperpage)
 1040      * @return string
 1041      */
 1042     public function courses_list($courses, $showcategoryname = false, $additionalclasses = null, $paginationurl = null, $totalcount = null, $page = 0, $perpage = null) {
 1043         global $CFG;
 1044         // create instance of coursecat_helper to pass display options to function rendering courses list
 1045         $chelper = new coursecat_helper();
 1046         if ($showcategoryname) {
 1047             $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT);
 1048         } else {
 1049             $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED);
 1050         }
 1051         if ($totalcount !== null && $paginationurl !== null) {
 1052             // add options to display pagination
 1053             if ($perpage === null) {
 1054                 $perpage = $CFG->coursesperpage;
 1055             }
 1056             $chelper->set_courses_display_options(array(
 1057                 'limit' => $perpage,
 1058                 'offset' => ((int)$page) * $perpage,
 1059                 'paginationurl' => $paginationurl,
 1060             ));
 1061         } else if ($paginationurl !== null) {
 1062             // add options to display 'View more' link
 1063             $chelper->set_courses_display_options(array('viewmoreurl' => $paginationurl));
 1064             $totalcount = count($courses) + 1; // has to be bigger than count($courses) otherwise link will not be displayed
 1065         }
 1066         $chelper->set_attributes(array('class' => $additionalclasses));
 1067         $content = $this->coursecat_courses($chelper, $courses, $totalcount);
 1068         return $content;
 1069     }
 1070 
 1071     /**
 1072      * Displays one course in the list of courses.
 1073      *
 1074      * This is an internal function, to display an information about just one course
 1075      * please use {@link core_course_renderer::course_info_box()}
 1076      *
 1077      * @param coursecat_helper $chelper various display options
 1078      * @param core_course_list_element|stdClass $course
 1079      * @param string $additionalclasses additional classes to add to the main <div> tag (usually
 1080      *    depend on the course position in list - first/last/even/odd)
 1081      * @return string
 1082      */
 1083     protected function coursecat_coursebox(coursecat_helper $chelper, $course, $additionalclasses = '') {
 1084         if (!isset($this->strings->summary)) {
 1085             $this->strings->summary = get_string('summary');
 1086         }
 1087         if ($chelper->get_show_courses() <= self::COURSECAT_SHOW_COURSES_COUNT) {
 1088             return '';
 1089         }
 1090         if ($course instanceof stdClass) {
 1091             $course = new core_course_list_element($course);
 1092         }
 1093         $content = '';
 1094         $classes = trim('coursebox clearfix '. $additionalclasses);
 1095         if ($chelper->get_show_courses() >= self::COURSECAT_SHOW_COURSES_EXPANDED) {
 1096             $nametag = 'h3';
 1097         } else {
 1098             $classes .= ' collapsed';
 1099             $nametag = 'div';
 1100         }
 1101 
 1102         // .coursebox
 1103         $content .= html_writer::start_tag('div', array(
 1104             'class' => $classes,
 1105             'data-courseid' => $course->id,
 1106             'data-type' => self::COURSECAT_TYPE_COURSE,
 1107         ));
 1108 
 1109         $content .= html_writer::start_tag('div', array('class' => 'info'));
 1110 
 1111         // course name
 1112         $coursename = $chelper->get_course_formatted_name($course);
 1113         $coursenamelink = html_writer::link(new moodle_url('/course/view.php', array('id' => $course->id)),
 1114                                             $coursename, array('class' => $course->visible ? '' : 'dimmed'));
 1115         $content .= html_writer::tag($nametag, $coursenamelink, array('class' => 'coursename'));
 1116         // If we display course in collapsed form but the course has summary or course contacts, display the link to the info page.
 1117         $content .= html_writer::start_tag('div', array('class' => 'moreinfo'));
 1118         if ($chelper->get_show_courses() < self::COURSECAT_SHOW_COURSES_EXPANDED) {
 1119             if ($course->has_summary() || $course->has_course_contacts() || $course->has_course_overviewfiles()) {
 1120                 $url = new moodle_url('/course/info.php', array('id' => $course->id));
 1121                 $image = $this->output->pix_icon('i/info', $this->strings->summary);
 1122                 $content .= html_writer::link($url, $image, array('title' => $this->strings->summary));
 1123                 // Make sure JS file to expand course content is included.
 1124                 $this->coursecat_include_js();
 1125             }
 1126         }
 1127         $content .= html_writer::end_tag('div'); // .moreinfo
 1128 
 1129         // print enrolmenticons
 1130         if ($icons = enrol_get_course_info_icons($course)) {
 1131             $content .= html_writer::start_tag('div', array('class' => 'enrolmenticons'));
 1132             foreach ($icons as $pix_icon) {
 1133                 $content .= $this->render($pix_icon);
 1134             }
 1135             $content .= html_writer::end_tag('div'); // .enrolmenticons
 1136         }
 1137 
 1138         $content .= html_writer::end_tag('div'); // .info
 1139 
 1140         $content .= html_writer::start_tag('div', array('class' => 'content'));
 1141         $content .= $this->coursecat_coursebox_content($chelper, $course);
 1142         $content .= html_writer::end_tag('div'); // .content
 1143 
 1144         $content .= html_writer::end_tag('div'); // .coursebox
 1145         return $content;
 1146     }
 1147 
 1148     /**
 1149      * Returns HTML to display course content (summary, course contacts and optionally category name)
 1150      *
 1151      * This method is called from coursecat_coursebox() and may be re-used in AJAX
 1152      *
 1153      * @param coursecat_helper $chelper various display options
 1154      * @param stdClass|core_course_list_element $course
 1155      * @return string
 1156      */
 1157     protected function coursecat_coursebox_content(coursecat_helper $chelper, $course) {
 1158         global $CFG;
 1159         if ($chelper->get_show_courses() < self::COURSECAT_SHOW_COURSES_EXPANDED) {
 1160             return '';
 1161         }
 1162         if ($course instanceof stdClass) {
 1163             $course = new core_course_list_element($course);
 1164         }
 1165         $content = '';
 1166 
 1167         // display course summary
 1168         if ($course->has_summary()) {
 1169             $content .= html_writer::start_tag('div', array('class' => 'summary'));
 1170             $content .= $chelper->get_course_formatted_summary($course,
 1171                     array('overflowdiv' => true, 'noclean' => true, 'para' => false));
 1172             $content .= html_writer::end_tag('div'); // .summary
 1173         }
 1174 
 1175         // display course overview files
 1176         $contentimages = $contentfiles = '';
 1177         foreach ($course->get_course_overviewfiles() as $file) {
 1178             $isimage = $file->is_valid_image();
 1179             $url = file_encode_url("$CFG->wwwroot/pluginfile.php",
 1180                     '/'. $file->get_contextid(). '/'. $file->get_component(). '/'.
 1181                     $file->get_filearea(). $file->get_filepath(). $file->get_filename(), !$isimage);
 1182             if ($isimage) {
 1183                 $contentimages .= html_writer::tag('div',
 1184                         html_writer::empty_tag('img', array('src' => $url)),
 1185                         array('class' => 'courseimage'));
 1186             } else {
 1187                 $image = $this->output->pix_icon(file_file_icon($file, 24), $file->get_filename(), 'moodle');
 1188                 $filename = html_writer::tag('span', $image, array('class' => 'fp-icon')).
 1189                         html_writer::tag('span', $file->get_filename(), array('class' => 'fp-filename'));
 1190                 $contentfiles .= html_writer::tag('span',
 1191                         html_writer::link($url, $filename),
 1192                         array('class' => 'coursefile fp-filename-icon'));
 1193             }
 1194         }
 1195         $content .= $contentimages. $contentfiles;
 1196 
 1197         // Display course contacts. See core_course_list_element::get_course_contacts().
 1198         if ($course->has_course_contacts()) {
 1199             $content .= html_writer::start_tag('ul', array('class' => 'teachers'));
 1200             foreach ($course->get_course_contacts() as $coursecontact) {
 1201                 $rolenames = array_map(function ($role) {
 1202                     return $role->displayname;
 1203                 }, $coursecontact['roles']);
 1204                 $name = implode(", ", $rolenames).': '.
 1205                         html_writer::link(new moodle_url('/user/view.php',
 1206                                 array('id' => $coursecontact['user']->id, 'course' => SITEID)),
 1207                             $coursecontact['username']);
 1208                 $content .= html_writer::tag('li', $name);
 1209             }
 1210             $content .= html_writer::end_tag('ul'); // .teachers
 1211         }
 1212 
 1213         // display course category if necessary (for example in search results)
 1214         if ($chelper->get_show_courses() == self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT) {
 1215             if ($cat = core_course_category::get($course->category, IGNORE_MISSING)) {
 1216                 $content .= html_writer::start_tag('div', array('class' => 'coursecat'));
 1217                 $content .= get_string('category').': '.
 1218                         html_writer::link(new moodle_url('/course/index.php', array('categoryid' => $cat->id)),
 1219                                 $cat->get_formatted_name(), array('class' => $cat->visible ? '' : 'dimmed'));
 1220                 $content .= html_writer::end_tag('div'); // .coursecat
 1221             }
 1222         }
 1223 
 1224         return $content;
 1225     }
 1226 
 1227     /**
 1228      * Renders the list of courses
 1229      *
 1230      * This is internal function, please use {@link core_course_renderer::courses_list()} or another public
 1231      * method from outside of the class
 1232      *
 1233      * If list of courses is specified in $courses; the argument $chelper is only used
 1234      * to retrieve display options and attributes, only methods get_show_courses(),
 1235      * get_courses_display_option() and get_and_erase_attributes() are called.
 1236      *
 1237      * @param coursecat_helper $chelper various display options
 1238      * @param array $courses the list of courses to display
 1239      * @param int|null $totalcount total number of courses (affects display mode if it is AUTO or pagination if applicable),
 1240      *     defaulted to count($courses)
 1241      * @return string
 1242      */
 1243     protected function coursecat_courses(coursecat_helper $chelper, $courses, $totalcount = null) {
 1244         global $CFG;
 1245         if ($totalcount === null) {
 1246             $totalcount = count($courses);
 1247         }
 1248         if (!$totalcount) {
 1249             // Courses count is cached during courses retrieval.
 1250             return '';
 1251         }
 1252 
 1253         if ($chelper->get_show_courses() == self::COURSECAT_SHOW_COURSES_AUTO) {
 1254             // In 'auto' course display mode we analyse if number of courses is more or less than $CFG->courseswithsummarieslimit
 1255             if ($totalcount <= $CFG->courseswithsummarieslimit) {
 1256                 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED);
 1257             } else {
 1258                 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_COLLAPSED);
 1259             }
 1260         }
 1261 
 1262         // prepare content of paging bar if it is needed
 1263         $paginationurl = $chelper->get_courses_display_option('paginationurl');
 1264         $paginationallowall = $chelper->get_courses_display_option('paginationallowall');
 1265         if ($totalcount > count($courses)) {
 1266             // there are more results that can fit on one page
 1267             if ($paginationurl) {
 1268                 // the option paginationurl was specified, display pagingbar
 1269                 $perpage = $chelper->get_courses_display_option('limit', $CFG->coursesperpage);
 1270                 $page = $chelper->get_courses_display_option('offset') / $perpage;
 1271                 $pagingbar = $this->paging_bar($totalcount, $page, $perpage,
 1272                         $paginationurl->out(false, array('perpage' => $perpage)));
 1273                 if ($paginationallowall) {
 1274                     $pagingbar .= html_writer::tag('div', html_writer::link($paginationurl->out(false, array('perpage' => 'all')),
 1275                             get_string('showall', '', $totalcount)), array('class' => 'paging paging-showall'));
 1276                 }
 1277             } else if ($viewmoreurl = $chelper->get_courses_display_option('viewmoreurl')) {
 1278                 // the option for 'View more' link was specified, display more link
 1279                 $viewmoretext = $chelper->get_courses_display_option('viewmoretext', new lang_string('viewmore'));
 1280                 $morelink = html_writer::tag('div', html_writer::link($viewmoreurl, $viewmoretext),
 1281                         array('class' => 'paging paging-morelink'));
 1282             }
 1283         } else if (($totalcount > $CFG->coursesperpage) && $paginationurl && $paginationallowall) {
 1284             // there are more than one page of results and we are in 'view all' mode, suggest to go back to paginated view mode
 1285             $pagingbar = html_writer::tag('div', html_writer::link($paginationurl->out(false, array('perpage' => $CFG->coursesperpage)),
 1286                 get_string('showperpage', '', $CFG->coursesperpage)), array('class' => 'paging paging-showperpage'));
 1287         }
 1288 
 1289         // display list of courses
 1290         $attributes = $chelper->get_and_erase_attributes('courses');
 1291         $content = html_writer::start_tag('div', $attributes);
 1292 
 1293         if (!empty($pagingbar)) {
 1294             $content .= $pagingbar;
 1295         }
 1296 
 1297         $coursecount = 0;
 1298         foreach ($courses as $course) {
 1299             $coursecount ++;
 1300             $classes = ($coursecount%2) ? 'odd' : 'even';
 1301             if ($coursecount == 1) {
 1302                 $classes .= ' first';
 1303             }
 1304             if ($coursecount >= count($courses)) {
 1305                 $classes .= ' last';
 1306             }
 1307             $content .= $this->coursecat_coursebox($chelper, $course, $classes);
 1308         }
 1309 
 1310         if (!empty($pagingbar)) {
 1311             $content .= $pagingbar;
 1312         }
 1313         if (!empty($morelink)) {
 1314             $content .= $morelink;
 1315         }
 1316 
 1317         $content .= html_writer::end_tag('div'); // .courses
 1318         return $content;
 1319     }
 1320 
 1321     /**
 1322      * Renders the list of subcategories in a category
 1323      *
 1324      * @param coursecat_helper $chelper various display options
 1325      * @param core_course_category $coursecat
 1326      * @param int $depth depth of the category in the current tree
 1327      * @return string
 1328      */
 1329     protected function coursecat_subcategories(coursecat_helper $chelper, $coursecat, $depth) {
 1330         global $CFG;
 1331         $subcategories = array();
 1332         if (!$chelper->get_categories_display_option('nodisplay')) {
 1333             $subcategories = $coursecat->get_children($chelper->get_categories_display_options());
 1334         }
 1335         $totalcount = $coursecat->get_children_count();
 1336         if (!$totalcount) {
 1337             // Note that we call core_course_category::get_children_count() AFTER core_course_category::get_children()
 1338             // to avoid extra DB requests.
 1339             // Categories count is cached during children categories retrieval.
 1340             return '';
 1341         }
 1342 
 1343         // prepare content of paging bar or more link if it is needed
 1344         $paginationurl = $chelper->get_categories_display_option('paginationurl');
 1345         $paginationallowall = $chelper->get_categories_display_option('paginationallowall');
 1346         if ($totalcount > count($subcategories)) {
 1347             if ($paginationurl) {
 1348                 // the option 'paginationurl was specified, display pagingbar
 1349                 $perpage = $chelper->get_categories_display_option('limit', $CFG->coursesperpage);
 1350                 $page = $chelper->get_categories_display_option('offset') / $perpage;
 1351                 $pagingbar = $this->paging_bar($totalcount, $page, $perpage,
 1352                         $paginationurl->out(false, array('perpage' => $perpage)));
 1353                 if ($paginationallowall) {
 1354                     $pagingbar .= html_writer::tag('div', html_writer::link($paginationurl->out(false, array('perpage' => 'all')),
 1355                             get_string('showall', '', $totalcount)), array('class' => 'paging paging-showall'));
 1356                 }
 1357             } else if ($viewmoreurl = $chelper->get_categories_display_option('viewmoreurl')) {
 1358                 // the option 'viewmoreurl' was specified, display more link (if it is link to category view page, add category id)
 1359                 if ($viewmoreurl->compare(new moodle_url('/course/index.php'), URL_MATCH_BASE)) {
 1360                     $viewmoreurl->param('categoryid', $coursecat->id);
 1361                 }
 1362                 $viewmoretext = $chelper->get_categories_display_option('viewmoretext', new lang_string('viewmore'));
 1363                 $morelink = html_writer::tag('div', html_writer::link($viewmoreurl, $viewmoretext),
 1364                         array('class' => 'paging paging-morelink'));
 1365             }
 1366         } else if (($totalcount > $CFG->coursesperpage) && $paginationurl && $paginationallowall) {
 1367             // there are more than one page of results and we are in 'view all' mode, suggest to go back to paginated view mode
 1368             $pagingbar = html_writer::tag('div', html_writer::link($paginationurl->out(false, array('perpage' => $CFG->coursesperpage)),
 1369                 get_string('showperpage', '', $CFG->coursesperpage)), array('class' => 'paging paging-showperpage'));
 1370         }
 1371 
 1372         // display list of subcategories
 1373         $content = html_writer::start_tag('div', array('class' => 'subcategories'));
 1374 
 1375         if (!empty($pagingbar)) {
 1376             $content .= $pagingbar;
 1377         }
 1378 
 1379         foreach ($subcategories as $subcategory) {
 1380             $content .= $this->coursecat_category($chelper, $subcategory, $depth + 1);
 1381         }
 1382 
 1383         if (!empty($pagingbar)) {
 1384             $content .= $pagingbar;
 1385         }
 1386         if (!empty($morelink)) {
 1387             $content .= $morelink;
 1388         }
 1389 
 1390         $content .= html_writer::end_tag('div');
 1391         return $content;
 1392     }
 1393 
 1394     /**
 1395      * Make sure that javascript file for AJAX expanding of courses and categories content is included
 1396      */
 1397     protected function coursecat_include_js() {
 1398         if (!$this->page->requires->should_create_one_time_item_now('core_course_categoryexpanderjsinit')) {
 1399             return;
 1400         }
 1401 
 1402         // We must only load this module once.
 1403         $this->page->requires->yui_module('moodle-course-categoryexpander',
 1404                 'Y.Moodle.course.categoryexpander.init');
 1405     }
 1406 
 1407     /**
 1408      * Returns HTML to display the subcategories and courses in the given category
 1409      *
 1410      * This method is re-used by AJAX to expand content of not loaded category
 1411      *
 1412      * @param coursecat_helper $chelper various display options
 1413      * @param core_course_category $coursecat
 1414      * @param int $depth depth of the category in the current tree
 1415      * @return string
 1416      */
 1417     protected function coursecat_category_content(coursecat_helper $chelper, $coursecat, $depth) {
 1418         $content = '';
 1419         // Subcategories
 1420         $content .= $this->coursecat_subcategories($chelper, $coursecat, $depth);
 1421 
 1422         // AUTO show courses: Courses will be shown expanded if this is not nested category,
 1423         // and number of courses no bigger than $CFG->courseswithsummarieslimit.
 1424         $showcoursesauto = $chelper->get_show_courses() == self::COURSECAT_SHOW_COURSES_AUTO;
 1425         if ($showcoursesauto && $depth) {
 1426             // this is definitely collapsed mode
 1427             $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_COLLAPSED);
 1428         }
 1429 
 1430         // Courses
 1431         if ($chelper->get_show_courses() > core_course_renderer::COURSECAT_SHOW_COURSES_COUNT) {
 1432             $courses = array();
 1433             if (!$chelper->get_courses_display_option('nodisplay')) {
 1434                 $courses = $coursecat->get_courses($chelper->get_courses_display_options());
 1435             }
 1436             if ($viewmoreurl = $chelper->get_courses_display_option('viewmoreurl')) {
 1437                 // the option for 'View more' link was specified, display more link (if it is link to category view page, add category id)
 1438                 if ($viewmoreurl->compare(new moodle_url('/course/index.php'), URL_MATCH_BASE)) {
 1439                     $chelper->set_courses_display_option('viewmoreurl', new moodle_url($viewmoreurl, array('categoryid' => $coursecat->id)));
 1440                 }
 1441             }
 1442             $content .= $this->coursecat_courses($chelper, $courses, $coursecat->get_courses_count());
 1443         }
 1444 
 1445         if ($showcoursesauto) {
 1446             // restore the show_courses back to AUTO
 1447             $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_AUTO);
 1448         }
 1449 
 1450         return $content;
 1451     }
 1452 
 1453     /**
 1454      * Returns HTML to display a course category as a part of a tree
 1455      *
 1456      * This is an internal function, to display a particular category and all its contents
 1457      * use {@link core_course_renderer::course_category()}
 1458      *
 1459      * @param coursecat_helper $chelper various display options
 1460      * @param core_course_category $coursecat
 1461      * @param int $depth depth of this category in the current tree
 1462      * @return string
 1463      */
 1464     protected function coursecat_category(coursecat_helper $chelper, $coursecat, $depth) {
 1465         // open category tag
 1466         $classes = array('category');
 1467         if (empty($coursecat->visible)) {
 1468             $classes[] = 'dimmed_category';
 1469         }
 1470         if ($chelper->get_subcat_depth() > 0 && $depth >= $chelper->get_subcat_depth()) {
 1471             // do not load content
 1472             $categorycontent = '';
 1473             $classes[] = 'notloaded';
 1474             if ($coursecat->get_children_count() ||
 1475                     ($chelper->get_show_courses() >= self::COURSECAT_SHOW_COURSES_COLLAPSED && $coursecat->get_courses_count())) {
 1476                 $classes[] = 'with_children';
 1477                 $classes[] = 'collapsed';
 1478             }
 1479         } else {
 1480             // load category content
 1481             $categorycontent = $this->coursecat_category_content($chelper, $coursecat, $depth);
 1482             $classes[] = 'loaded';
 1483             if (!empty($categorycontent)) {
 1484                 $classes[] = 'with_children';
 1485                 // Category content loaded with children.
 1486                 $this->categoryexpandedonload = true;
 1487             }
 1488         }
 1489 
 1490         // Make sure JS file to expand category content is included.
 1491         $this->coursecat_include_js();
 1492 
 1493         $content = html_writer::start_tag('div', array(
 1494             'class' => join(' ', $classes),
 1495             'data-categoryid' => $coursecat->id,
 1496             'data-depth' => $depth,
 1497             'data-showcourses' => $chelper->get_show_courses(),
 1498             'data-type' => self::COURSECAT_TYPE_CATEGORY,
 1499         ));
 1500 
 1501         // category name
 1502         $categoryname = $coursecat->get_formatted_name();
 1503         $categoryname = html_writer::link(new moodle_url('/course/index.php',
 1504                 array('categoryid' => $coursecat->id)),
 1505                 $categoryname);
 1506         if ($chelper->get_show_courses() == self::COURSECAT_SHOW_COURSES_COUNT
 1507                 && ($coursescount = $coursecat->get_courses_count())) {
 1508             $categoryname .= html_writer::tag('span', ' ('. $coursescount.')',
 1509                     array('title' => get_string('numberofcourses'), 'class' => 'numberofcourse'));
 1510         }
 1511         $content .= html_writer::start_tag('div', array('class' => 'info'));
 1512 
 1513         $content .= html_writer::tag(($depth > 1) ? 'h4' : 'h3', $categoryname, array('class' => 'categoryname'));
 1514         $content .= html_writer::end_tag('div'); // .info
 1515 
 1516         // add category content to the output
 1517         $content .= html_writer::tag('div', $categorycontent, array('class' => 'content'));
 1518 
 1519         $content .= html_writer::end_tag('div'); // .category
 1520 
 1521         // Return the course category tree HTML
 1522         return $content;
 1523     }
 1524 
 1525     /**
 1526      * Returns HTML to display a tree of subcategories and courses in the given category
 1527      *
 1528      * @param coursecat_helper $chelper various display options
 1529      * @param core_course_category $coursecat top category (this category's name and description will NOT be added to the tree)
 1530      * @return string
 1531      */
 1532     protected function coursecat_tree(coursecat_helper $chelper, $coursecat) {
 1533         // Reset the category expanded flag for this course category tree first.
 1534         $this->categoryexpandedonload = false;
 1535         $categorycontent = $this->coursecat_category_content($chelper, $coursecat, 0);
 1536         if (empty($categorycontent)) {
 1537             return '';
 1538         }
 1539 
 1540         // Start content generation
 1541         $content = '';
 1542         $attributes = $chelper->get_and_erase_attributes('course_category_tree clearfix');
 1543         $content .= html_writer::start_tag('div', $attributes);
 1544 
 1545         if ($coursecat->get_children_count()) {
 1546             $classes = array(
 1547                 'collapseexpand',
 1548             );
 1549 
 1550             // Check if the category content contains subcategories with children's content loaded.
 1551             if ($this->categoryexpandedonload) {
 1552                 $classes[] = 'collapse-all';
 1553                 $linkname = get_string('collapseall');
 1554             } else {
 1555                 $linkname = get_string('expandall');
 1556             }
 1557 
 1558             // Only show the collapse/expand if there are children to expand.
 1559             $content .= html_writer::start_tag('div', array('class' => 'collapsible-actions'));
 1560             $content .= html_writer::link('#', $linkname, array('class' => implode(' ', $classes)));
 1561             $content .= html_writer::end_tag('div');
 1562             $this->page->requires->strings_for_js(array('collapseall', 'expandall'), 'moodle');
 1563         }
 1564 
 1565         $content .= html_writer::tag('div', $categorycontent, array('class' => 'content'));
 1566 
 1567         $content .= html_writer::end_tag('div'); // .course_category_tree
 1568 
 1569         return $content;
 1570     }
 1571 
 1572     /**
 1573      * Renders HTML to display particular course category - list of it's subcategories and courses
 1574      *
 1575      * Invoked from /course/index.php
 1576      *
 1577      * @param int|stdClass|core_course_category $category
 1578      */
 1579     public function course_category($category) {
 1580         global $CFG;
 1581         $coursecat = core_course_category::get(is_object($category) ? $category->id : $category);
 1582         $site = get_site();
 1583         $output = '';
 1584 
 1585         if (can_edit_in_category($coursecat->id)) {
 1586             // Add 'Manage' button if user has permissions to edit this category.
 1587             $managebutton = $this->single_button(new moodle_url('/course/management.php',
 1588                 array('categoryid' => $coursecat->id)), get_string('managecourses'), 'get');
 1589             $this->page->set_button($managebutton);
 1590         }
 1591         if (!$coursecat->id) {
 1592             if (core_course_category::count_all() == 1) {
 1593                 // There exists only one category in the system, do not display link to it
 1594                 $coursecat = core_course_category::get_default();
 1595                 $strfulllistofcourses = get_string('fulllistofcourses');
 1596                 $this->page->set_title("$site->shortname: $strfulllistofcourses");
 1597             } else {
 1598                 $strcategories = get_string('categories');
 1599                 $this->page->set_title("$site->shortname: $strcategories");
 1600             }
 1601         } else {
 1602             $title = $site->shortname;
 1603             if (core_course_category::count_all() > 1) {
 1604                 $title .= ": ". $coursecat->get_formatted_name();
 1605             }
 1606             $this->page->set_title($title);
 1607 
 1608             // Print the category selector
 1609             if (core_course_category::count_all() > 1) {
 1610                 $output .= html_writer::start_tag('div', array('class' => 'categorypicker'));
 1611                 $select = new single_select(new moodle_url('/course/index.php'), 'categoryid',
 1612                         core_course_category::make_categories_list(), $coursecat->id, null, 'switchcategory');
 1613                 $select->set_label(get_string('categories').':');
 1614                 $output .= $this->render($select);
 1615                 $output .= html_writer::end_tag('div'); // .categorypicker
 1616             }
 1617         }
 1618 
 1619         // Print current category description
 1620         $chelper = new coursecat_helper();
 1621         if ($description = $chelper->get_category_formatted_description($coursecat)) {
 1622             $output .= $this->box($description, array('class' => 'generalbox info'));
 1623         }
 1624 
 1625         // Prepare parameters for courses and categories lists in the tree
 1626         $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_AUTO)
 1627                 ->set_attributes(array('class' => 'category-browse category-browse-'.$coursecat->id));
 1628 
 1629         $coursedisplayoptions = array();
 1630         $catdisplayoptions = array();
 1631         $browse = optional_param('browse', null, PARAM_ALPHA);
 1632         $perpage = optional_param('perpage', $CFG->coursesperpage, PARAM_INT);
 1633         $page = optional_param('page', 0, PARAM_INT);
 1634         $baseurl = new moodle_url('/course/index.php');
 1635         if ($coursecat->id) {
 1636             $baseurl->param('categoryid', $coursecat->id);
 1637         }
 1638         if ($perpage != $CFG->coursesperpage) {
 1639             $baseurl->param('perpage', $perpage);
 1640         }
 1641         $coursedisplayoptions['limit'] = $perpage;
 1642         $catdisplayoptions['limit'] = $perpage;
 1643         if ($browse === 'courses' || !$coursecat->has_children()) {
 1644             $coursedisplayoptions['offset'] = $page * $perpage;
 1645             $coursedisplayoptions['paginationurl'] = new moodle_url($baseurl, array('browse' => 'courses'));
 1646             $catdisplayoptions['nodisplay'] = true;
 1647             $catdisplayoptions['viewmoreurl'] = new moodle_url($baseurl, array('browse' => 'categories'));
 1648             $catdisplayoptions['viewmoretext'] = new lang_string('viewallsubcategories');
 1649         } else if ($browse === 'categories' || !$coursecat->has_courses()) {
 1650             $coursedisplayoptions['nodisplay'] = true;
 1651             $catdisplayoptions['offset'] = $page * $perpage;
 1652             $catdisplayoptions['paginationurl'] = new moodle_url($baseurl, array('browse' => 'categories'));
 1653             $coursedisplayoptions['viewmoreurl'] = new moodle_url($baseurl, array('browse' => 'courses'));
 1654             $coursedisplayoptions['viewmoretext'] = new lang_string('viewallcourses');
 1655         } else {
 1656             // we have a category that has both subcategories and courses, display pagination separately
 1657             $coursedisplayoptions['viewmoreurl'] = new moodle_url($baseurl, array('browse' => 'courses', 'page' => 1));
 1658             $catdisplayoptions['viewmoreurl'] = new moodle_url($baseurl, array('browse' => 'categories', 'page' => 1));
 1659         }
 1660         $chelper->set_courses_display_options($coursedisplayoptions)->set_categories_display_options($catdisplayoptions);
 1661         // Add course search form.
 1662         $output .= $this->course_search_form();
 1663 
 1664         // Display course category tree.
 1665         $output .= $this->coursecat_tree($chelper, $coursecat);
 1666 
 1667         // Add action buttons
 1668         $output .= $this->container_start('buttons');
 1669         $context = get_category_or_system_context($coursecat->id);
 1670         if (has_capability('moodle/course:create', $context)) {
 1671             // Print link to create a new course, for the 1st available category.
 1672             if ($coursecat->id) {
 1673                 $url = new moodle_url('/course/edit.php', array('category' => $coursecat->id, 'returnto' => 'category'));
 1674             } else {
 1675                 $url = new moodle_url('/course/edit.php', array('category' => $CFG->defaultrequestcategory, 'returnto' => 'topcat'));
 1676             }
 1677             $output .= $this->single_button($url, get_string('addnewcourse'), 'get');
 1678         }
 1679         ob_start();
 1680         if (core_course_category::count_all() == 1) {
 1681             print_course_request_buttons(context_system::instance());
 1682         } else {
 1683             print_course_request_buttons($context);
 1684         }
 1685         $output .= ob_get_contents();
 1686         ob_end_clean();
 1687         $output .= $this->container_end();
 1688 
 1689         return $output;
 1690     }
 1691 
 1692     /**
 1693      * Serves requests to /course/category.ajax.php
 1694      *
 1695      * In this renderer implementation it may expand the category content or
 1696      * course content.
 1697      *
 1698      * @return string
 1699      * @throws coding_exception
 1700      */
 1701     public function coursecat_ajax() {
 1702         global $DB, $CFG;
 1703 
 1704         $type = required_param('type', PARAM_INT);
 1705 
 1706         if ($type === self::COURSECAT_TYPE_CATEGORY) {
 1707             // This is a request for a category list of some kind.
 1708             $categoryid = required_param('categoryid', PARAM_INT);
 1709             $showcourses = required_param('showcourses', PARAM_INT);
 1710             $depth = required_param('depth', PARAM_INT);
 1711 
 1712             $category = core_course_category::get($categoryid);
 1713 
 1714             $chelper = new coursecat_helper();
 1715             $baseurl = new moodle_url('/course/index.php', array('categoryid' => $categoryid));
 1716             $coursedisplayoptions = array(
 1717                 'limit' => $CFG->coursesperpage,
 1718                 'viewmoreurl' => new moodle_url($baseurl, array('browse' => 'courses', 'page' => 1))
 1719             );
 1720             $catdisplayoptions = array(
 1721                 'limit' => $CFG->coursesperpage,
 1722                 'viewmoreurl' => new moodle_url($baseurl, array('browse' => 'categories', 'page' => 1))
 1723             );
 1724             $chelper->set_show_courses($showcourses)->
 1725                     set_courses_display_options($coursedisplayoptions)->
 1726                     set_categories_display_options($catdisplayoptions);
 1727 
 1728             return $this->coursecat_category_content($chelper, $category, $depth);
 1729         } else if ($type === self::COURSECAT_TYPE_COURSE) {
 1730             // This is a request for the course information.
 1731             $courseid = required_param('courseid', PARAM_INT);
 1732 
 1733             $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
 1734 
 1735             $chelper = new coursecat_helper();
 1736             $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED);
 1737             return $this->coursecat_coursebox_content($chelper, $course);
 1738         } else {
 1739             throw new coding_exception('Invalid request type');
 1740         }
 1741     }
 1742 
 1743     /**
 1744      * Renders html to display search result page
 1745      *
 1746      * @param array $searchcriteria may contain elements: search, blocklist, modulelist, tagid
 1747      * @return string
 1748      */
 1749     public function search_courses($searchcriteria) {
 1750         global $CFG;
 1751         $content = '';
 1752         if (!empty($searchcriteria)) {
 1753             // print search results
 1754 
 1755             $displayoptions = array('sort' => array('displayname' => 1));
 1756             // take the current page and number of results per page from query
 1757             $perpage = optional_param('perpage', 0, PARAM_RAW);
 1758             if ($perpage !== 'all') {
 1759                 $displayoptions['limit'] = ((int)$perpage <= 0) ? $CFG->coursesperpage : (int)$perpage;
 1760                 $page = optional_param('page', 0, PARAM_INT);
 1761                 $displayoptions['offset'] = $displayoptions['limit'] * $page;
 1762             }
 1763             // options 'paginationurl' and 'paginationallowall' are only used in method coursecat_courses()
 1764             $displayoptions['paginationurl'] = new moodle_url('/course/search.php', $searchcriteria);
 1765             $displayoptions['paginationallowall'] = true; // allow adding link 'View all'
 1766 
 1767             $class = 'course-search-result';
 1768             foreach ($searchcriteria as $key => $value) {
 1769                 if (!empty($value)) {
 1770                     $class .= ' course-search-result-'. $key;
 1771                 }
 1772             }
 1773             $chelper = new coursecat_helper();
 1774             $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT)->
 1775                     set_courses_display_options($displayoptions)->
 1776                     set_search_criteria($searchcriteria)->
 1777                     set_attributes(array('class' => $class));
 1778 
 1779             $courses = core_course_category::search_courses($searchcriteria, $chelper->get_courses_display_options());
 1780             $totalcount = core_course_category::search_courses_count($searchcriteria);
 1781             $courseslist = $this->coursecat_courses($chelper, $courses, $totalcount);
 1782 
 1783             if (!$totalcount) {
 1784                 if (!empty($searchcriteria['search'])) {
 1785                     $content .= $this->heading(get_string('nocoursesfound', '', $searchcriteria['search']));
 1786                 } else {
 1787                     $content .= $this->heading(get_string('novalidcourses'));
 1788                 }
 1789             } else {
 1790                 $content .= $this->heading(get_string('searchresults'). ": $totalcount");
 1791                 $content .= $courseslist;
 1792             }
 1793 
 1794             if (!empty($searchcriteria['search'])) {
 1795                 // print search form only if there was a search by search string, otherwise it is confusing
 1796                 $content .= $this->box_start('generalbox mdl-align');
 1797                 $content .= $this->course_search_form($searchcriteria['search']);
 1798                 $content .= $this->box_end();
 1799             }
 1800         } else {
 1801             // just print search form
 1802             $content .= $this->box_start('generalbox mdl-align');
 1803             $content .= $this->course_search_form();
 1804             $content .= $this->box_end();
 1805         }
 1806         return $content;
 1807     }
 1808 
 1809     /**
 1810      * Renders html to print list of courses tagged with particular tag
 1811      *
 1812      * @param int $tagid id of the tag
 1813      * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag
 1814      *             are displayed on the page and the per-page limit may be bigger
 1815      * @param int $fromctx context id where the link was displayed, may be used by callbacks
 1816      *            to display items in the same context first
 1817      * @param int $ctx context id where to search for records
 1818      * @param bool $rec search in subcontexts as well
 1819      * @param array $displayoptions
 1820      * @return string empty string if no courses are marked with this tag or rendered list of courses
 1821      */
 1822     public function tagged_courses($tagid, $exclusivemode = true, $ctx = 0, $rec = true, $displayoptions = null) {
 1823         global $CFG;
 1824         if (empty($displayoptions)) {
 1825             $displayoptions = array();
 1826         }
 1827         $showcategories = core_course_category::count_all() > 1;
 1828         $displayoptions += array('limit' => $CFG->coursesperpage, 'offset' => 0);
 1829         $chelper = new coursecat_helper();
 1830         $searchcriteria = array('tagid' => $tagid, 'ctx' => $ctx, 'rec' => $rec);
 1831         $chelper->set_show_courses($showcategories ? self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT :
 1832                     self::COURSECAT_SHOW_COURSES_EXPANDED)->
 1833                 set_search_criteria($searchcriteria)->
 1834                 set_courses_display_options($displayoptions)->
 1835                 set_attributes(array('class' => 'course-search-result course-search-result-tagid'));
 1836                 // (we set the same css class as in search results by tagid)
 1837         if ($totalcount = core_course_category::search_courses_count($searchcriteria)) {
 1838             $courses = core_course_category::search_courses($searchcriteria, $chelper->get_courses_display_options());
 1839             if ($exclusivemode) {
 1840                 return $this->coursecat_courses($chelper, $courses, $totalcount);
 1841             } else {
 1842                 $tagfeed = new core_tag\output\tagfeed();
 1843                 $img = $this->output->pix_icon('i/course', '');
 1844                 foreach ($courses as $course) {
 1845                     $url = course_get_url($course);
 1846                     $imgwithlink = html_writer::link($url, $img);
 1847                     $coursename = html_writer::link($url, $course->get_formatted_name());
 1848                     $details = '';
 1849                     if ($showcategories && ($cat = core_course_category::get($course->category, IGNORE_MISSING))) {
 1850                         $details = get_string('category').': '.
 1851                                 html_writer::link(new moodle_url('/course/index.php', array('categoryid' => $cat->id)),
 1852                                         $cat->get_formatted_name(), array('class' => $cat->visible ? '' : 'dimmed'));
 1853                     }
 1854                     $tagfeed->add($imgwithlink, $coursename, $details);
 1855                 }
 1856                 return $this->output->render_from_template('core_tag/tagfeed', $tagfeed->export_for_template($this->output));
 1857             }
 1858         }
 1859         return '';
 1860     }
 1861 
 1862     /**
 1863      * Returns HTML to display one remote course
 1864      *
 1865      * @param stdClass $course remote course information, contains properties:
 1866            id, remoteid, shortname, fullname, hostid, summary, summaryformat, cat_name, hostname
 1867      * @return string
 1868      */
 1869     protected function frontpage_remote_course(stdClass $course) {
 1870         $url = new moodle_url('/auth/mnet/jump.php', array(
 1871             'hostid' => $course->hostid,
 1872             'wantsurl' => '/course/view.php?id='. $course->remoteid
 1873         ));
 1874 
 1875         $output = '';
 1876         $output .= html_writer::start_tag('div', array('class' => 'coursebox remotecoursebox clearfix'));
 1877         $output .= html_writer::start_tag('div', array('class' => 'info'));
 1878         $output .= html_writer::start_tag('h3', array('class' => 'name'));
 1879         $output .= html_writer::link($url, format_string($course->fullname), array('title' => get_string('entercourse')));
 1880         $output .= html_writer::end_tag('h3'); // .name
 1881         $output .= html_writer::tag('div', '', array('class' => 'moreinfo'));
 1882         $output .= html_writer::end_tag('div'); // .info
 1883         $output .= html_writer::start_tag('div', array('class' => 'content'));
 1884         $output .= html_writer::start_tag('div', array('class' => 'summary'));
 1885         $options = new stdClass();
 1886         $options->noclean = true;
 1887         $options->para = false;
 1888         $options->overflowdiv = true;
 1889         $output .= format_text($course->summary, $course->summaryformat, $options);
 1890         $output .= html_writer::end_tag('div'); // .summary
 1891         $addinfo = format_string($course->hostname) . ' : '
 1892             . format_string($course->cat_name) . ' : '
 1893             . format_string($course->shortname);
 1894         $output .= html_writer::tag('div', $addinfo, array('class' => 'remotecourseinfo'));
 1895         $output .= html_writer::end_tag('div'); // .content
 1896         $output .= html_writer::end_tag('div'); // .coursebox
 1897         return $output;
 1898     }
 1899 
 1900     /**
 1901      * Returns HTML to display one remote host
 1902      *
 1903      * @param array $host host information, contains properties: name, url, count
 1904      * @return string
 1905      */
 1906     protected function frontpage_remote_host($host) {
 1907         $output = '';
 1908         $output .= html_writer::start_tag('div', array('class' => 'coursebox remotehost clearfix'));
 1909         $output .= html_writer::start_tag('div', array('class' => 'info'));
 1910         $output .= html_writer::start_tag('h3', array('class' => 'name'));
 1911         $output .= html_writer::link($host['url'], s($host['name']), array('title' => s($host['name'])));
 1912         $output .= html_writer::end_tag('h3'); // .name
 1913         $output .= html_writer::tag('div', '', array('class' => 'moreinfo'));
 1914         $output .= html_writer::end_tag('div'); // .info
 1915         $output .= html_writer::start_tag('div', array('class' => 'content'));
 1916         $output .= html_writer::start_tag('div', array('class' => 'summary'));
 1917         $output .= $host['count'] . ' ' . get_string('courses');
 1918         $output .= html_writer::end_tag('div'); // .content
 1919         $output .= html_writer::end_tag('div'); // .coursebox
 1920         return $output;
 1921     }
 1922 
 1923     /**
 1924      * Returns HTML to print list of courses user is enrolled to for the frontpage
 1925      *
 1926      * Also lists remote courses or remote hosts if MNET authorisation is used
 1927      *
 1928      * @return string
 1929      */
 1930     public function frontpage_my_courses() {
 1931         global $USER, $CFG, $DB;
 1932 
 1933         if (!isloggedin() or isguestuser()) {
 1934             return '';
 1935         }
 1936 
 1937         $output = '';
 1938         $courses  = enrol_get_my_courses('summary, summaryformat');
 1939         $rhosts   = array();
 1940         $rcourses = array();
 1941         if (!empty($CFG->mnet_dispatcher_mode) && $CFG->mnet_dispatcher_mode==='strict') {
 1942             $rcourses = get_my_remotecourses($USER->id);
 1943             $rhosts   = get_my_remotehosts();
 1944         }
 1945 
 1946         if (!empty($courses) || !empty($rcourses) || !empty($rhosts)) {
 1947 
 1948             $chelper = new coursecat_helper();
 1949             if (count($courses) > $CFG->frontpagecourselimit) {
 1950                 // There are more enrolled courses than we can display, display link to 'My courses'.
 1951                 $totalcount = count($courses);
 1952                 $courses = array_slice($courses, 0, $CFG->frontpagecourselimit, true);
 1953                 $chelper->set_courses_display_options(array(
 1954                         'viewmoreurl' => new moodle_url('/my/'),
 1955                         'viewmoretext' => new lang_string('mycourses')
 1956                     ));
 1957             } else {
 1958                 // All enrolled courses are displayed, display link to 'All courses' if there are more courses in system.
 1959                 $chelper->set_courses_display_options(array(
 1960                         'viewmoreurl' => new moodle_url('/course/index.php'),
 1961                         'viewmoretext' => new lang_string('fulllistofcourses')
 1962                     ));
 1963                 $totalcount = $DB->count_records('course') - 1;
 1964             }
 1965             $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED)->
 1966                     set_attributes(array('class' => 'frontpage-course-list-enrolled'));
 1967             $output .= $this->coursecat_courses($chelper, $courses, $totalcount);
 1968 
 1969             // MNET
 1970             if (!empty($rcourses)) {
 1971                 // at the IDP, we know of all the remote courses
 1972                 $output .= html_writer::start_tag('div', array('class' => 'courses'));
 1973                 foreach ($rcourses as $course) {
 1974                     $output .= $this->frontpage_remote_course($course);
 1975                 }
 1976                 $output .= html_writer::end_tag('div'); // .courses
 1977             } elseif (!empty($rhosts)) {
 1978                 // non-IDP, we know of all the remote servers, but not courses
 1979                 $output .= html_writer::start_tag('div', array('class' => 'courses'));
 1980                 foreach ($rhosts as $host) {
 1981                     $output .= $this->frontpage_remote_host($host);
 1982                 }
 1983                 $output .= html_writer::end_tag('div'); // .courses
 1984             }
 1985         }
 1986         return $output;
 1987     }
 1988 
 1989     /**
 1990      * Returns HTML to print list of available courses for the frontpage
 1991      *
 1992      * @return string
 1993      */
 1994     public function frontpage_available_courses() {
 1995         global $CFG;
 1996 
 1997         $chelper = new coursecat_helper();
 1998         $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED)->
 1999                 set_courses_display_options(array(
 2000                     'recursive' => true,
 2001                     'limit' => $CFG->frontpagecourselimit,
 2002                     'viewmoreurl' => new moodle_url('/course/index.php'),
 2003                     'viewmoretext' => new lang_string('fulllistofcourses')));
 2004 
 2005         $chelper->set_attributes(array('class' => 'frontpage-course-list-all'));
 2006         $courses = core_course_category::get(0)->get_courses($chelper->get_courses_display_options());
 2007         $totalcount = core_course_category::get(0)->get_courses_count($chelper->get_courses_display_options());
 2008         if (!$totalcount && !$this->page->user_is_editing() && has_capability('moodle/course:create', context_system::instance())) {
 2009             // Print link to create a new course, for the 1st available category.
 2010             return $this->add_new_course_button();
 2011         }
 2012         return $this->coursecat_courses($chelper, $courses, $totalcount);
 2013     }
 2014 
 2015     /**
 2016      * Returns HTML to the "add new course" button for the page
 2017      *
 2018      * @return string
 2019      */
 2020     public function add_new_course_button() {
 2021         global $CFG;
 2022         // Print link to create a new course, for the 1st available category.
 2023         $output = $this->container_start('buttons');
 2024         $url = new moodle_url('/course/edit.php', array('category' => $CFG->defaultrequestcategory, 'returnto' => 'topcat'));
 2025         $output .= $this->single_button($url, get_string('addnewcourse'), 'get');
 2026         $output .= $this->container_end('buttons');
 2027         return $output;
 2028     }
 2029 
 2030     /**
 2031      * Returns HTML to print tree with course categories and courses for the frontpage
 2032      *
 2033      * @return string
 2034      */
 2035     public function frontpage_combo_list() {
 2036         global $CFG;
 2037         $chelper = new coursecat_helper();
 2038         $chelper->set_subcat_depth($CFG->maxcategorydepth)->
 2039             set_categories_display_options(array(
 2040                 'limit' => $CFG->coursesperpage,
 2041                 'viewmoreurl' => new moodle_url('/course/index.php',
 2042                         array('browse' => 'categories', 'page' => 1))
 2043             ))->
 2044             set_courses_display_options(array(
 2045                 'limit' => $CFG->coursesperpage,
 2046                 'viewmoreurl' => new moodle_url('/course/index.php',
 2047                         array('browse' => 'courses', 'page' => 1))
 2048             ))->
 2049             set_attributes(array('class' => 'frontpage-category-combo'));
 2050         return $this->coursecat_tree($chelper, core_course_category::get(0));
 2051     }
 2052 
 2053     /**
 2054      * Returns HTML to print tree of course categories (with number of courses) for the frontpage
 2055      *
 2056      * @return string
 2057      */
 2058     public function frontpage_categories_list() {
 2059         global $CFG;
 2060         $chelper = new coursecat_helper();
 2061         $chelper->set_subcat_depth($CFG->maxcategorydepth)->
 2062                 set_show_courses(self::COURSECAT_SHOW_COURSES_COUNT)->
 2063                 set_categories_display_options(array(
 2064                     'limit' => $CFG->coursesperpage,
 2065                     'viewmoreurl' => new moodle_url('/course/index.php',
 2066                             array('browse' => 'categories', 'page' => 1))
 2067                 ))->
 2068                 set_attributes(array('class' => 'frontpage-category-names'));
 2069         return $this->coursecat_tree($chelper, core_course_category::get(0));
 2070     }
 2071 
 2072     /**
 2073      * Renders the activity navigation.
 2074      *
 2075      * Defer to template.
 2076      *
 2077      * @param \core_course\output\activity_navigation $page
 2078      * @return string html for the page
 2079      */
 2080     public function render_activity_navigation(\core_course\output\activity_navigation $page) {
 2081         $data = $page->export_for_template($this->output);
 2082         return $this->output->render_from_template('core_course/activity_navigation', $data);
 2083     }
 2084 
 2085     /**
 2086      * Display waiting information about backup size during uploading backup process
 2087      * @param object $backupfile the backup stored_file
 2088      * @return $html string
 2089      */
 2090     public function sendingbackupinfo($backupfile) {
 2091         $sizeinfo = new stdClass();
 2092         $sizeinfo->total = number_format($backupfile->get_filesize() / 1000000, 2);
 2093         $html = html_writer::tag('div', get_string('sendingsize', 'hub', $sizeinfo),
 2094             array('class' => 'courseuploadtextinfo'));
 2095         return $html;
 2096     }
 2097 
 2098     /**
 2099      * Hub information (logo - name - description - link)
 2100      * @param object $hubinfo
 2101      * @return string html code
 2102      */
 2103     public function hubinfo($hubinfo) {
 2104         $screenshothtml = html_writer::empty_tag('img',
 2105             array('src' => $hubinfo['imgurl'], 'alt' => $hubinfo['name']));
 2106         $hubdescription = html_writer::tag('div', $screenshothtml,
 2107             array('class' => 'hubscreenshot'));
 2108 
 2109         $hubdescription .= html_writer::tag('a', $hubinfo['name'],
 2110             array('class' => 'hublink', 'href' => $hubinfo['url'],
 2111                 'onclick' => 'this.target="_blank"'));
 2112 
 2113         $hubdescription .= html_writer::tag('div', format_text($hubinfo['description'], FORMAT_PLAIN),
 2114             array('class' => 'hubdescription'));
 2115         $hubdescription = html_writer::tag('div', $hubdescription, array('class' => 'hubinfo clearfix'));
 2116 
 2117         return $hubdescription;
 2118     }
 2119 
 2120     /**
 2121      * Output frontpage summary text and frontpage modules (stored as section 1 in site course)
 2122      *
 2123      * This may be disabled in settings
 2124      *
 2125      * @return string
 2126      */
 2127     public function frontpage_section1() {
 2128         global $SITE, $USER;
 2129 
 2130         $output = '';
 2131         $editing = $this->page->user_is_editing();
 2132 
 2133         if ($editing) {
 2134             // Make sure section with number 1 exists.
 2135             course_create_sections_if_missing($SITE, 1);
 2136         }
 2137 
 2138         $modinfo = get_fast_modinfo($SITE);
 2139         $section = $modinfo->get_section_info(1);
 2140         if (($section && (!empty($modinfo->sections[1]) or !empty($section->summary))) or $editing) {
 2141             $output .= $this->box_start('generalbox sitetopic');
 2142 
 2143             // If currently moving a file then show the current clipboard.
 2144             if (ismoving($SITE->id)) {
 2145                 $stractivityclipboard = strip_tags(get_string('activityclipboard', '', $USER->activitycopyname));
 2146                 $output .= '<p><font size="2">';
 2147                 $cancelcopyurl = new moodle_url('/course/mod.php', ['cancelcopy' => 'true', 'sesskey' => sesskey()]);
 2148                 $output .= "$stractivityclipboard&nbsp;&nbsp;(" . html_writer::link($cancelcopyurl, get_string('cancel')) .')';
 2149                 $output .= '</font></p>';
 2150             }
 2151 
 2152             $context = context_course::instance(SITEID);
 2153 
 2154             // If the section name is set we show it.
 2155             if (trim($section->name) !== '') {
 2156                 $output .= $this->heading(
 2157                     format_string($section->name, true, array('context' => $context)),
 2158                     2,
 2159                     'sectionname'
 2160                 );
 2161             }
 2162 
 2163             $summarytext = file_rewrite_pluginfile_urls($section->summary,
 2164                 'pluginfile.php',
 2165                 $context->id,
 2166                 'course',
 2167                 'section',
 2168                 $section->id);
 2169             $summaryformatoptions = new stdClass();
 2170             $summaryformatoptions->noclean = true;
 2171             $summaryformatoptions->overflowdiv = true;
 2172 
 2173             $output .= format_text($summarytext, $section->summaryformat, $summaryformatoptions);
 2174 
 2175             if ($editing && has_capability('moodle/course:update', $context)) {
 2176                 $streditsummary = get_string('editsummary');
 2177                 $editsectionurl = new moodle_url('/course/editsection.php', ['id' => $section->id]);
 2178                 $output .= html_writer::link($editsectionurl, $this->pix_icon('t/edit', $streditsummary)) .
 2179                     "<br /><br />";
 2180             }
 2181 
 2182             $output .= $this->course_section_cm_list($SITE, $section);
 2183 
 2184             $output .= $this->course_section_add_cm_control($SITE, $section->section);
 2185             $output .= $this->box_end();
 2186         }
 2187 
 2188         return $output;
 2189     }
 2190 
 2191     /**
 2192      * Output news for the frontpage (extract from site-wide news forum)
 2193      *
 2194      * @param stdClass $newsforum record from db table 'forum' that represents the site news forum
 2195      * @return string
 2196      */
 2197     protected function frontpage_news($newsforum) {
 2198         global $CFG, $SITE, $SESSION, $USER;
 2199         require_once($CFG->dirroot .'/mod/forum/lib.php');
 2200 
 2201         $output = '';
 2202 
 2203         if (isloggedin()) {
 2204             $SESSION->fromdiscussion = $CFG->wwwroot;
 2205             $subtext = '';
 2206             if (\mod_forum\subscriptions::is_subscribed($USER->id, $newsforum)) {
 2207                 if (!\mod_forum\subscriptions::is_forcesubscribed($newsforum)) {
 2208                     $subtext = get_string('unsubscribe', 'forum');
 2209                 }
 2210             } else {
 2211                 $subtext = get_string('subscribe', 'forum');
 2212             }
 2213             $suburl = new moodle_url('/mod/forum/subscribe.php', array('id' => $newsforum->id, 'sesskey' => sesskey()));
 2214             $output .= html_writer::tag('div', html_writer::link($suburl, $subtext), array('class' => 'subscribelink'));
 2215         }
 2216 
 2217         ob_start();
 2218         forum_print_latest_discussions($SITE, $newsforum, $SITE->newsitems, 'plain', 'p.modified DESC');
 2219         $output .= ob_get_contents();
 2220         ob_end_clean();
 2221 
 2222         return $output;
 2223     }
 2224 
 2225     /**
 2226      * Renders part of frontpage with a skip link (i.e. "My courses", "Site news", etc.)
 2227      *
 2228      * @param string $skipdivid
 2229      * @param string $contentsdivid
 2230      * @param string $header Header of the part
 2231      * @param string $contents Contents of the part
 2232      * @return string
 2233      */
 2234     protected function frontpage_part($skipdivid, $contentsdivid, $header, $contents) {
 2235         $output = html_writer::link('#' . $skipdivid,
 2236             get_string('skipa', 'access', core_text::strtolower(strip_tags($header))),
 2237             array('class' => 'skip-block skip'));
 2238 
 2239         // Wrap frontpage part in div container.
 2240         $output .= html_writer::start_tag('div', array('id' => $contentsdivid));
 2241         $output .= $this->heading($header);
 2242 
 2243         $output .= $contents;
 2244 
 2245         // End frontpage part div container.
 2246         $output .= html_writer::end_tag('div');
 2247 
 2248         $output .= html_writer::tag('span', '', array('class' => 'skip-block-to', 'id' => $skipdivid));
 2249         return $output;
 2250     }
 2251 
 2252     /**
 2253      * Outputs contents for frontpage as configured in $CFG->frontpage or $CFG->frontpageloggedin
 2254      *
 2255      * @return string
 2256      */
 2257     public function frontpage() {
 2258         global $CFG, $SITE;
 2259 
 2260         $output = '';
 2261 
 2262         if (isloggedin() and !isguestuser() and isset($CFG->frontpageloggedin)) {
 2263             $frontpagelayout = $CFG->frontpageloggedin;
 2264         } else {
 2265             $frontpagelayout = $CFG->frontpage;
 2266         }
 2267 
 2268         $frontpageoptions = explode(',', $frontpagelayout);
 2269         foreach ($frontpageoptions as $v) {
 2270             switch ($v) {
 2271                 // Display the main part of the front page.
 2272                 case FRONTPAGENEWS:
 2273                     if ($SITE->newsitems) {
 2274                         // Print forums only when needed.
 2275                         require_once($CFG->dirroot .'/mod/forum/lib.php');
 2276                         if (($newsforum = forum_get_course_forum($SITE->id, 'news')) &&
 2277                                 ($forumcontents = $this->frontpage_news($newsforum))) {
 2278                             $newsforumcm = get_fast_modinfo($SITE)->instances['forum'][$newsforum->id];
 2279                             $output .= $this->frontpage_part('skipsitenews', 'site-news-forum',
 2280                                 $newsforumcm->get_formatted_name(), $forumcontents);
 2281                         }
 2282                     }
 2283                     break;
 2284 
 2285                 case FRONTPAGEENROLLEDCOURSELIST:
 2286                     $mycourseshtml = $this->frontpage_my_courses();
 2287                     if (!empty($mycourseshtml)) {
 2288                         $output .= $this->frontpage_part('skipmycourses', 'frontpage-course-list',
 2289                             get_string('mycourses'), $mycourseshtml);
 2290                         break;
 2291                     } else {
 2292                         // Temp fix/fallback in order to display available courses when enrolled courses should be shown,
 2293                         // but user is not enrolled in any course.
 2294                         if (array_search(FRONTPAGEALLCOURSELIST, $frontpageoptions)) {
 2295                             break;
 2296                         }
 2297                     }
 2298                     // No "break" here. If there are no enrolled courses - continue to 'Available courses'.
 2299 
 2300                 case FRONTPAGEALLCOURSELIST:
 2301                     $availablecourseshtml = $this->frontpage_available_courses();
 2302                     if (!empty($availablecourseshtml)) {
 2303                         $output .= $this->frontpage_part('skipavailablecourses', 'frontpage-available-course-list',
 2304                             get_string('availablecourses'), $availablecourseshtml);
 2305                     }
 2306                     break;
 2307 
 2308                 case FRONTPAGECATEGORYNAMES:
 2309                     $output .= $this->frontpage_part('skipcategories', 'frontpage-category-names',
 2310                         get_string('categories'), $this->frontpage_categories_list());
 2311                     break;
 2312 
 2313                 case FRONTPAGECATEGORYCOMBO:
 2314                     $output .= $this->frontpage_part('skipcourses', 'frontpage-category-combo',
 2315                         get_string('courses'), $this->frontpage_combo_list());
 2316                     break;
 2317 
 2318                 case FRONTPAGECOURSESEARCH:
 2319                     $output .= $this->box($this->course_search_form('', 'short'), 'mdl-align');
 2320                     break;
 2321 
 2322             }
 2323             $output .= '<br />';
 2324         }
 2325         return $output;
 2326     }
 2327 }
 2328 
 2329 /**
 2330  * Class storing display options and functions to help display course category and/or courses lists
 2331  *
 2332  * This is a wrapper for core_course_category objects that also stores display options
 2333  * and functions to retrieve sorted and paginated lists of categories/courses.
 2334  *
 2335  * If theme overrides methods in core_course_renderers that access this class
 2336  * it may as well not use this class at all or extend it.
 2337  *
 2338  * @package   core
 2339  * @copyright 2013 Marina Glancy
 2340  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 2341  */
 2342 class coursecat_helper {
 2343     /** @var string [none, collapsed, expanded] how (if) display courses list */
 2344     protected $showcourses = 10; /* core_course_renderer::COURSECAT_SHOW_COURSES_COLLAPSED */
 2345     /** @var int depth to expand subcategories in the tree (deeper subcategories will be loaded by AJAX or proceed to category page by clicking on category name) */
 2346     protected $subcatdepth = 1;
 2347     /** @var array options to display courses list */
 2348     protected $coursesdisplayoptions = array();
 2349     /** @var array options to display subcategories list */
 2350     protected $categoriesdisplayoptions = array();
 2351     /** @var array additional HTML attributes */
 2352     protected $attributes = array();
 2353     /** @var array search criteria if the list is a search result */
 2354     protected $searchcriteria = null;
 2355 
 2356     /**
 2357      * Sets how (if) to show the courses - none, collapsed, expanded, etc.
 2358      *
 2359      * @param int $showcourses SHOW_COURSES_NONE, SHOW_COURSES_COLLAPSED, SHOW_COURSES_EXPANDED, etc.
 2360      * @return coursecat_helper
 2361      */
 2362     public function set_show_courses($showcourses) {
 2363         $this->showcourses = $showcourses;
 2364         // Automatically set the options to preload summary and coursecontacts for core_course_category::get_courses()
 2365         // and core_course_category::search_courses().
 2366         $this->coursesdisplayoptions['summary'] = $showcourses >= core_course_renderer::COURSECAT_SHOW_COURSES_AUTO;
 2367         $this->coursesdisplayoptions['coursecontacts'] = $showcourses >= core_course_renderer::COURSECAT_SHOW_COURSES_EXPANDED;
 2368         return $this;
 2369     }
 2370 
 2371     /**
 2372      * Returns how (if) to show the courses - none, collapsed, expanded, etc.
 2373      *
 2374      * @return int - COURSECAT_SHOW_COURSES_NONE, COURSECAT_SHOW_COURSES_COLLAPSED, COURSECAT_SHOW_COURSES_EXPANDED, etc.
 2375      */
 2376     public function get_show_courses() {
 2377         return $this->showcourses;
 2378     }
 2379 
 2380     /**
 2381      * Sets the maximum depth to expand subcategories in the tree
 2382      *
 2383      * deeper subcategories may be loaded by AJAX or proceed to category page by clicking on category name
 2384      *
 2385      * @param int $subcatdepth
 2386      * @return coursecat_helper
 2387      */
 2388     public function set_subcat_depth($subcatdepth) {
 2389         $this->subcatdepth = $subcatdepth;
 2390         return $this;
 2391     }
 2392 
 2393     /**
 2394      * Returns the maximum depth to expand subcategories in the tree
 2395      *
 2396      * deeper subcategories may be loaded by AJAX or proceed to category page by clicking on category name
 2397      *
 2398      * @return int
 2399      */
 2400     public function get_subcat_depth() {
 2401         return $this->subcatdepth;
 2402     }
 2403 
 2404     /**
 2405      * Sets options to display list of courses
 2406      *
 2407      * Options are later submitted as argument to core_course_category::get_courses() and/or core_course_category::search_courses()
 2408      *
 2409      * Options that core_course_category::get_courses() accept:
 2410      *    - recursive - return courses from subcategories as well. Use with care,
 2411      *      this may be a huge list!
 2412      *    - summary - preloads fields 'summary' and 'summaryformat'
 2413      *    - coursecontacts - preloads course contacts
 2414      *    - isenrolled - preloads indication whether this user is enrolled in the course
 2415      *    - sort - list of fields to sort. Example
 2416      *             array('idnumber' => 1, 'shortname' => 1, 'id' => -1)
 2417      *             will sort by idnumber asc, shortname asc and id desc.
 2418      *             Default: array('sortorder' => 1)
 2419      *             Only cached fields may be used for sorting!
 2420      *    - offset
 2421      *    - limit - maximum number of children to return, 0 or null for no limit
 2422      *
 2423      * Options summary and coursecontacts are filled automatically in the set_show_courses()
 2424      *
 2425      * Also renderer can set here any additional options it wants to pass between renderer functions.
 2426      *
 2427      * @param array $options
 2428      * @return coursecat_helper
 2429      */
 2430     public function set_courses_display_options($options) {
 2431         $this->coursesdisplayoptions = $options;
 2432         $this->set_show_courses($this->showcourses); // this will calculate special display options
 2433         return $this;
 2434     }
 2435 
 2436     /**
 2437      * Sets one option to display list of courses
 2438      *
 2439      * @see coursecat_helper::set_courses_display_options()
 2440      *
 2441      * @param string $key
 2442      * @param mixed $value
 2443      * @return coursecat_helper
 2444      */
 2445     public function set_courses_display_option($key, $value) {
 2446         $this->coursesdisplayoptions[$key] = $value;
 2447         return $this;
 2448     }
 2449 
 2450     /**
 2451      * Return the specified option to display list of courses
 2452      *
 2453      * @param string $optionname option name
 2454      * @param mixed $defaultvalue default value for option if it is not specified
 2455      * @return mixed
 2456      */
 2457     public function get_courses_display_option($optionname, $defaultvalue = null) {
 2458         if (array_key_exists($optionname, $this->coursesdisplayoptions)) {
 2459             return $this->coursesdisplayoptions[$optionname];
 2460         } else {
 2461             return $defaultvalue;
 2462         }
 2463     }
 2464 
 2465     /**
 2466      * Returns all options to display the courses
 2467      *
 2468      * This array is usually passed to {@link core_course_category::get_courses()} or
 2469      * {@link core_course_category::search_courses()}
 2470      *
 2471      * @return array
 2472      */
 2473     public function get_courses_display_options() {
 2474         return $this->coursesdisplayoptions;
 2475     }
 2476 
 2477     /**
 2478      * Sets options to display list of subcategories
 2479      *
 2480      * Options 'sort', 'offset' and 'limit' are passed to core_course_category::get_children().
 2481      * Any other options may be used by renderer functions
 2482      *
 2483      * @param array $options
 2484      * @return coursecat_helper
 2485      */
 2486     public function set_categories_display_options($options) {
 2487         $this->categoriesdisplayoptions = $options;
 2488         return $this;
 2489     }
 2490 
 2491     /**
 2492      * Return the specified option to display list of subcategories
 2493      *
 2494      * @param string $optionname option name
 2495      * @param mixed $defaultvalue default value for option if it is not specified
 2496      * @return mixed
 2497      */
 2498     public function get_categories_display_option($optionname, $defaultvalue = null) {
 2499         if (array_key_exists($optionname, $this->categoriesdisplayoptions)) {
 2500             return $this->categoriesdisplayoptions[$optionname];
 2501         } else {
 2502             return $defaultvalue;
 2503         }
 2504     }
 2505 
 2506     /**
 2507      * Returns all options to display list of subcategories
 2508      *
 2509      * This array is usually passed to {@link core_course_category::get_children()}
 2510      *
 2511      * @return array
 2512      */
 2513     public function get_categories_display_options() {
 2514         return $this->categoriesdisplayoptions;
 2515     }
 2516 
 2517     /**
 2518      * Sets additional general options to pass between renderer functions, usually HTML attributes
 2519      *
 2520      * @param array $attributes
 2521      * @return coursecat_helper
 2522      */
 2523     public function set_attributes($attributes) {
 2524         $this->attributes = $attributes;
 2525         return $this;
 2526     }
 2527 
 2528     /**
 2529      * Return all attributes and erases them so they are not applied again
 2530      *
 2531      * @param string $classname adds additional class name to the beginning of $attributes['class']
 2532      * @return array
 2533      */
 2534     public function get_and_erase_attributes($classname) {
 2535         $attributes = $this->attributes;
 2536         $this->attributes = array();
 2537         if (empty($attributes['class'])) {
 2538             $attributes['class'] = '';
 2539         }
 2540         $attributes['class'] = $classname . ' '. $attributes['class'];
 2541         return $attributes;
 2542     }
 2543 
 2544     /**
 2545      * Sets the search criteria if the course is a search result
 2546      *
 2547      * Search string will be used to highlight terms in course name and description
 2548      *
 2549      * @param array $searchcriteria
 2550      * @return coursecat_helper
 2551      */
 2552     public function set_search_criteria($searchcriteria) {
 2553         $this->searchcriteria = $searchcriteria;
 2554         return $this;
 2555     }
 2556 
 2557     /**
 2558      * Returns formatted and filtered description of the given category
 2559      *
 2560      * @param core_course_category $coursecat category
 2561      * @param stdClass|array $options format options, by default [noclean,overflowdiv],
 2562      *     if context is not specified it will be added automatically
 2563      * @return string|null
 2564      */
 2565     public function get_category_formatted_description($coursecat, $options = null) {
 2566         if ($coursecat->id && !empty($coursecat->description)) {
 2567             if (!isset($coursecat->descriptionformat)) {
 2568                 $descriptionformat = FORMAT_MOODLE;
 2569             } else {
 2570                 $descriptionformat = $coursecat->descriptionformat;
 2571             }
 2572             if ($options === null) {
 2573                 $options = array('noclean' => true, 'overflowdiv' => true);
 2574             } else {
 2575                 $options = (array)$options;
 2576             }
 2577             $context = context_coursecat::instance($coursecat->id);
 2578             if (!isset($options['context'])) {
 2579                 $options['context'] = $context;
 2580             }
 2581             $text = file_rewrite_pluginfile_urls($coursecat->description,
 2582                     'pluginfile.php', $context->id, 'coursecat', 'description', null);
 2583             return format_text($text, $descriptionformat, $options);
 2584         }
 2585         return null;
 2586     }
 2587 
 2588     /**
 2589      * Returns given course's summary with proper embedded files urls and formatted
 2590      *
 2591      * @param core_course_list_element $course
 2592      * @param array|stdClass $options additional formatting options
 2593      * @return string
 2594      */
 2595     public function get_course_formatted_summary($course, $options = array()) {
 2596         global $CFG;
 2597         require_once($CFG->libdir. '/filelib.php');
 2598         if (!$course->has_summary()) {
 2599             return '';
 2600         }
 2601         $options = (array)$options;
 2602         $context = context_course::instance($course->id);
 2603         if (!isset($options['context'])) {
 2604             // TODO see MDL-38521
 2605             // option 1 (current), page context - no code required
 2606             // option 2, system context
 2607             // $options['context'] = context_system::instance();
 2608             // option 3, course context:
 2609             // $options['context'] = $context;
 2610             // option 4, course category context:
 2611             // $options['context'] = $context->get_parent_context();
 2612         }
 2613         $summary = file_rewrite_pluginfile_urls($course->summary, 'pluginfile.php', $context->id, 'course', 'summary', null);
 2614         $summary = format_text($summary, $course->summaryformat, $options, $course->id);
 2615         if (!empty($this->searchcriteria['search'])) {
 2616             $summary = highlight($this->searchcriteria['search'], $summary);
 2617         }
 2618         return $summary;
 2619     }
 2620 
 2621     /**
 2622      * Returns course name as it is configured to appear in courses lists formatted to course context
 2623      *
 2624      * @param core_course_list_element $course
 2625      * @param array|stdClass $options additional formatting options
 2626      * @return string
 2627      */
 2628     public function get_course_formatted_name($course, $options = array()) {
 2629         $options = (array)$options;
 2630         if (!isset($options['context'])) {
 2631             $options['context'] = context_course::instance($course->id);
 2632         }
 2633         $name = format_string(get_course_display_name_for_list($course), true, $options);
 2634         if (!empty($this->searchcriteria['search'])) {
 2635             $name = highlight($this->searchcriteria['search'], $name);
 2636         }
 2637         return $name;
 2638     }
 2639 }