Merge branch 'MDL-38700-master' of git://github.com/danpoltawski/moodle
authorDamyon Wiese <damyon@moodle.com>
Tue, 2 Apr 2013 04:48:08 +0000 (12:48 +0800)
committerDamyon Wiese <damyon@moodle.com>
Tue, 2 Apr 2013 04:48:08 +0000 (12:48 +0800)
Conflicts:
course/tests/courselib_test.php

1  2 
course/lib.php
course/tests/courselib_test.php

diff --combined course/lib.php
@@@ -1219,6 -1219,129 +1219,6 @@@ function get_category_or_system_context
      }
  }
  
 -/**
 - * Gets the child categories of a given courses category. Uses a static cache
 - * to make repeat calls efficient.
 - *
 - * @param int $parentid the id of a course category.
 - * @return array all the child course categories.
 - */
 -function get_child_categories($parentid) {
 -    static $allcategories = null;
 -
 -    // only fill in this variable the first time
 -    if (null == $allcategories) {
 -        $allcategories = array();
 -
 -        $categories = get_categories();
 -        foreach ($categories as $category) {
 -            if (empty($allcategories[$category->parent])) {
 -                $allcategories[$category->parent] = array();
 -            }
 -            $allcategories[$category->parent][] = $category;
 -        }
 -    }
 -
 -    if (empty($allcategories[$parentid])) {
 -        return array();
 -    } else {
 -        return $allcategories[$parentid];
 -    }
 -}
 -
 -/**
 - * This function recursively travels the categories, building up a nice list
 - * for display. It also makes an array that list all the parents for each
 - * category.
 - *
 - * 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
 - * $list = array(1 => 'Miscellaneous', 2 => 'Miscellaneous / Subcategory',
 - *      4 => 'Miscellaneous / Subcategory / Sub-subcategory',
 - *      3 => 'Other category');
 - * $parents = array(2 => array(1), 4 => array(1, 2));
 - *
 - * If you specify $requiredcapability, then only categories where the current
 - * user has that capability will be added to $list, although all categories
 - * will still be added to $parents, and 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 $excluded, 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.
 - *
 - * @param array $list For output, accumulates an array categoryid => full category path name
 - * @param array $parents For output, accumulates an array categoryid => list of parent category ids.
 - * @param string/array $requiredcapability if given, only categories where the current
 - *      user has this capability will be added to $list. Can also be an array of capabilities,
 - *      in which case they are all required.
 - * @param integer $excludeid Omit this category and its children from the lists built.
 - * @param object $category Build the tree starting at this category - otherwise starts at the top level.
 - * @param string $path For internal use, as part of recursive calls.
 - */
 -function make_categories_list(&$list, &$parents, $requiredcapability = '',
 -        $excludeid = 0, $category = NULL, $path = "") {
 -
 -    // initialize the arrays if needed
 -    if (!is_array($list)) {
 -        $list = array();
 -    }
 -    if (!is_array($parents)) {
 -        $parents = array();
 -    }
 -
 -    if (empty($category)) {
 -        // Start at the top level.
 -        $category = new stdClass;
 -        $category->id = 0;
 -    } else {
 -        // This is the excluded category, don't include it.
 -        if ($excludeid > 0 && $excludeid == $category->id) {
 -            return;
 -        }
 -
 -        $context = context_coursecat::instance($category->id);
 -        $categoryname = format_string($category->name, true, array('context' => $context));
 -
 -        // Update $path.
 -        if ($path) {
 -            $path = $path.' / '.$categoryname;
 -        } else {
 -            $path = $categoryname;
 -        }
 -
 -        // Add this category to $list, if the permissions check out.
 -        if (empty($requiredcapability)) {
 -            $list[$category->id] = $path;
 -
 -        } else {
 -            $requiredcapability = (array)$requiredcapability;
 -            if (has_all_capabilities($requiredcapability, $context)) {
 -                $list[$category->id] = $path;
 -            }
 -        }
 -    }
 -
 -    // Add all the children recursively, while updating the parents array.
 -    if ($categories = get_child_categories($category->id)) {
 -        foreach ($categories as $cat) {
 -            if (!empty($category->id)) {
 -                if (isset($parents[$category->id])) {
 -                    $parents[$cat->id]   = $parents[$category->id];
 -                }
 -                $parents[$cat->id][] = $category->id;
 -            }
 -            make_categories_list($list, $parents, $requiredcapability, $excludeid, $cat, $path);
 -        }
 -    }
 -}
 -
  /**
   * This function generates a structured array of courses and categories.
   *
   */
  function get_course_category_tree($id = 0, $depth = 0) {
      global $DB, $CFG;
 -    $viewhiddencats = has_capability('moodle/category:viewhiddencategories', context_system::instance());
 -    $categories = get_child_categories($id);
 +    require_once($CFG->libdir. '/coursecatlib.php');
 +    if (!$coursecat = coursecat::get($id, IGNORE_MISSING)) {
 +        return array();
 +    }
 +    $categories = array();
      $categoryids = array();
 -    foreach ($categories as $key => &$category) {
 -        if (!$category->visible && !$viewhiddencats) {
 -            unset($categories[$key]);
 -            continue;
 -        }
 +    foreach ($coursecat->get_children() as $child) {
 +        $categories[] = $category = (object)convert_to_array($child);
          $categoryids[$category->id] = $category;
          if (empty($CFG->maxcategorydepth) || $depth <= $CFG->maxcategorydepth) {
              list($category->categories, $subcategories) = get_course_category_tree($category->id, $depth+1);
   */
  function print_whole_category_list($category=NULL, $displaylist=NULL, $parentslist=NULL, $depth=-1, $showcourses = true, $categorycourses=NULL) {
      global $CFG;
 +    require_once($CFG->libdir. '/coursecatlib.php');
  
      // maxcategorydepth == 0 meant no limit
      if (!empty($CFG->maxcategorydepth) && $depth >= $CFG->maxcategorydepth) {
          return;
      }
  
 -    if (!$displaylist) {
 -        make_categories_list($displaylist, $parentslist);
 +    // make sure category is visible to the current user
 +    if ($category) {
 +        if (!$coursecat = coursecat::get($category->id, IGNORE_MISSING)) {
 +            return;
 +        }
 +    } else {
 +        $coursecat = coursecat::get(0);
      }
  
      if (!$categorycourses) {
 -        if ($category) {
 -            $categorycourses = get_category_courses_array($category->id);
 -        } else {
 -            $categorycourses = get_category_courses_array();
 -        }
 +        $categorycourses = get_category_courses_array($coursecat->id);
      }
  
 -    if ($category) {
 -        if ($category->visible or has_capability('moodle/category:viewhiddencategories', context_system::instance())) {
 -            print_category_info($category, $depth, $showcourses, $categorycourses[$category->id]);
 -        } else {
 -            return;  // Don't bother printing children of invisible categories
 -        }
 -
 -    } else {
 -        $category = new stdClass();
 -        $category->id = "0";
 +    if ($coursecat->id) {
 +        print_category_info($category, $depth, $showcourses, $categorycourses[$category->id]);
      }
  
 -    if ($categories = get_child_categories($category->id)) {   // Print all the children recursively
 +    if ($categories = $coursecat->get_children()) {   // Print all the children recursively
          $countcats = count($categories);
          $count = 0;
          $first = true;
@@@ -1368,19 -1497,18 +1368,19 @@@ function get_category_courses_array_rec
  }
  
  /**
 - * This function will return $options array for html_writer::select(), with whitespace to denote nesting.
 + * Returns full course categories trees to be used in html_writer::select()
 + *
 + * Calls {@link coursecat::make_categories_list()} to build the tree and
 + * adds whitespace to denote nesting
 + *
 + * @return array array mapping coursecat id to the display name
   */
  function make_categories_options() {
 -    make_categories_list($cats,$parents);
 +    global $CFG;
 +    require_once($CFG->libdir. '/coursecatlib.php');
 +    $cats = coursecat::make_categories_list();
      foreach ($cats as $key => $value) {
 -        if (array_key_exists($key,$parents)) {
 -            if ($indent = count($parents[$key])) {
 -                for ($i = 0; $i < $indent; $i++) {
 -                    $cats[$key] = '&nbsp;'.$cats[$key];
 -                }
 -            }
 -        }
 +        $cats[$key] = str_repeat('&nbsp;', coursecat::get($key)->depth - 1). $value;
      }
      return $cats;
  }
@@@ -1544,10 -1672,9 +1544,10 @@@ function can_edit_in_category($category
   */
  function print_courses($category) {
      global $CFG, $OUTPUT;
 +    require_once($CFG->libdir. '/coursecatlib.php');
  
      if (!is_object($category) && $category==0) {
 -        $categories = get_child_categories(0);  // Parent = 0   ie top-level categories only
 +        $categories = coursecat::get(0)->get_children();  // Parent = 0   ie top-level categories only
          if (is_array($categories) && count($categories) == 1) {
              $category   = array_shift($categories);
              $courses    = get_courses_wmanagers($category->id,
@@@ -2340,17 -2467,19 +2340,19 @@@ function moveto_module($mod, $section, 
      }
  
      // if moving to a hidden section then hide module
-     if (!$section->visible && $mod->visible) {
-         // Set this in the object because it is sent as a response to ajax calls.
-         $mod->visible = 0;
-         set_coursemodule_visible($mod->id, 0);
-         // Set visibleold to 1 so module will be visible when section is made visible.
-         $DB->set_field('course_modules', 'visibleold', 1, array('id' => $mod->id));
-     }
-     if ($section->visible && !$mod->visible) {
-         set_coursemodule_visible($mod->id, $mod->visibleold);
-         // Set this in the object because it is sent as a response to ajax calls.
-         $mod->visible = $mod->visibleold;
+     if ($mod->section != $section->id) {
+         if (!$section->visible && $mod->visible) {
+             // Set this in the object because it is sent as a response to ajax calls.
+             $mod->visible = 0;
+             set_coursemodule_visible($mod->id, 0);
+             // Set visibleold to 1 so module will be visible when section is made visible.
+             $DB->set_field('course_modules', 'visibleold', 1, array('id' => $mod->id));
+         }
+         if ($section->visible && !$mod->visible) {
+             set_coursemodule_visible($mod->id, $mod->visibleold);
+             // Set this in the object because it is sent as a response to ajax calls.
+             $mod->visible = $mod->visibleold;
+         }
      }
  
  /// Add the module into the new section
@@@ -2605,6 -2734,112 +2607,6 @@@ function course_allowed_module($course
      return has_capability($capability, $coursecontext);
  }
  
 -/**
 - * Recursively delete category including all subcategories and courses.
 - * @param stdClass $category
 - * @param boolean $showfeedback display some notices
 - * @return array return deleted courses
 - */
 -function category_delete_full($category, $showfeedback=true) {
 -    global $CFG, $DB;
 -    require_once($CFG->libdir.'/gradelib.php');
 -    require_once($CFG->libdir.'/questionlib.php');
 -    require_once($CFG->dirroot.'/cohort/lib.php');
 -
 -    if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
 -        foreach ($children as $childcat) {
 -            category_delete_full($childcat, $showfeedback);
 -        }
 -    }
 -
 -    $deletedcourses = array();
 -    if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC')) {
 -        foreach ($courses as $course) {
 -            if (!delete_course($course, false)) {
 -                throw new moodle_exception('cannotdeletecategorycourse','','',$course->shortname);
 -            }
 -            $deletedcourses[] = $course;
 -        }
 -    }
 -
 -    // move or delete cohorts in this context
 -    cohort_delete_category($category);
 -
 -    // now delete anything that may depend on course category context
 -    grade_course_category_delete($category->id, 0, $showfeedback);
 -    if (!question_delete_course_category($category, 0, $showfeedback)) {
 -        throw new moodle_exception('cannotdeletecategoryquestions','','',$category->name);
 -    }
 -
 -    // finally delete the category and it's context
 -    $DB->delete_records('course_categories', array('id'=>$category->id));
 -    delete_context(CONTEXT_COURSECAT, $category->id);
 -    add_to_log(SITEID, "category", "delete", "index.php", "$category->name (ID $category->id)");
 -
 -    events_trigger('course_category_deleted', $category);
 -
 -    return $deletedcourses;
 -}
 -
 -/**
 - * Delete category, but move contents to another category.
 - * @param object $ccategory
 - * @param int $newparentid category id
 - * @return bool status
 - */
 -function category_delete_move($category, $newparentid, $showfeedback=true) {
 -    global $CFG, $DB, $OUTPUT;
 -    require_once($CFG->libdir.'/gradelib.php');
 -    require_once($CFG->libdir.'/questionlib.php');
 -    require_once($CFG->dirroot.'/cohort/lib.php');
 -
 -    if (!$newparentcat = $DB->get_record('course_categories', array('id'=>$newparentid))) {
 -        return false;
 -    }
 -
 -    if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
 -        foreach ($children as $childcat) {
 -            move_category($childcat, $newparentcat);
 -        }
 -    }
 -
 -    if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC', 'id')) {
 -        if (!move_courses(array_keys($courses), $newparentid)) {
 -            if ($showfeedback) {
 -                echo $OUTPUT->notification("Error moving courses");
 -            }
 -            return false;
 -        }
 -        if ($showfeedback) {
 -            echo $OUTPUT->notification(get_string('coursesmovedout', '', format_string($category->name)), 'notifysuccess');
 -        }
 -    }
 -
 -    // move or delete cohorts in this context
 -    cohort_delete_category($category);
 -
 -    // now delete anything that may depend on course category context
 -    grade_course_category_delete($category->id, $newparentid, $showfeedback);
 -    if (!question_delete_course_category($category, $newparentcat, $showfeedback)) {
 -        if ($showfeedback) {
 -            echo $OUTPUT->notification(get_string('errordeletingquestionsfromcategory', 'question', $category), 'notifysuccess');
 -        }
 -        return false;
 -    }
 -
 -    // finally delete the category and it's context
 -    $DB->delete_records('course_categories', array('id'=>$category->id));
 -    delete_context(CONTEXT_COURSECAT, $category->id);
 -    add_to_log(SITEID, "category", "delete", "index.php", "$category->name (ID $category->id)");
 -
 -    events_trigger('course_category_deleted', $category);
 -
 -    if ($showfeedback) {
 -        echo $OUTPUT->notification(get_string('coursecategorydeleted', '', format_string($category->name)), 'notifysuccess');
 -    }
 -    return true;
 -}
 -
  /**
   * Efficiently moves many courses around while maintaining
   * sortorder in order.
@@@ -2649,11 -2884,98 +2651,11 @@@ function move_courses($courseids, $cate
          }
      }
      fix_course_sortorder();
 +    cache_helper::purge_by_event('changesincourse');
  
      return true;
  }
  
 -/**
 - * Hide course category and child course and subcategories
 - * @param stdClass $category
 - * @return void
 - */
 -function course_category_hide($category) {
 -    global $DB;
 -
 -    $category->visible = 0;
 -    $DB->set_field('course_categories', 'visible', 0, array('id'=>$category->id));
 -    $DB->set_field('course_categories', 'visibleold', 0, array('id'=>$category->id));
 -    $DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($category->id)); // store visible flag so that we can return to it if we immediately unhide
 -    $DB->set_field('course', 'visible', 0, array('category' => $category->id));
 -    // get all child categories and hide too
 -    if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
 -        foreach ($subcats as $cat) {
 -            $DB->set_field('course_categories', 'visibleold', $cat->visible, array('id'=>$cat->id));
 -            $DB->set_field('course_categories', 'visible', 0, array('id'=>$cat->id));
 -            $DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($cat->id));
 -            $DB->set_field('course', 'visible', 0, array('category' => $cat->id));
 -        }
 -    }
 -    add_to_log(SITEID, "category", "hide", "editcategory.php?id=$category->id", $category->id);
 -}
 -
 -/**
 - * Show course category and child course and subcategories
 - * @param stdClass $category
 - * @return void
 - */
 -function course_category_show($category) {
 -    global $DB;
 -
 -    $category->visible = 1;
 -    $DB->set_field('course_categories', 'visible', 1, array('id'=>$category->id));
 -    $DB->set_field('course_categories', 'visibleold', 1, array('id'=>$category->id));
 -    $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($category->id));
 -    // get all child categories and unhide too
 -    if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
 -        foreach ($subcats as $cat) {
 -            if ($cat->visibleold) {
 -                $DB->set_field('course_categories', 'visible', 1, array('id'=>$cat->id));
 -            }
 -            $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($cat->id));
 -        }
 -    }
 -    add_to_log(SITEID, "category", "show", "editcategory.php?id=$category->id", $category->id);
 -}
 -
 -/**
 - * Efficiently moves a category - NOTE that this can have
 - * a huge impact access-control-wise...
 - */
 -function move_category($category, $newparentcat) {
 -    global $CFG, $DB;
 -
 -    $context = context_coursecat::instance($category->id);
 -
 -    $hidecat = false;
 -    if (empty($newparentcat->id)) {
 -        $DB->set_field('course_categories', 'parent', 0, array('id' => $category->id));
 -        $newparent = context_system::instance();
 -    } else {
 -        $DB->set_field('course_categories', 'parent', $newparentcat->id, array('id' => $category->id));
 -        $newparent = context_coursecat::instance($newparentcat->id);
 -
 -        if (!$newparentcat->visible and $category->visible) {
 -            // better hide category when moving into hidden category, teachers may unhide afterwards and the hidden children will be restored properly
 -            $hidecat = true;
 -        }
 -    }
 -
 -    context_moved($context, $newparent);
 -
 -    // now make it last in new category
 -    $DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('id'=>$category->id));
 -
 -    // Log action.
 -    add_to_log(SITEID, "category", "move", "editcategory.php?id=$category->id", $category->id);
 -
 -    // and fix the sortorders
 -    fix_course_sortorder();
 -
 -    if ($hidecat) {
 -        course_category_hide($category);
 -    }
 -}
 -
  /**
   * Returns the display name of the given section that the course prefers
   *
@@@ -2829,8 -3151,6 +2831,8 @@@ function create_course($data, $editorop
      course_create_sections_if_missing($course, 0);
  
      fix_course_sortorder();
 +    // purge appropriate caches in case fix_course_sortorder() did not change anything
 +    cache_helper::purge_by_event('changesincourse');
  
      // new context created - better mark it as dirty
      mark_context_dirty($context->path);
      return $course;
  }
  
 -/**
 - * Create a new course category and marks the context as dirty
 - *
 - * This function does not set the sortorder for the new category and
 - * @see{fix_course_sortorder} should be called after creating a new course
 - * category
 - *
 - * Please note that this function does not verify access control.
 - *
 - * @param object $category All of the data required for an entry in the course_categories table
 - * @return object new course category
 - */
 -function create_course_category($category) {
 -    global $DB;
 -
 -    $category->timemodified = time();
 -    $category->id = $DB->insert_record('course_categories', $category);
 -    $category = $DB->get_record('course_categories', array('id' => $category->id));
 -
 -    // We should mark the context as dirty
 -    $category->context = context_coursecat::instance($category->id);
 -    $category->context->mark_dirty();
 -
 -    return $category;
 -}
 -
  /**
   * Update a course.
   *
@@@ -2911,8 -3257,6 +2913,8 @@@ function update_course($data, $editorop
      }
  
      fix_course_sortorder();
 +    // purge appropriate caches in case fix_course_sortorder() did not change anything
 +    cache_helper::purge_by_event('changesincourse');
  
      // Test for and remove blocks which aren't appropriate anymore
      blocks_remove_inappropriate($course);
@@@ -3213,31 -3557,6 +3215,31 @@@ class course_request 
          return $this->properties->collision;
      }
  
 +    /**
 +     * Returns the category where this course request should be created
 +     *
 +     * Note that we don't check here that user has a capability to view
 +     * hidden categories if he has capabilities 'moodle/site:approvecourse' and
 +     * 'moodle/course:changecategory'
 +     *
 +     * @return coursecat
 +     */
 +    public function get_category() {
 +        global $CFG;
 +        require_once($CFG->libdir.'/coursecatlib.php');
 +        // If the category is not set, if the current user does not have the rights to change the category, or if the
 +        // category does not exist, we set the default category to the course to be approved.
 +        // The system level is used because the capability moodle/site:approvecourse is based on a system level.
 +        if (empty($this->properties->category) || !has_capability('moodle/course:changecategory', context_system::instance()) ||
 +                (!$category = coursecat::get($this->properties->category, IGNORE_MISSING, true))) {
 +            $category = coursecat::get($CFG->defaultrequestcategory, IGNORE_MISSING, true);
 +        }
 +        if (!$category) {
 +            $category = coursecat::get_default();
 +        }
 +        return $category;
 +    }
 +
      /**
       * This function approves the request turning it into a course
       *
          unset($data->reason);
          unset($data->requester);
  
 -        // If the category is not set, if the current user does not have the rights to change the category, or if the
 -        // category does not exist, we set the default category to the course to be approved.
 -        // The system level is used because the capability moodle/site:approvecourse is based on a system level.
 -        if (empty($data->category) || !has_capability('moodle/course:changecategory', context_system::instance()) ||
 -                (!$category = get_course_category($data->category))) {
 -            $category = get_course_category($CFG->defaultrequestcategory);
 -        }
 -
          // Set category
 +        $category = $this->get_category();
          $data->category = $category->id;
 -        $data->sortorder = $category->sortorder; // place as the first in category
 -
          // Set misc settings
          $data->requested = 1;
  
@@@ -3646,55 -3974,3 +3648,55 @@@ function update_module($moduleinfo) 
  
      return $moduleinfo;
  }
 +
 +/**
 + * Compare two objects to find out their correct order based on timestamp (to be used by usort).
 + * Sorts by descending order of time.
 + *
 + * @param stdClass $a First object
 + * @param stdClass $b Second object
 + * @return int 0,1,-1 representing the order
 + */
 +function compare_activities_by_time_desc($a, $b) {
 +    // Make sure the activities actually have a timestamp property.
 +    if ((!property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
 +        return 0;
 +    }
 +    // We treat instances without timestamp as if they have a timestamp of 0.
 +    if ((!property_exists($a, 'timestamp')) && (property_exists($b,'timestamp'))) {
 +        return 1;
 +    }
 +    if ((property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
 +        return -1;
 +    }
 +    if ($a->timestamp == $b->timestamp) {
 +        return 0;
 +    }
 +    return ($a->timestamp > $b->timestamp) ? -1 : 1;
 +}
 +
 +/**
 + * Compare two objects to find out their correct order based on timestamp (to be used by usort).
 + * Sorts by ascending order of time.
 + *
 + * @param stdClass $a First object
 + * @param stdClass $b Second object
 + * @return int 0,1,-1 representing the order
 + */
 +function compare_activities_by_time_asc($a, $b) {
 +    // Make sure the activities actually have a timestamp property.
 +    if ((!property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
 +      return 0;
 +    }
 +    // We treat instances without timestamp as if they have a timestamp of 0.
 +    if ((!property_exists($a, 'timestamp')) && (property_exists($b, 'timestamp'))) {
 +        return -1;
 +    }
 +    if ((property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
 +        return 1;
 +    }
 +    if ($a->timestamp == $b->timestamp) {
 +        return 0;
 +    }
 +    return ($a->timestamp < $b->timestamp) ? -1 : 1;
 +}
@@@ -785,6 -785,54 +785,6 @@@ class courselib_testcase extends advanc
          $this->assertEquals('FROG101 Introduction to pond life', get_course_display_name_for_list($course));
      }
  
 -    public function test_create_course_category() {
 -        global $CFG, $DB;
 -        $this->resetAfterTest(true);
 -
 -        // Create the category
 -        $data = new stdClass();
 -        $data->name = 'aaa';
 -        $data->description = 'aaa';
 -        $data->idnumber = '';
 -
 -        $category1 = create_course_category($data);
 -
 -        // Initially confirm that base data was inserted correctly
 -        $this->assertEquals($data->name, $category1->name);
 -        $this->assertEquals($data->description, $category1->description);
 -        $this->assertEquals($data->idnumber, $category1->idnumber);
 -
 -        // sortorder should be blank initially
 -        $this->assertEmpty($category1->sortorder);
 -
 -        // Calling fix_course_sortorder() should provide a new sortorder
 -        fix_course_sortorder();
 -        $category1 = $DB->get_record('course_categories', array('id' => $category1->id));
 -
 -        $this->assertGreaterThanOrEqual(1, $category1->sortorder);
 -
 -        // Create two more categories and test the sortorder worked correctly
 -        $data->name = 'ccc';
 -        $category2 = create_course_category($data);
 -        $this->assertEmpty($category2->sortorder);
 -
 -        $data->name = 'bbb';
 -        $category3 = create_course_category($data);
 -        $this->assertEmpty($category3->sortorder);
 -
 -        // Calling fix_course_sortorder() should provide a new sortorder to give category1,
 -        // category2, category3. New course categories are ordered by id not name
 -        fix_course_sortorder();
 -
 -        $category1 = $DB->get_record('course_categories', array('id' => $category1->id));
 -        $category2 = $DB->get_record('course_categories', array('id' => $category2->id));
 -        $category3 = $DB->get_record('course_categories', array('id' => $category3->id));
 -
 -        $this->assertGreaterThanOrEqual($category1->sortorder, $category2->sortorder);
 -        $this->assertGreaterThanOrEqual($category2->sortorder, $category3->sortorder);
 -        $this->assertGreaterThanOrEqual($category1->sortorder, $category3->sortorder);
 -    }
 -
      public function test_move_module_in_course() {
          global $DB;
  
          $this->assertEquals($pagetypelist, $testpagetypelist1);
      }
  
 +    public function test_compare_activities_by_time_desc() {
 +
 +        // Let's create some test data.
 +        $activitiesivities = array();
 +        $x = new stdClass();
 +        $x->timestamp = null;
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $x->timestamp = 1;
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $x->timestamp = 3;
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $x->timestamp = 0;
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $x->timestamp = 5;
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $x->timestamp = 5;
 +        $activities[] = $x;
 +
 +        // Do the sorting.
 +        usort($activities, 'compare_activities_by_time_desc');
 +
 +        // Let's check the result.
 +        $last = 10;
 +        foreach($activities as $activity) {
 +            if (empty($activity->timestamp)) {
 +                $activity->timestamp = 0;
 +            }
 +            $this->assertLessThanOrEqual($last, $activity->timestamp);
 +        }
 +    }
 +
 +    public function test_compare_activities_by_time_asc() {
 +
 +        // Let's create some test data.
 +        $activities = array();
 +        $x = new stdClass();
 +        $x->timestamp = null;
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $x->timestamp = 1;
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $x->timestamp = 3;
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $x->timestamp = 0;
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $x->timestamp = 5;
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $activities[] = $x;
 +
 +        $x = new stdClass();
 +        $x->timestamp = 5;
 +        $activities[] = $x;
 +
 +        // Do the sorting.
 +        usort($activities, 'compare_activities_by_time_asc');
 +
 +        // Let's check the result.
 +        $last = 0;
 +        foreach($activities as $activity) {
 +            if (empty($activity->timestamp)) {
 +                $activity->timestamp = 0;
 +            }
 +            $this->assertGreaterThanOrEqual($last, $activity->timestamp);
 +        }
 +    }
++
+     /**
+      * Tests moving a module between hidden/visible sections and
+      * verifies that the course/module visiblity seettings are
+      * retained.
+      */
+     public function test_moveto_module_between_hidden_sections() {
+         global $DB;
+         $this->resetAfterTest(true);
+         $course = $this->getDataGenerator()->create_course(array('numsections' => 4), array('createsections' => true));
+         $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
+         $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
+         $quiz= $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+         // Set the page as hidden
+         set_coursemodule_visible($page->cmid, 0);
+         // Set sections 3 as hidden.
+         set_section_visible($course->id, 3, 0);
+         $modinfo = get_fast_modinfo($course);
+         $hiddensection = $modinfo->get_section_info(3);
+         // New section is definitely not visible:
+         $this->assertEquals($hiddensection->visible, 0);
+         $forumcm = $modinfo->cms[$forum->cmid];
+         $pagecm = $modinfo->cms[$page->cmid];
+         // Move the forum and the page to a hidden section.
+         moveto_module($forumcm, $hiddensection);
+         moveto_module($pagecm, $hiddensection);
+         // Reset modinfo cache.
+         get_fast_modinfo(0, 0, true);
+         $modinfo = get_fast_modinfo($course);
+         // Verify that forum and page have been moved to the hidden section and quiz has not.
+         $this->assertContains($forum->cmid, $modinfo->sections[3]);
+         $this->assertContains($page->cmid, $modinfo->sections[3]);
+         $this->assertNotContains($quiz->cmid, $modinfo->sections[3]);
+         // Verify that forum has been made invisible.
+         $forumcm = $modinfo->cms[$forum->cmid];
+         $this->assertEquals($forumcm->visible, 0);
+         // Verify that old state has been retained.
+         $this->assertEquals($forumcm->visibleold, 1);
+         // Verify that page has stayed invisible.
+         $pagecm = $modinfo->cms[$page->cmid];
+         $this->assertEquals($pagecm->visible, 0);
+         // Verify that old state has been retained.
+         $this->assertEquals($pagecm->visibleold, 0);
+         // Verify that quiz has been unaffected.
+         $quizcm = $modinfo->cms[$quiz->cmid];
+         $this->assertEquals($quizcm->visible, 1);
+         // Move forum and page back to visible section.
+         $visiblesection = $modinfo->get_section_info(2);
+         moveto_module($forumcm, $visiblesection);
+         moveto_module($pagecm, $visiblesection);
+         // Reset modinfo cache.
+         get_fast_modinfo(0, 0, true);
+         $modinfo = get_fast_modinfo($course);
+         // Verify that forum has been made visible.
+         $forumcm = $modinfo->cms[$forum->cmid];
+         $this->assertEquals($forumcm->visible, 1);
+         // Verify that page has stayed invisible.
+         $pagecm = $modinfo->cms[$page->cmid];
+         $this->assertEquals($pagecm->visible, 0);
+         // Move the page in the same section (this is what mod duplicate does_
+         moveto_module($pagecm, $visiblesection, $forumcm);
+         // Reset modinfo cache.
+         get_fast_modinfo(0, 0, true);
+         // Verify that the the page is still hidden
+         $modinfo = get_fast_modinfo($course);
+         $pagecm = $modinfo->cms[$page->cmid];
+         $this->assertEquals($pagecm->visible, 0);
+     }
+     /**
+      * Tests moving a module around in the same section. moveto_module()
+      * is called this way in modduplicate.
+      */
+     public function test_moveto_module_in_same_section() {
+         global $DB;
+         $this->resetAfterTest(true);
+         $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
+         $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
+         $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
+         // Simulate inconsistent visible/visibleold values (MDL-38713).
+         $cm = $DB->get_record('course_modules', array('id' => $page->cmid), '*', MUST_EXIST);
+         $cm->visible = 0;
+         $cm->visibleold = 1;
+         $DB->update_record('course_modules', $cm);
+         $modinfo = get_fast_modinfo($course);
+         $forumcm = $modinfo->cms[$forum->cmid];
+         $pagecm = $modinfo->cms[$page->cmid];
+         // Verify that page is hidden.
+         $this->assertEquals($pagecm->visible, 0);
+         // Verify section 0 is where all mods added.
+         $section = $modinfo->get_section_info(0);
+         $this->assertEquals($section->id, $forumcm->section);
+         $this->assertEquals($section->id, $pagecm->section);
+         // Move the forum and the page to a hidden section.
+         moveto_module($pagecm, $section, $forumcm);
+         // Reset modinfo cache.
+         get_fast_modinfo(0, 0, true);
+         // Verify that the the page is still hidden
+         $modinfo = get_fast_modinfo($course);
+         $pagecm = $modinfo->cms[$page->cmid];
+         $this->assertEquals($pagecm->visible, 0);
+     }
  }