/** * Renders the list of subcategories in a category * * @param coursecat_helper $chelper various display options * @param coursecat $coursecat * @param int $depth depth of the category in the current tree * @return string */ protected function coursecat_subcategories(coursecat_helper $chelper, $coursecat, $depth) { global $CFG; $subcategories = array(); if (!$chelper->get_categories_display_option('nodisplay')) { $subcategories = $coursecat->get_children($chelper->get_categories_display_options()); } // IOMAD: Filter out unwanted categories if (!is_siteadmin()) { $subcategories = iomad::iomad_filter_categories($subcategories); } $totalcount = $coursecat->get_children_count(); if (!$totalcount) { // Note that we call get_child_categories_count() AFTER get_child_categories() to avoid extra DB requests. // Categories count is cached during children categories retrieval. return ''; } // prepare content of paging bar or more link if it is needed $paginationurl = $chelper->get_categories_display_option('paginationurl'); $paginationallowall = $chelper->get_categories_display_option('paginationallowall'); if ($totalcount > count($subcategories)) { if ($paginationurl) { // the option 'paginationurl was specified, display pagingbar $perpage = $chelper->get_categories_display_option('limit', $CFG->coursesperpage); $page = $chelper->get_categories_display_option('offset') / $perpage; $pagingbar = $this->paging_bar($totalcount, $page, $perpage, $paginationurl->out(false, array('perpage' => $perpage))); if ($paginationallowall) { $pagingbar .= html_writer::tag('div', html_writer::link($paginationurl->out(false, array('perpage' => 'all')), get_string('showall', '', $totalcount)), array('class' => 'paging paging-showall')); } } else { if ($viewmoreurl = $chelper->get_categories_display_option('viewmoreurl')) { // the option 'viewmoreurl' was specified, display more link (if it is link to category view page, add category id) if ($viewmoreurl->compare(new moodle_url('/course/index.php'), URL_MATCH_BASE)) { $viewmoreurl->param('categoryid', $coursecat->id); } $viewmoretext = $chelper->get_categories_display_option('viewmoretext', new lang_string('viewmore')); $morelink = html_writer::tag('div', html_writer::link($viewmoreurl, $viewmoretext), array('class' => 'paging paging-morelink')); } } } else { if ($totalcount > $CFG->coursesperpage && $paginationurl && $paginationallowall) { // there are more than one page of results and we are in 'view all' mode, suggest to go back to paginated view mode $pagingbar = html_writer::tag('div', html_writer::link($paginationurl->out(false, array('perpage' => $CFG->coursesperpage)), get_string('showperpage', '', $CFG->coursesperpage)), array('class' => 'paging paging-showperpage')); } } // display list of subcategories $content = html_writer::start_tag('div', array('class' => 'subcategories')); if (!empty($pagingbar)) { $content .= $pagingbar; } foreach ($subcategories as $subcategory) { $content .= $this->coursecat_category($chelper, $subcategory, $depth + 1); } if (!empty($pagingbar)) { $content .= $pagingbar; } if (!empty($morelink)) { $content .= $morelink; } $content .= html_writer::end_tag('div'); return $content; }
/** * Loads all categories (top level or if an id is specified for that category) * * @param int $categoryid The category id to load or null/0 to load all base level categories * @param bool $showbasecategories If set to true all base level categories will be loaded as well * as the requested category and any parent categories. * @return navigation_node|void returns a navigation node if a category has been loaded. */ protected function load_all_categories($categoryid = self::LOAD_ROOT_CATEGORIES, $showbasecategories = false) { global $CFG, $DB; // Check if this category has already been loaded if ($this->allcategoriesloaded || $categoryid < 1 && $this->is_category_fully_loaded($categoryid)) { return true; } $catcontextsql = context_helper::get_preload_record_columns_sql('ctx'); $sqlselect = "SELECT cc.*, {$catcontextsql}\n FROM {course_categories} cc\n JOIN {context} ctx ON cc.id = ctx.instanceid"; $sqlwhere = "WHERE ctx.contextlevel = " . CONTEXT_COURSECAT; $sqlorder = "ORDER BY cc.depth ASC, cc.sortorder ASC, cc.id ASC"; $params = array(); $categoriestoload = array(); if ($categoryid == self::LOAD_ALL_CATEGORIES) { // We are going to load all categories regardless... prepare to fire // on the database server! } else { if ($categoryid == self::LOAD_ROOT_CATEGORIES) { // can be 0 // We are going to load all of the first level categories (categories without parents) $sqlwhere .= " AND cc.parent = 0"; } else { if (array_key_exists($categoryid, $this->addedcategories)) { // The category itself has been loaded already so we just need to ensure its subcategories // have been loaded list($sql, $params) = $DB->get_in_or_equal(array_keys($this->addedcategories), SQL_PARAMS_NAMED, 'parent', false); if ($showbasecategories) { // We need to include categories with parent = 0 as well $sqlwhere .= " AND (cc.parent = :categoryid OR cc.parent = 0) AND cc.parent {$sql}"; } else { // All we need is categories that match the parent $sqlwhere .= " AND cc.parent = :categoryid AND cc.parent {$sql}"; } $params['categoryid'] = $categoryid; } else { // This category hasn't been loaded yet so we need to fetch it, work out its category path // and load this category plus all its parents and subcategories $category = $DB->get_record('course_categories', array('id' => $categoryid), 'path', MUST_EXIST); $categoriestoload = explode('/', trim($category->path, '/')); list($select, $params) = $DB->get_in_or_equal($categoriestoload); // We are going to use select twice so double the params $params = array_merge($params, $params); $basecategorysql = $showbasecategories ? ' OR cc.depth = 1' : ''; $sqlwhere .= " AND (cc.id {$select} OR cc.parent {$select}{$basecategorysql})"; } } } $categoriesrs = $DB->get_recordset_sql("{$sqlselect} {$sqlwhere} {$sqlorder}", $params); // IOMAD - Filter out the unwanted categories if (!is_siteadmin()) { $categoriesiomad = iomad::iomad_filter_categories($categoriesrs); } else { $categoriesiomad = $categoriesrs; } $categories = array(); foreach ($categoriesiomad as $category) { // Preload the context.. we'll need it when adding the category in order // to format the category name. context_helper::preload_from_record($category); if (array_key_exists($category->id, $this->addedcategories)) { // Do nothing, its already been added. } else { if ($category->parent == '0') { // This is a root category lets add it immediately $this->add_category($category, $this->rootnodes['courses']); } else { if (array_key_exists($category->parent, $this->addedcategories)) { // This categories parent has already been added we can add this immediately $this->add_category($category, $this->addedcategories[$category->parent]); } else { $categories[] = $category; } } } } $categoriesrs->close(); // Now we have an array of categories we need to add them to the navigation. while (!empty($categories)) { $category = reset($categories); if (array_key_exists($category->id, $this->addedcategories)) { // Do nothing } else { if ($category->parent == '0') { $this->add_category($category, $this->rootnodes['courses']); } else { if (array_key_exists($category->parent, $this->addedcategories)) { $this->add_category($category, $this->addedcategories[$category->parent]); } else { // This category isn't in the navigation and niether is it's parent (yet). // We need to go through the category path and add all of its components in order. $path = explode('/', trim($category->path, '/')); foreach ($path as $catid) { if (!array_key_exists($catid, $this->addedcategories)) { // This category isn't in the navigation yet so add it. $subcategory = $categories[$catid]; if ($subcategory->parent == '0') { // Yay we have a root category - this likely means we will now be able // to add categories without problems. $this->add_category($subcategory, $this->rootnodes['courses']); } else { if (array_key_exists($subcategory->parent, $this->addedcategories)) { // The parent is in the category (as we'd expect) so add it now. $this->add_category($subcategory, $this->addedcategories[$subcategory->parent]); // Remove the category from the categories array. unset($categories[$catid]); } else { // We should never ever arrive here - if we have then there is a bigger // problem at hand. throw new coding_exception('Category path order is incorrect and/or there are missing categories'); } } } } } } } // Remove the category from the categories array now that we know it has been added. unset($categories[$category->id]); } if ($categoryid === self::LOAD_ALL_CATEGORIES) { $this->allcategoriesloaded = true; } // Check if there are any categories to load. if (count($categoriestoload) > 0) { $readytoloadcourses = array(); foreach ($categoriestoload as $category) { if ($this->can_add_more_courses_to_category($category)) { $readytoloadcourses[] = $category; } } if (count($readytoloadcourses)) { $this->load_all_courses($readytoloadcourses); } } // Look for all categories which have been loaded if (!empty($this->addedcategories)) { $categoryids = array(); foreach ($this->addedcategories as $category) { if ($this->can_add_more_courses_to_category($category)) { $categoryids[] = $category->key; } } if ($categoryids) { list($categoriessql, $params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED); $params['limit'] = !empty($CFG->navcourselimit) ? $CFG->navcourselimit : 20; $sql = "SELECT cc.id, COUNT(c.id) AS coursecount\n FROM {course_categories} cc\n JOIN {course} c ON c.category = cc.id\n WHERE cc.id {$categoriessql}\n GROUP BY cc.id\n HAVING COUNT(c.id) > :limit"; $excessivecategories = $DB->get_records_sql($sql, $params); foreach ($categories as &$category) { if (array_key_exists($category->key, $excessivecategories) && !$this->can_add_more_courses_to_category($category)) { $url = new moodle_url('/course/index.php', array('categoryid' => $category->key)); $category->add(get_string('viewallcourses'), $url, self::TYPE_SETTING); } } } } }
/** * This function returns a nice list representing category tree * for display or to use in a form <select> element * * List is cached for 10 minutes * * For example, if you have a tree of categories like: * Miscellaneous (id = 1) * Subcategory (id = 2) * Sub-subcategory (id = 4) * Other category (id = 3) * Then after calling this function you will have * array(1 => 'Miscellaneous', * 2 => 'Miscellaneous / Subcategory', * 4 => 'Miscellaneous / Subcategory / Sub-subcategory', * 3 => 'Other category'); * * If you specify $requiredcapability, then only categories where the current * user has that capability will be added to $list. * If you only have $requiredcapability in a child category, not the parent, * then the child catgegory will still be included. * * If you specify the option $excludeid, then that category, and all its children, * are omitted from the tree. This is useful when you are doing something like * moving categories, where you do not want to allow people to move a category * to be the child of itself. * * See also {@link make_categories_options()} * * @param string/array $requiredcapability if given, only categories where the current * user has this capability will be returned. Can also be an array of capabilities, * in which case they are all required. * @param integer $excludeid Exclude this category and its children from the lists built. * @param string $separator string to use as a separator between parent and child category. Default ' / ' * @return array of strings */ public static function make_categories_list($requiredcapability = '', $excludeid = 0, $separator = ' / ') { global $DB; global $CFG; $coursecatcache = cache::make('core', 'coursecat'); // Check if we cached the complete list of user-accessible category names ($baselist) or list of ids // with requried cap ($thislist). $basecachekey = 'catlist'; $baselist = $coursecatcache->get($basecachekey); $thislist = false; $thiscachekey = null; if (!empty($requiredcapability)) { $requiredcapability = (array) $requiredcapability; $thiscachekey = 'catlist:' . serialize($requiredcapability); if ($baselist !== false && ($thislist = $coursecatcache->get($thiscachekey)) !== false) { $thislist = preg_split('|,|', $thislist, -1, PREG_SPLIT_NO_EMPTY); } } else { if ($baselist !== false) { $thislist = array_keys($baselist); } } if ($baselist === false) { // We don't have $baselist cached, retrieve it. Retrieve $thislist again in any case. $ctxselect = context_helper::get_preload_record_columns_sql('ctx'); $sql = "SELECT cc.id, cc.sortorder, cc.name, cc.visible, cc.parent, cc.path, {$ctxselect}\n FROM {course_categories} cc\n JOIN {context} ctx ON cc.id = ctx.instanceid AND ctx.contextlevel = :contextcoursecat\n ORDER BY cc.sortorder"; $rs = $DB->get_recordset_sql($sql, array('contextcoursecat' => CONTEXT_COURSECAT)); $baselist = array(); $thislist = array(); foreach ($rs as $record) { // If the category's parent is not visible to the user, it is not visible as well. if (!$record->parent || isset($baselist[$record->parent])) { context_helper::preload_from_record($record); $context = context_coursecat::instance($record->id); if (!$record->visible && !has_capability('moodle/category:viewhiddencategories', $context)) { // No cap to view category, added to neither $baselist nor $thislist. continue; } $baselist[$record->id] = array('name' => format_string($record->name, true, array('context' => $context)), 'path' => $record->path); if (!empty($requiredcapability) && !has_all_capabilities($requiredcapability, $context)) { // No required capability, added to $baselist but not to $thislist. continue; } $thislist[] = $record->id; } } $rs->close(); $coursecatcache->set($basecachekey, $baselist); if (!empty($requiredcapability)) { $coursecatcache->set($thiscachekey, join(',', $thislist)); } } else { if ($thislist === false) { // We have $baselist cached but not $thislist. Simplier query is used to retrieve. $ctxselect = context_helper::get_preload_record_columns_sql('ctx'); $sql = "SELECT ctx.instanceid AS id, {$ctxselect}\n FROM {context} ctx WHERE ctx.contextlevel = :contextcoursecat"; $contexts = $DB->get_records_sql($sql, array('contextcoursecat' => CONTEXT_COURSECAT)); $thislist = array(); foreach (array_keys($baselist) as $id) { context_helper::preload_from_record($contexts[$id]); if (has_all_capabilities($requiredcapability, context_coursecat::instance($id))) { $thislist[] = $id; } } $coursecatcache->set($thiscachekey, join(',', $thislist)); } } // Now build the array of strings to return, mind $separator and $excludeid. $names = array(); foreach ($thislist as $id) { $path = preg_split('|/|', $baselist[$id]['path'], -1, PREG_SPLIT_NO_EMPTY); if (!$excludeid || !in_array($excludeid, $path)) { $namechunks = array(); foreach ($path as $parentid) { $namechunks[] = $baselist[$parentid]['name']; } $names[$id] = join($separator, $namechunks); } } // IOMAD : Filter the list of categories. if (!is_siteadmin() and !during_initial_install()) { $names = iomad::iomad_filter_categories($names); } return $names; }