Search moodle.org's
Developer Documentation

See Release Notes

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

Differences Between: [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Cohort related management functions, this file needs to be included manually.
  19   *
  20   * @package    core_cohort
  21   * @copyright  2010 Petr Skoda  {@link http://skodak.org}
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  define('COHORT_ALL', 0);
  28  define('COHORT_COUNT_MEMBERS', 1);
  29  define('COHORT_COUNT_ENROLLED_MEMBERS', 3);
  30  define('COHORT_WITH_MEMBERS_ONLY', 5);
  31  define('COHORT_WITH_ENROLLED_MEMBERS_ONLY', 17);
  32  define('COHORT_WITH_NOTENROLLED_MEMBERS_ONLY', 23);
  33  
  34  /**
  35   * Add new cohort.
  36   *
  37   * @param  stdClass $cohort
  38   * @return int new cohort id
  39   */
  40  function cohort_add_cohort($cohort) {
  41      global $DB, $CFG;
  42  
  43      if (!isset($cohort->name)) {
  44          throw new coding_exception('Missing cohort name in cohort_add_cohort().');
  45      }
  46      if (!isset($cohort->idnumber)) {
  47          $cohort->idnumber = NULL;
  48      }
  49      if (!isset($cohort->description)) {
  50          $cohort->description = '';
  51      }
  52      if (!isset($cohort->descriptionformat)) {
  53          $cohort->descriptionformat = FORMAT_HTML;
  54      }
  55      if (!isset($cohort->visible)) {
  56          $cohort->visible = 1;
  57      }
  58      if (empty($cohort->component)) {
  59          $cohort->component = '';
  60      }
  61      if (empty($CFG->allowcohortthemes) && isset($cohort->theme)) {
  62          unset($cohort->theme);
  63      }
  64      if (empty($cohort->theme) || empty($CFG->allowcohortthemes)) {
  65          $cohort->theme = '';
  66      }
  67      if (!isset($cohort->timecreated)) {
  68          $cohort->timecreated = time();
  69      }
  70      if (!isset($cohort->timemodified)) {
  71          $cohort->timemodified = $cohort->timecreated;
  72      }
  73  
  74      $cohort->id = $DB->insert_record('cohort', $cohort);
  75  
  76      $event = \core\event\cohort_created::create(array(
  77          'context' => context::instance_by_id($cohort->contextid),
  78          'objectid' => $cohort->id,
  79      ));
  80      $event->add_record_snapshot('cohort', $cohort);
  81      $event->trigger();
  82  
  83      return $cohort->id;
  84  }
  85  
  86  /**
  87   * Update existing cohort.
  88   * @param  stdClass $cohort
  89   * @return void
  90   */
  91  function cohort_update_cohort($cohort) {
  92      global $DB, $CFG;
  93      if (property_exists($cohort, 'component') and empty($cohort->component)) {
  94          // prevent NULLs
  95          $cohort->component = '';
  96      }
  97      // Only unset the cohort theme if allowcohortthemes is enabled to prevent the value from being overwritten.
  98      if (empty($CFG->allowcohortthemes) && isset($cohort->theme)) {
  99          unset($cohort->theme);
 100      }
 101      $cohort->timemodified = time();
 102      $DB->update_record('cohort', $cohort);
 103  
 104      $event = \core\event\cohort_updated::create(array(
 105          'context' => context::instance_by_id($cohort->contextid),
 106          'objectid' => $cohort->id,
 107      ));
 108      $event->trigger();
 109  }
 110  
 111  /**
 112   * Delete cohort.
 113   * @param  stdClass $cohort
 114   * @return void
 115   */
 116  function cohort_delete_cohort($cohort) {
 117      global $DB;
 118  
 119      if ($cohort->component) {
 120          // TODO: add component delete callback
 121      }
 122  
 123      $DB->delete_records('cohort_members', array('cohortid'=>$cohort->id));
 124      $DB->delete_records('cohort', array('id'=>$cohort->id));
 125  
 126      // Notify the competency subsystem.
 127      \core_competency\api::hook_cohort_deleted($cohort);
 128  
 129      $event = \core\event\cohort_deleted::create(array(
 130          'context' => context::instance_by_id($cohort->contextid),
 131          'objectid' => $cohort->id,
 132      ));
 133      $event->add_record_snapshot('cohort', $cohort);
 134      $event->trigger();
 135  }
 136  
 137  /**
 138   * Somehow deal with cohorts when deleting course category,
 139   * we can not just delete them because they might be used in enrol
 140   * plugins or referenced in external systems.
 141   * @param  stdClass|core_course_category $category
 142   * @return void
 143   */
 144  function cohort_delete_category($category) {
 145      global $DB;
 146      // TODO: make sure that cohorts are really, really not used anywhere and delete, for now just move to parent or system context
 147  
 148      $oldcontext = context_coursecat::instance($category->id);
 149  
 150      if ($category->parent and $parent = $DB->get_record('course_categories', array('id'=>$category->parent))) {
 151          $parentcontext = context_coursecat::instance($parent->id);
 152          $sql = "UPDATE {cohort} SET contextid = :newcontext WHERE contextid = :oldcontext";
 153          $params = array('oldcontext'=>$oldcontext->id, 'newcontext'=>$parentcontext->id);
 154      } else {
 155          $syscontext = context_system::instance();
 156          $sql = "UPDATE {cohort} SET contextid = :newcontext WHERE contextid = :oldcontext";
 157          $params = array('oldcontext'=>$oldcontext->id, 'newcontext'=>$syscontext->id);
 158      }
 159  
 160      $DB->execute($sql, $params);
 161  }
 162  
 163  /**
 164   * Add cohort member
 165   * @param  int $cohortid
 166   * @param  int $userid
 167   * @return void
 168   */
 169  function cohort_add_member($cohortid, $userid) {
 170      global $DB;
 171      if ($DB->record_exists('cohort_members', array('cohortid'=>$cohortid, 'userid'=>$userid))) {
 172          // No duplicates!
 173          return;
 174      }
 175      $record = new stdClass();
 176      $record->cohortid  = $cohortid;
 177      $record->userid    = $userid;
 178      $record->timeadded = time();
 179      $DB->insert_record('cohort_members', $record);
 180  
 181      $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
 182  
 183      $event = \core\event\cohort_member_added::create(array(
 184          'context' => context::instance_by_id($cohort->contextid),
 185          'objectid' => $cohortid,
 186          'relateduserid' => $userid,
 187      ));
 188      $event->add_record_snapshot('cohort', $cohort);
 189      $event->trigger();
 190  }
 191  
 192  /**
 193   * Remove cohort member
 194   * @param  int $cohortid
 195   * @param  int $userid
 196   * @return void
 197   */
 198  function cohort_remove_member($cohortid, $userid) {
 199      global $DB;
 200      $DB->delete_records('cohort_members', array('cohortid'=>$cohortid, 'userid'=>$userid));
 201  
 202      $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
 203  
 204      $event = \core\event\cohort_member_removed::create(array(
 205          'context' => context::instance_by_id($cohort->contextid),
 206          'objectid' => $cohortid,
 207          'relateduserid' => $userid,
 208      ));
 209      $event->add_record_snapshot('cohort', $cohort);
 210      $event->trigger();
 211  }
 212  
 213  /**
 214   * Is this user a cohort member?
 215   * @param int $cohortid
 216   * @param int $userid
 217   * @return bool
 218   */
 219  function cohort_is_member($cohortid, $userid) {
 220      global $DB;
 221  
 222      return $DB->record_exists('cohort_members', array('cohortid'=>$cohortid, 'userid'=>$userid));
 223  }
 224  
 225  /**
 226   * Returns the list of cohorts visible to the current user in the given course.
 227   *
 228   * The following fields are returned in each record: id, name, contextid, idnumber, visible
 229   * Fields memberscnt and enrolledcnt will be also returned if requested
 230   *
 231   * @param context $currentcontext
 232   * @param int $withmembers one of the COHORT_XXX constants that allows to return non empty cohorts only
 233   *      or cohorts with enroled/not enroled users, or just return members count
 234   * @param int $offset
 235   * @param int $limit
 236   * @param string $search
 237   * @return array
 238   */
 239  function cohort_get_available_cohorts($currentcontext, $withmembers = 0, $offset = 0, $limit = 25, $search = '') {
 240      global $DB;
 241  
 242      $params = array();
 243  
 244      // Build context subquery. Find the list of parent context where user is able to see any or visible-only cohorts.
 245      // Since this method is normally called for the current course all parent contexts are already preloaded.
 246      $contextsany = array_filter($currentcontext->get_parent_context_ids(),
 247          function($a) {
 248              return has_capability("moodle/cohort:view", context::instance_by_id($a));
 249          });
 250      $contextsvisible = array_diff($currentcontext->get_parent_context_ids(), $contextsany);
 251      if (empty($contextsany) && empty($contextsvisible)) {
 252          // User does not have any permissions to view cohorts.
 253          return array();
 254      }
 255      $subqueries = array();
 256      if (!empty($contextsany)) {
 257          list($parentsql, $params1) = $DB->get_in_or_equal($contextsany, SQL_PARAMS_NAMED, 'ctxa');
 258          $subqueries[] = 'c.contextid ' . $parentsql;
 259          $params = array_merge($params, $params1);
 260      }
 261      if (!empty($contextsvisible)) {
 262          list($parentsql, $params1) = $DB->get_in_or_equal($contextsvisible, SQL_PARAMS_NAMED, 'ctxv');
 263          $subqueries[] = '(c.visible = 1 AND c.contextid ' . $parentsql. ')';
 264          $params = array_merge($params, $params1);
 265      }
 266      $wheresql = '(' . implode(' OR ', $subqueries) . ')';
 267  
 268      // Build the rest of the query.
 269      $fromsql = "";
 270      $fieldssql = 'c.id, c.name, c.contextid, c.idnumber, c.visible';
 271      $groupbysql = '';
 272      $havingsql = '';
 273      if ($withmembers) {
 274          $fieldssql .= ', s.memberscnt';
 275          $subfields = "c.id, COUNT(DISTINCT cm.userid) AS memberscnt";
 276          $groupbysql = " GROUP BY c.id";
 277          $fromsql = " LEFT JOIN {cohort_members} cm ON cm.cohortid = c.id ";
 278          if (in_array($withmembers,
 279                  array(COHORT_COUNT_ENROLLED_MEMBERS, COHORT_WITH_ENROLLED_MEMBERS_ONLY, COHORT_WITH_NOTENROLLED_MEMBERS_ONLY))) {
 280              list($esql, $params2) = get_enrolled_sql($currentcontext);
 281              $fromsql .= " LEFT JOIN ($esql) u ON u.id = cm.userid ";
 282              $params = array_merge($params2, $params);
 283              $fieldssql .= ', s.enrolledcnt';
 284              $subfields .= ', COUNT(DISTINCT u.id) AS enrolledcnt';
 285          }
 286          if ($withmembers == COHORT_WITH_MEMBERS_ONLY) {
 287              $havingsql = " HAVING COUNT(DISTINCT cm.userid) > 0";
 288          } else if ($withmembers == COHORT_WITH_ENROLLED_MEMBERS_ONLY) {
 289              $havingsql = " HAVING COUNT(DISTINCT u.id) > 0";
 290          } else if ($withmembers == COHORT_WITH_NOTENROLLED_MEMBERS_ONLY) {
 291              $havingsql = " HAVING COUNT(DISTINCT cm.userid) > COUNT(DISTINCT u.id)";
 292          }
 293      }
 294      if ($search) {
 295          list($searchsql, $searchparams) = cohort_get_search_query($search);
 296          $wheresql .= ' AND ' . $searchsql;
 297          $params = array_merge($params, $searchparams);
 298      }
 299  
 300      if ($withmembers) {
 301          $sql = "SELECT " . str_replace('c.', 'cohort.', $fieldssql) . "
 302                    FROM {cohort} cohort
 303                    JOIN (SELECT $subfields
 304                            FROM {cohort} c $fromsql
 305                           WHERE $wheresql $groupbysql $havingsql
 306                          ) s ON cohort.id = s.id
 307                ORDER BY cohort.name, cohort.idnumber";
 308      } else {
 309          $sql = "SELECT $fieldssql
 310                    FROM {cohort} c $fromsql
 311                   WHERE $wheresql
 312                ORDER BY c.name, c.idnumber";
 313      }
 314  
 315      return $DB->get_records_sql($sql, $params, $offset, $limit);
 316  }
 317  
 318  /**
 319   * Check if cohort exists and user is allowed to access it from the given context.
 320   *
 321   * @param stdClass|int $cohortorid cohort object or id
 322   * @param context $currentcontext current context (course) where visibility is checked
 323   * @return boolean
 324   */
 325  function cohort_can_view_cohort($cohortorid, $currentcontext) {
 326      global $DB;
 327      if (is_numeric($cohortorid)) {
 328          $cohort = $DB->get_record('cohort', array('id' => $cohortorid), 'id, contextid, visible');
 329      } else {
 330          $cohort = $cohortorid;
 331      }
 332  
 333      if ($cohort && in_array($cohort->contextid, $currentcontext->get_parent_context_ids())) {
 334          if ($cohort->visible) {
 335              return true;
 336          }
 337          $cohortcontext = context::instance_by_id($cohort->contextid);
 338          if (has_capability('moodle/cohort:view', $cohortcontext)) {
 339              return true;
 340          }
 341      }
 342      return false;
 343  }
 344  
 345  /**
 346   * Get a cohort by id. Also does a visibility check and returns false if the user cannot see this cohort.
 347   *
 348   * @param stdClass|int $cohortorid cohort object or id
 349   * @param context $currentcontext current context (course) where visibility is checked
 350   * @return stdClass|boolean
 351   */
 352  function cohort_get_cohort($cohortorid, $currentcontext) {
 353      global $DB;
 354      if (is_numeric($cohortorid)) {
 355          $cohort = $DB->get_record('cohort', array('id' => $cohortorid), 'id, contextid, visible');
 356      } else {
 357          $cohort = $cohortorid;
 358      }
 359  
 360      if ($cohort && in_array($cohort->contextid, $currentcontext->get_parent_context_ids())) {
 361          if ($cohort->visible) {
 362              return $cohort;
 363          }
 364          $cohortcontext = context::instance_by_id($cohort->contextid);
 365          if (has_capability('moodle/cohort:view', $cohortcontext)) {
 366              return $cohort;
 367          }
 368      }
 369      return false;
 370  }
 371  
 372  /**
 373   * Produces a part of SQL query to filter cohorts by the search string
 374   *
 375   * Called from {@link cohort_get_cohorts()}, {@link cohort_get_all_cohorts()} and {@link cohort_get_available_cohorts()}
 376   *
 377   * @access private
 378   *
 379   * @param string $search search string
 380   * @param string $tablealias alias of cohort table in the SQL query (highly recommended if other tables are used in query)
 381   * @return array of two elements - SQL condition and array of named parameters
 382   */
 383  function cohort_get_search_query($search, $tablealias = '') {
 384      global $DB;
 385      $params = array();
 386      if (empty($search)) {
 387          // This function should not be called if there is no search string, just in case return dummy query.
 388          return array('1=1', $params);
 389      }
 390      if ($tablealias && substr($tablealias, -1) !== '.') {
 391          $tablealias .= '.';
 392      }
 393      $searchparam = '%' . $DB->sql_like_escape($search) . '%';
 394      $conditions = array();
 395      $fields = array('name', 'idnumber', 'description');
 396      $cnt = 0;
 397      foreach ($fields as $field) {
 398          $conditions[] = $DB->sql_like($tablealias . $field, ':csearch' . $cnt, false);
 399          $params['csearch' . $cnt] = $searchparam;
 400          $cnt++;
 401      }
 402      $sql = '(' . implode(' OR ', $conditions) . ')';
 403      return array($sql, $params);
 404  }
 405  
 406  /**
 407   * Get all the cohorts defined in given context.
 408   *
 409   * The function does not check user capability to view/manage cohorts in the given context
 410   * assuming that it has been already verified.
 411   *
 412   * @param int $contextid
 413   * @param int $page number of the current page
 414   * @param int $perpage items per page
 415   * @param string $search search string
 416   * @return array    Array(totalcohorts => int, cohorts => array, allcohorts => int)
 417   */
 418  function cohort_get_cohorts($contextid, $page = 0, $perpage = 25, $search = '') {
 419      global $DB;
 420  
 421      $fields = "SELECT *";
 422      $countfields = "SELECT COUNT(1)";
 423      $sql = " FROM {cohort}
 424               WHERE contextid = :contextid";
 425      $params = array('contextid' => $contextid);
 426      $order = " ORDER BY name ASC, idnumber ASC";
 427  
 428      if (!empty($search)) {
 429          list($searchcondition, $searchparams) = cohort_get_search_query($search);
 430          $sql .= ' AND ' . $searchcondition;
 431          $params = array_merge($params, $searchparams);
 432      }
 433  
 434      $totalcohorts = $allcohorts = $DB->count_records('cohort', array('contextid' => $contextid));
 435      if (!empty($search)) {
 436          $totalcohorts = $DB->count_records_sql($countfields . $sql, $params);
 437      }
 438      $cohorts = $DB->get_records_sql($fields . $sql . $order, $params, $page*$perpage, $perpage);
 439  
 440      return array('totalcohorts' => $totalcohorts, 'cohorts' => $cohorts, 'allcohorts' => $allcohorts);
 441  }
 442  
 443  /**
 444   * Get all the cohorts defined anywhere in system.
 445   *
 446   * The function assumes that user capability to view/manage cohorts on system level
 447   * has already been verified. This function only checks if such capabilities have been
 448   * revoked in child (categories) contexts.
 449   *
 450   * @param int $page number of the current page
 451   * @param int $perpage items per page
 452   * @param string $search search string
 453   * @return array    Array(totalcohorts => int, cohorts => array, allcohorts => int)
 454   */
 455  function cohort_get_all_cohorts($page = 0, $perpage = 25, $search = '') {
 456      global $DB;
 457  
 458      $fields = "SELECT c.*, ".context_helper::get_preload_record_columns_sql('ctx');
 459      $countfields = "SELECT COUNT(*)";
 460      $sql = " FROM {cohort} c
 461               JOIN {context} ctx ON ctx.id = c.contextid ";
 462      $params = array();
 463      $wheresql = '';
 464  
 465      if ($excludedcontexts = cohort_get_invisible_contexts()) {
 466          list($excludedsql, $excludedparams) = $DB->get_in_or_equal($excludedcontexts, SQL_PARAMS_NAMED, 'excl', false);
 467          $wheresql = ' WHERE c.contextid '.$excludedsql;
 468          $params = array_merge($params, $excludedparams);
 469      }
 470  
 471      $totalcohorts = $allcohorts = $DB->count_records_sql($countfields . $sql . $wheresql, $params);
 472  
 473      if (!empty($search)) {
 474          list($searchcondition, $searchparams) = cohort_get_search_query($search, 'c');
 475          $wheresql .= ($wheresql ? ' AND ' : ' WHERE ') . $searchcondition;
 476          $params = array_merge($params, $searchparams);
 477          $totalcohorts = $DB->count_records_sql($countfields . $sql . $wheresql, $params);
 478      }
 479  
 480      $order = " ORDER BY c.name ASC, c.idnumber ASC";
 481      $cohorts = $DB->get_records_sql($fields . $sql . $wheresql . $order, $params, $page*$perpage, $perpage);
 482  
 483      // Preload used contexts, they will be used to check view/manage/assign capabilities and display categories names.
 484      foreach (array_keys($cohorts) as $key) {
 485          context_helper::preload_from_record($cohorts[$key]);
 486      }
 487  
 488      return array('totalcohorts' => $totalcohorts, 'cohorts' => $cohorts, 'allcohorts' => $allcohorts);
 489  }
 490  
 491  /**
 492   * Get all the cohorts where the given user is member of.
 493   *
 494   * @param int $userid
 495   * @return array Array
 496   */
 497  function cohort_get_user_cohorts($userid) {
 498      global $DB;
 499  
 500      $sql = 'SELECT c.*
 501                FROM {cohort} c
 502                JOIN {cohort_members} cm ON c.id = cm.cohortid
 503               WHERE cm.userid = ? AND c.visible = 1';
 504      return $DB->get_records_sql($sql, array($userid));
 505  }
 506  
 507  /**
 508   * Get the user cohort theme.
 509   *
 510   * If the user is member of one cohort, will return this cohort theme (if defined).
 511   * If the user is member of 2 or more cohorts, will return the theme if all them have the same
 512   * theme (null themes are ignored).
 513   *
 514   * @param int $userid
 515   * @return string|null
 516   */
 517  function cohort_get_user_cohort_theme($userid) {
 518      $cohorts = cohort_get_user_cohorts($userid);
 519      $theme = null;
 520      foreach ($cohorts as $cohort) {
 521          if (!empty($cohort->theme)) {
 522              if (null === $theme) {
 523                  $theme = $cohort->theme;
 524              } else if ($theme != $cohort->theme) {
 525                  return null;
 526              }
 527          }
 528      }
 529      return $theme;
 530  }
 531  
 532  /**
 533   * Returns list of contexts where cohorts are present but current user does not have capability to view/manage them.
 534   *
 535   * This function is called from {@link cohort_get_all_cohorts()} to ensure correct pagination in rare cases when user
 536   * is revoked capability in child contexts. It assumes that user's capability to view/manage cohorts on system
 537   * level has already been verified.
 538   *
 539   * @access private
 540   *
 541   * @return array array of context ids
 542   */
 543  function cohort_get_invisible_contexts() {
 544      global $DB;
 545      if (is_siteadmin()) {
 546          // Shortcut, admin can do anything and can not be prohibited from any context.
 547          return array();
 548      }
 549      $records = $DB->get_recordset_sql("SELECT DISTINCT ctx.id, ".context_helper::get_preload_record_columns_sql('ctx')." ".
 550          "FROM {context} ctx JOIN {cohort} c ON ctx.id = c.contextid ");
 551      $excludedcontexts = array();
 552      foreach ($records as $ctx) {
 553          context_helper::preload_from_record($ctx);
 554          if (context::instance_by_id($ctx->id) == context_system::instance()) {
 555              continue; // System context cohorts should be available and permissions already checked.
 556          }
 557          if (!has_any_capability(array('moodle/cohort:manage', 'moodle/cohort:view'), context::instance_by_id($ctx->id))) {
 558              $excludedcontexts[] = $ctx->id;
 559          }
 560      }
 561      $records->close();
 562      return $excludedcontexts;
 563  }
 564  
 565  /**
 566   * Returns navigation controls (tabtree) to be displayed on cohort management pages
 567   *
 568   * @param context $context system or category context where cohorts controls are about to be displayed
 569   * @param moodle_url $currenturl
 570   * @return null|renderable
 571   */
 572  function cohort_edit_controls(context $context, moodle_url $currenturl) {
 573      $tabs = array();
 574      $currenttab = 'view';
 575      $viewurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
 576      if (($searchquery = $currenturl->get_param('search'))) {
 577          $viewurl->param('search', $searchquery);
 578      }
 579      if ($context->contextlevel == CONTEXT_SYSTEM) {
 580          $tabs[] = new tabobject('view', new moodle_url($viewurl, array('showall' => 0)), get_string('systemcohorts', 'cohort'));
 581          $tabs[] = new tabobject('viewall', new moodle_url($viewurl, array('showall' => 1)), get_string('allcohorts', 'cohort'));
 582          if ($currenturl->get_param('showall')) {
 583              $currenttab = 'viewall';
 584          }
 585      } else {
 586          $tabs[] = new tabobject('view', $viewurl, get_string('cohorts', 'cohort'));
 587      }
 588      if (has_capability('moodle/cohort:manage', $context)) {
 589          $addurl = new moodle_url('/cohort/edit.php', array('contextid' => $context->id));
 590          $tabs[] = new tabobject('addcohort', $addurl, get_string('addcohort', 'cohort'));
 591          if ($currenturl->get_path() === $addurl->get_path() && !$currenturl->param('id')) {
 592              $currenttab = 'addcohort';
 593          }
 594  
 595          $uploadurl = new moodle_url('/cohort/upload.php', array('contextid' => $context->id));
 596          $tabs[] = new tabobject('uploadcohorts', $uploadurl, get_string('uploadcohorts', 'cohort'));
 597          if ($currenturl->get_path() === $uploadurl->get_path()) {
 598              $currenttab = 'uploadcohorts';
 599          }
 600      }
 601      if (count($tabs) > 1) {
 602          return new tabtree($tabs, $currenttab);
 603      }
 604      return null;
 605  }
 606  
 607  /**
 608   * Implements callback inplace_editable() allowing to edit values in-place
 609   *
 610   * @param string $itemtype
 611   * @param int $itemid
 612   * @param mixed $newvalue
 613   * @return \core\output\inplace_editable
 614   */
 615  function core_cohort_inplace_editable($itemtype, $itemid, $newvalue) {
 616      if ($itemtype === 'cohortname') {
 617          return \core_cohort\output\cohortname::update($itemid, $newvalue);
 618      } else if ($itemtype === 'cohortidnumber') {
 619          return \core_cohort\output\cohortidnumber::update($itemid, $newvalue);
 620      }
 621  }
 622  
 623  /**
 624   * Returns a list of valid themes which can be displayed in a selector.
 625   *
 626   * @return array as (string)themename => (string)get_string_theme
 627   */
 628  function cohort_get_list_of_themes() {
 629      $themes = array();
 630      $allthemes = get_list_of_themes();
 631      foreach ($allthemes as $key => $theme) {
 632          if (empty($theme->hidefromselector)) {
 633              $themes[$key] = get_string('pluginname', 'theme_'.$theme->name);
 634          }
 635      }
 636      return $themes;
 637  }