Index: course/lib.php
===================================================================
RCS file: /cvsroot/moodle/moodle/course/lib.php,v
retrieving revision 1.538.2.46
diff -u -a -w -r1.538.2.46 lib.php
--- course/lib.php	21 May 2008 12:02:08 -0000	1.538.2.46
+++ course/lib.php	5 Jul 2008 12:47:50 -0000
@@ -2276,6 +2276,13 @@
     return false;
 }
 
+/**
+ * Moves a section up or down by 1. CANNOT BE USED DIRECTLY BY AJAX!
+ *
+ * @param object $course
+ * @param int $section
+ * @param int $move (-1 or 1)
+ */
 function move_section($course, $section, $move) {
 /// Moves a whole course section up and down within the course
     global $USER;
@@ -2324,6 +2331,113 @@
     return true;
 }
 
+/**
+ * Moves a section within a course, from a position to another.
+ * Be very careful: $section and $destination refer to section number,
+ * not id!.
+ *
+ * @param object $course
+ * @param int $section Section number (not id!!!)
+ * @param int $destination
+ * @return boolean Result
+ */
+function move_section_to($course, $section, $destination) {
+/// Moves a whole course section up and down within the course
+    global $USER;
+
+    if (!$destination) {
+        return true;
+    }
+
+    if ($destination > $course->numsections or $destination < 1) {
+        return false;
+    }
+
+    // Get all sections for this course and re-order them (2 of them should now share the same section number)
+    if (!$sections = get_records_menu('course_sections', 'course',$course->id, 'section ASC, id ASC', 'id, section')) {
+        return false;
+    }
+
+    $sections = reorder_sections($sections, $section, $destination);
+
+    // Update all sections
+    foreach ($sections as $id => $position) {
+        set_field('course_sections', 'section', $position, 'id', $id);
+    }
+
+    // if the focus is on the section that is being moved, then move the focus along
+    if (isset($USER->display[$course->id]) and ($USER->display[$course->id] == $section)) {
+        course_set_display($course->id, $destination);
+    }
+    return true;
+}
+
+/**
+ * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
+ * an original position number and a target position number, rebuilds the array so that the
+ * move is made without any duplication of section positions.
+ * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to
+ * insert a section before the first one, you must give 0 as the target (section 0 can never be moved).
+ *
+ * @param array $sections
+ * @param int $origin_position
+ * @param int $target_position
+ * @return array
+ */
+function reorder_sections($sections, $origin_position, $target_position) {
+    if (!is_array($sections)) {
+        return false;
+    }
+
+    // We can't move section position 0
+    if ($origin_position < 1) {
+        echo "We can't move section position 0";
+        return false;
+    }
+
+    // Locate origin section in sections array
+    if (!$origin_key = array_search($origin_position, $sections)) {
+        echo "searched position not in sections array";
+        return false; // searched position not in sections array
+    }
+
+    // Extract origin section
+    $origin_section = $sections[$origin_key];
+    unset($sections[$origin_key]);
+
+    // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!)
+    $found = false;
+    $append_array = array();
+    foreach ($sections as $id => $position) {
+        if ($found) {
+            $append_array[$id] = $position;
+            unset($sections[$id]);
+        }
+        if ($position == $target_position) {
+            $found = true;
+        }
+    }
+
+    // Append moved section
+    $sections[$origin_key] = $origin_section;
+
+    // Append rest of array (if applicable)
+    if (!empty($append_array)) {
+        foreach ($append_array as $id => $position) {
+            $sections[$id] = $position;
+        }
+    }
+
+    // Renumber positions
+    $position = 0;
+    foreach ($sections as $id => $p) {
+        $sections[$id] = $position;
+        $position++;
+    }
+
+    return $sections;
+
+}
 
 function moveto_module($mod, $section, $beforemod=NULL) {
 /// All parameters are objects
Index: course/rest.php
===================================================================
RCS file: /cvsroot/moodle/moodle/course/rest.php,v
retrieving revision 1.8.6.1
diff -u -a -w -r1.8.6.1 rest.php
--- course/rest.php	1 Feb 2008 08:22:25 -0000	1.8.6.1
+++ course/rest.php	5 Jul 2008 12:47:50 -0000
@@ -80,7 +80,7 @@
                         break;
 
                     case 'move':
-                        move_section($course, $id, $value);
+                        move_section_to($course, $id, $value);
                         break;
                 }
                 rebuild_course_cache($course->id);
Index: lib/ajax/section_classes.js
===================================================================
RCS file: /cvsroot/moodle/moodle/lib/ajax/section_classes.js,v
retrieving revision 1.35.2.2
diff -u -a -w -r1.35.2.2 section_classes.js
--- lib/ajax/section_classes.js	17 Apr 2008 08:28:24 -0000	1.35.2.2
+++ lib/ajax/section_classes.js	5 Jul 2008 12:47:50 -0000
@@ -213,16 +213,17 @@
         var loopStart = 1;
         var loopInc = 'i++';
         var loopmodifier = 'i - 1'; 
+        var targetOffset = 0;
     } else {
         var loopCondition = 'i > 0';
         var loopStart = sectionCount - 1;  
         var loopInc = 'i--'; 
         var loopmodifier = 'i + 1';       
+        var targetOffset = 1;
     }
 
     //move on backend
-    main.connect('POST','class=section&field=move',null,'id='+this.sectionId+'&value='
-            +(target.sectionId - this.sectionId));
+    main.connect('POST','class=section&field=move',null,'id='+this.sectionId+'&value=' + (target.sectionId - targetOffset));
 
     //move on front end
     for (var i=loopStart; eval(loopCondition); eval(loopInc)) {
Index: course/simpletest/testcourselib.php
===================================================================
RCS file: course/simpletest/testcourselib.php
diff -N course/simpletest/testcourselib.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ course/simpletest/testcourselib.php	5 Jul 2008 12:47:50 -0000
@@ -0,0 +1,83 @@
+<?php // $Id: testcourselib.php,v 1.19 2008/06/02 21:05:51 skodak Exp $
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.org                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
+//                                                                       //
+// This program is free software; you can redistribute it and/or modify  //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation; either version 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program is distributed in the hope that it will be useful,       //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ * Unit tests for Course lib.
+ *
+ * @author nicolasconnault@gmail.com
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package moodlecore
+ */
+
+if (!defined('MOODLE_INTERNAL')) {
+    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
+}
+
+require_once($CFG->dirroot . '/course/lib.php');
+
+class courselib_test extends UnitTestCase {
+
+    function setUp() {
+    }
+
+    function tearDown() {
+    }
+
+
+    function testReorderSections() {
+        $sections = array(20 => 0, 21 => 1, 22 => 2, 23 => 3, 24 => 4, 25 => 5);
+        $this->assertFalse(reorder_sections(1,3,4));
+
+        $newsections = reorder_sections($sections, 2, 4);
+        $newsections_flipped = array_flip($newsections);
+
+        $this->assertEqual(20, reset($newsections_flipped));
+        $this->assertEqual(21, next($newsections_flipped));
+        $this->assertEqual(23, next($newsections_flipped));
+        $this->assertEqual(24, next($newsections_flipped));
+        $this->assertEqual(22, next($newsections_flipped));
+        $this->assertEqual(25, next($newsections_flipped));
+
+        $newsections = reorder_sections($sections, 4, 0);
+        $newsections_flipped = array_flip($newsections);
+
+        $this->assertEqual(20, reset($newsections_flipped));
+        $this->assertEqual(24, next($newsections_flipped));
+        $this->assertEqual(21, next($newsections_flipped));
+        $this->assertEqual(22, next($newsections_flipped));
+        $this->assertEqual(23, next($newsections_flipped));
+        $this->assertEqual(25, next($newsections_flipped));
+
+        $newsections = reorder_sections($sections, 1, 5);
+        $newsections_flipped = array_flip($newsections);
+
+        $this->assertEqual(20, reset($newsections_flipped));
+        $this->assertEqual(22, next($newsections_flipped));
+        $this->assertEqual(23, next($newsections_flipped));
+        $this->assertEqual(24, next($newsections_flipped));
+        $this->assertEqual(25, next($newsections_flipped));
+        $this->assertEqual(21, next($newsections_flipped));
+    }
+}
