aragonc 10 anni fa
parent
commit
1ccf25ae33
42 ha cambiato i file con 1575 aggiunte e 609 eliminazioni
  1. 54 1
      README.md
  2. 9 8
      composer.lock
  3. 0 1
      main/attendance/attendance_calendar.php
  4. 40 0
      main/attendance/attendance_controller.php
  5. 10 5
      main/attendance/attendance_list.php
  6. 40 13
      main/attendance/index.php
  7. 60 22
      main/coursecopy/classes/CourseRestorer.class.php
  8. 2 2
      main/cron/create_course_sessions.php
  9. 132 0
      main/cron/remind_course_expiration.php
  10. 5 5
      main/gradebook/gradebook_flatview.php
  11. 1 2
      main/gradebook/gradebook_view_result.php
  12. 1 1
      main/gradebook/index.php
  13. 25 0
      main/gradebook/lib/be/abstractlink.class.php
  14. 42 21
      main/gradebook/lib/be/attendancelink.class.php
  15. 158 45
      main/gradebook/lib/be/category.class.php
  16. 32 13
      main/gradebook/lib/be/evaluation.class.php
  17. 22 12
      main/gradebook/lib/be/exerciselink.class.php
  18. 23 13
      main/gradebook/lib/be/forumthreadlink.class.php
  19. 14 6
      main/gradebook/lib/be/learnpathlink.class.php
  20. 2 3
      main/gradebook/lib/be/result.class.php
  21. 14 6
      main/gradebook/lib/be/studentpublicationlink.class.php
  22. 26 10
      main/gradebook/lib/be/surveylink.class.php
  23. 175 67
      main/gradebook/lib/fe/gradebooktable.class.php
  24. 2 2
      main/gradebook/lib/flatview_data_generator.class.php
  25. 142 22
      main/gradebook/lib/gradebook_data_generator.class.php
  26. 26 12
      main/gradebook/lib/scoredisplay.class.php
  27. 86 12
      main/gradebook/lib/user_data_generator.class.php
  28. 97 18
      main/inc/lib/attendance.lib.php
  29. 50 231
      main/inc/lib/course.lib.php
  30. 2 6
      main/inc/lib/sessionmanager.lib.php
  31. 0 12
      main/inc/lib/social.lib.php
  32. 10 3
      main/install/configuration.dist.php
  33. 3 1
      main/install/db_main.sql
  34. 4 2
      main/lang/english/trad4all.inc.php
  35. 20 3
      main/lang/french/trad4all.inc.php
  36. 5 2
      main/lang/spanish/trad4all.inc.php
  37. 16 10
      main/newscorm/lp_controller.php
  38. 13 7
      main/newscorm/scorm_api.php
  39. 3 3
      main/template/default/skill/skill_wheel.tpl
  40. 3 3
      main/template/default/skill/skill_wheel_student.tpl
  41. 4 4
      plugin/buycourses/src/buy_course.lib.php
  42. 202 0
      tests/scripts/course2session.php

+ 54 - 1
README.md

@@ -2,9 +2,36 @@
 
 ## Installation
 
+This installation guide is for development environments only.
+
+### Install PHP, a web server and MySQL/MariaDB
+
+To run Chamilo, you will need at least a web server (we recommend Apache2 for commodity reasons), a database server (we recommend MariaDB but will explain MySQL for commodity reasons) and a PHP interpreter (and a series of libraries for it). If you are working on a Debian-based system (Debian, Ubuntu, Mint, etc), just
+type
+```
+sudo apt-get install libapache2-mod-php mysql-server php5-gd php5-intl php5-curl php5-json
+```
+
+### Install Git
+
+The development version 1.10.x requires you to have Git installed. If you are working on a Debian-based system (Debian, Ubuntu, Mint, etc), just type
+```
+sudo apt-get install git
+```
+
+### Install Composer
+
+To run the development version 1.10.x, you need Composer, a libraries dependency management system that will update all the libraries you need for Chamilo to the latest available version.
+
+Make sure you have Composer installed. If you do, you should be able to launch "composer" on the command line and have the inline help of composer show a few subcommands. If you don't, please follow the installation guide at https://getcomposer.org/download/
+
+### Download Chamilo from GitHub
+
 Clone the repository
 
 ```
+sudo mkdir chamilo-1.10
+sudo chown -R `whoami` chamilo-1.10
 git clone https://github.com/chamilo/chamilo-lms.git chamilo-1.10
 ```
 
@@ -16,11 +43,37 @@ git checkout --track origin/1.10.x
 git config --global push.default current
 ```
 
-Update dependencies using Composer
+### Update dependencies using Composer
+
+From the Chamilo folder (in which you should be now if you followed the previous steps), launch:
 
 ```
 composer update
 ```
 
+### Change permissions
+
+On a Debian-based system, launch:
+```
+sudo chown -R www-data:www-data archive course data home searchdb main/upload/users main/upload/sessions main/upload/courses main/default_course_document/images main/lang main/css main/inc/conf
+```
+
+### Start the installer
+
+In your browser, load the Chamilo URL. You should be automatically redirected to the installer. If not, add the "main/install/index.php" suffix manually in your browser address bar. The rest should be a matter of simple OK > Next > OK > Next...
+
+## Upgrade from 1.9.x
+
+1.10.x is a major version. As such, it contains a series of new features, that also mean a series of new database changes in regards with versions 1.9.x. As such, it is necessary to go through an upgrade procedure when upgrading from 1.9.x to 1.10.x.
+
+Although 1.10.x is not beta yet (and as such is *NOT* ready for production and does *NOT* contain all database changes yet - DO NOT UPGRADE A PRODUCTION SYSTEM to 1.10.x yet, PLEASE!), the upgrade procedure works to get you up and running with the latest *development* code of 1.10.x with data from an 1.9.x system, so feel free to test it out, but keep a backup of your database from 1.9.x as you will need to do the upgrade again each time you are updating the 1.10.x code from Git.
+
+The upgrade procedure is relatively straightforward. If you have a 1.9.x initially installed with Git, here are the steps you should follow (considering you are already inside the Chamilo folder):
+```
+git fetch --all
+git checkout origin 1.10.x
+```
+Then load the Chamilo URL in your browser, adding "main/install/index.php" and follow the upgrade instructions. Select the "Upgrade from 1.8.x" button to proceed.
+
 # Documentation
 For more information on Chamilo, visit https://stable.chamilo.org/documentation

+ 9 - 8
composer.lock

@@ -1365,16 +1365,16 @@
         },
         {
             "name": "monolog/monolog",
-            "version": "1.12.0",
+            "version": "1.13.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Seldaek/monolog.git",
-                "reference": "1fbe8c2641f2b163addf49cc5e18f144bec6b19f"
+                "reference": "c41c218e239b50446fd883acb1ecfd4b770caeae"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1fbe8c2641f2b163addf49cc5e18f144bec6b19f",
-                "reference": "1fbe8c2641f2b163addf49cc5e18f144bec6b19f",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c41c218e239b50446fd883acb1ecfd4b770caeae",
+                "reference": "c41c218e239b50446fd883acb1ecfd4b770caeae",
                 "shasum": ""
             },
             "require": {
@@ -1391,6 +1391,7 @@
                 "phpunit/phpunit": "~4.0",
                 "raven/raven": "~0.5",
                 "ruflin/elastica": "0.90.*",
+                "swiftmailer/swiftmailer": "~5.3",
                 "videlalvaro/php-amqplib": "~2.4"
             },
             "suggest": {
@@ -1407,7 +1408,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.12.x-dev"
+                    "dev-master": "1.13.x-dev"
                 }
             },
             "autoload": {
@@ -1433,19 +1434,19 @@
                 "logging",
                 "psr-3"
             ],
-            "time": "2014-12-29 21:29:35"
+            "time": "2015-03-05 01:12:12"
         },
         {
             "name": "mpdf/mpdf",
             "version": "v5.7.4",
             "source": {
                 "type": "git",
-                "url": "https://github.com/finwe/mpdf.git",
+                "url": "https://github.com/mpdf/mpdf.git",
                 "reference": "f9a374c7ea975ce8c795cec4dfd17ef55addac9c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/finwe/mpdf/zipball/f9a374c7ea975ce8c795cec4dfd17ef55addac9c",
+                "url": "https://api.github.com/repos/mpdf/mpdf/zipball/f9a374c7ea975ce8c795cec4dfd17ef55addac9c",
                 "reference": "f9a374c7ea975ce8c795cec4dfd17ef55addac9c",
                 "shasum": ""
             },

+ 0 - 1
main/attendance/attendance_calendar.php

@@ -130,7 +130,6 @@ if (isset($action) && $action == 'calendar_add') {
                             Display::return_icon('delete.png', get_lang('Delete'), array('style'=>'vertical-align:middle'), ICON_SIZE_SMALL).'</a>';
                         echo '</span>';
                     }
-
                 }
             }
             echo '</div>';

+ 40 - 0
main/attendance/attendance_controller.php

@@ -156,6 +156,11 @@ class AttendanceController
      */
     public function attendance_delete($attendance_id)
     {
+        $allowDeleteAttendance = api_get_configuration_value('allow_delete_attendance');
+        if ($allowDeleteAttendance == false) {
+            $this->attendance_list();
+            return false;
+        }
         $attendance = new Attendance();
         if (!empty($attendance_id)) {
             $affected_rows = $attendance->attendance_delete($attendance_id);
@@ -166,6 +171,41 @@ class AttendanceController
         $this->attendance_list();
     }
 
+    /**
+     * It's used for make attendance visible
+     * render to attendance_list view
+     * @param int	attendance id
+     */
+    public function attendanceSetVisible($attendanceId)
+    {
+        $attendance = new Attendance();
+        if (!empty($attendanceId)) {
+            $affectedRows = $attendance->changeVisibility($attendanceId, 1);
+        }
+        if ($affectedRows) {
+            $message['message_attendance_delete'] = true;
+        }
+        $this->attendance_list();
+    }
+
+    /**
+     * It's used for make attendance invisible
+     * render to attendance_list view
+     * @param int	attendance id
+     */
+    public function attendanceSetInvisible($attendanceId)
+    {
+        $attendance = new Attendance();
+        if (!empty($attendanceId)) {
+            $affectedRows = $attendance->changeVisibility($attendanceId, 0);
+        }
+        if ($affectedRows) {
+            $message['message_attendance_delete'] = true;
+        }
+        $this->attendance_list();
+    }
+
+
     /**
      * Restores an attendance entry and fallback to attendances rendering
      * @param int	attendance id

+ 10 - 5
main/attendance/attendance_list.php

@@ -18,10 +18,6 @@ if (api_is_allowed_to_edit(null, true)) {
     echo '<div class="actions">';
     echo '<a href="index.php?'.api_get_cidreq().$param_gradebook.'&action=attendance_add">'.
         Display::return_icon('new_attendance_list.png',get_lang('CreateANewAttendance'),'',ICON_SIZE_MEDIUM).'</a>';
-
-    /*echo '<a href="index.php?'.api_get_cidreq().$param_gradebook.'&action=calendar_logins">'.
-        Display::return_icon('attendance_list.png',get_lang('Logins'),'',ICON_SIZE_MEDIUM).'</a>';*/
-
     echo '</div>';
 }
 $attendance = new Attendance();
@@ -46,7 +42,16 @@ $table->set_header(3, get_lang('CountDoneAttendance'), true, array('style'=>'wid
 
 if (api_is_allowed_to_edit(null, true)) {
     $table->set_header(4, get_lang('Actions'), false, array('style'=>'text-align:center'));
-    $table->set_form_actions(array ('attendance_delete_select' => get_lang('DeleteAllSelectedAttendances')));
+    $actions = array(
+        'attendance_set_invisible_select' => get_lang('SetInvisible'),
+        'attendance_set_visible_select' => get_lang('SetVisible')
+    );
+
+    $allow = api_get_configuration_value('allow_delete_attendance');
+    if ($allow) {
+        $actions['attendance_delete_select'] = get_lang('DeleteAllSelectedAttendances');
+    }
+    $table->set_form_actions($actions);
 }
 
 if ($table->get_total_number_of_items() > 0) {

+ 40 - 13
main/attendance/index.php

@@ -41,6 +41,10 @@ $actions = array(
     'attendance_edit',
     'attendance_delete',
     'attendance_delete_select',
+    'attendance_set_invisible',
+    'attendance_set_invisible_select',
+    'attendance_set_visible',
+    'attendance_set_visible_select',
     'attendance_restore',
     'attendance_sheet_export_to_pdf',
     'attendance_sheet_list_no_edit',
@@ -228,11 +232,20 @@ if ($action == 'calendar_add') {
     $interbreadcrumb[] = array('url' => '#', 'name' => get_lang('AddDateAndTime'));
 }
 
-// delete selected attendance
+// Delete selected attendance
 if (isset($_POST['action']) && $_POST['action'] == 'attendance_delete_select') {
     $attendanceController->attendance_delete($_POST['id']);
 }
-// distpacher actions to controller
+
+if (isset($_POST['action']) && $_POST['action'] == 'attendance_set_invisible_select') {
+    $attendanceController->attendanceSetInvisible($_POST['id']);
+}
+
+if (isset($_POST['action']) && $_POST['action'] == 'attendance_set_visible_select') {
+    $attendanceController->attendanceSetVisible($_POST['id']);
+}
+
+// Distpacher actions to controller
 switch ($action) {
     case 'attendance_list':
         $attendanceController->attendance_list();
@@ -244,27 +257,41 @@ switch ($action) {
             api_not_allowed();
         }
         break;
-    case 'attendance_edit' :
+    case 'attendance_edit':
         if (api_is_allowed_to_edit(null, true)) {
             $attendanceController->attendance_edit($attendance_id);
         } else {
             api_not_allowed();
         }
         break;
-    case 'attendance_delete' :
+    case 'attendance_delete':
         if (api_is_allowed_to_edit(null, true)) {
             $attendanceController->attendance_delete($attendance_id);
         } else {
             api_not_allowed();
         }
         break;
-    case 'attendance_restore':
+    case 'attendance_set_invisible':
         if (api_is_allowed_to_edit(null, true)) {
-            $attendanceController->attendance_restore($attendance_id);
+            $attendanceController->attendanceSetInvisible($attendance_id);
+        } else {
+            api_not_allowed();
+        }
+        break;
+    case 'attendance_set_visible':
+        if (api_is_allowed_to_edit(null, true)) {
+            $attendanceController->attendanceSetVisible($attendance_id);
         } else {
             api_not_allowed();
         }
         break;
+    /*case 'attendance_restore':
+        if (api_is_allowed_to_edit(null, true)) {
+            $attendanceController->attendance_restore($attendance_id);
+        } else {
+            api_not_allowed();
+        }
+        break;*/
     case 'attendance_sheet_list':
         $attendanceController->attendance_sheet($action, $attendance_id, $student_id, true);
         break;
@@ -281,22 +308,22 @@ switch ($action) {
             api_not_allowed();
         }
         break;
-    case 'lock_attendance' :
-    case 'unlock_attendance' :
+    case 'lock_attendance':
+    case 'unlock_attendance':
         if (api_is_allowed_to_edit(null, true)) {
             $attendanceController->lock_attendance($action, $attendance_id);
         } else {
             api_not_allowed();
         }
         break;
-    case 'calendar_add' :
-    case 'calendar_edit' :
-    case 'calendar_all_delete' :
-    case 'calendar_delete' :
+    case 'calendar_add':
+    case 'calendar_edit':
+    case 'calendar_all_delete':
+    case 'calendar_delete':
         if (!api_is_allowed_to_edit(null, true)) {
             api_not_allowed();
         }
-    case 'calendar_list' :
+    case 'calendar_list':
         $attendanceController->attendance_calendar($action, $attendance_id, $calendar_id);
         break;
     case 'calendar_logins':

+ 60 - 22
main/coursecopy/classes/CourseRestorer.class.php

@@ -889,7 +889,7 @@ class CourseRestorer
 			foreach ($resources[RESOURCE_FORUM] as $id => $forum) {
                 $params = (array)$forum->obj;
                 if ($this->course->resources[RESOURCE_FORUMCATEGORY][$params['forum_category']]->destination_id == -1) {
-                    $cat_id = $this->restore_forum_category($params['forum_category']);
+                    $cat_id = $this->restore_forum_category($params['forum_category'], $sessionId);
                 } else {
                     $cat_id = $this->course->resources[RESOURCE_FORUMCATEGORY][$params['forum_category']]->destination_id;
                 }
@@ -924,7 +924,7 @@ class CourseRestorer
 				if (is_array($this->course->resources[RESOURCE_FORUMTOPIC])) {
 					foreach ($this->course->resources[RESOURCE_FORUMTOPIC] as $topic_id => $topic) {
 						if ($topic->obj->forum_id == $id) {
-							$this->restore_topic($topic_id, $new_id);
+							$this->restore_topic($topic_id, $new_id, $sessionId);
 							$forum_topics ++;
 						}
 					}
@@ -941,7 +941,7 @@ class CourseRestorer
 	/**
 	 * Restore forum-categories
 	 */
-    public function restore_forum_category($my_id = null)
+    public function restore_forum_category($my_id = null, $sessionId = 0)
     {
 		$forum_cat_table = Database :: get_course_table(TABLE_FORUM_CATEGORY);
 		$resources = $this->course->resources;
@@ -964,7 +964,14 @@ class CourseRestorer
                     }
                     $params = (array) $forum_cat->obj;
                     $params['c_id'] = $this->destination_course_id;
-                    $params['cat_comment']    = DocumentManager::replace_urls_inside_content_html_from_copy_course($params['cat_comment'], $this->course->code, $this->course->destination_path, $this->course->backup_path, $this->course->info['path']);
+                    $params['cat_comment'] = DocumentManager::replace_urls_inside_content_html_from_copy_course(
+                        $params['cat_comment'],
+                        $this->course->code,
+                        $this->course->destination_path,
+                        $this->course->backup_path,
+                        $this->course->info['path']
+                    );
+                    $params['session_id'] = intval($sessionId);
                     unset($params['cat_id']);
                     $params = self::DBUTF8_array($params);
                     $new_id = Database::insert($forum_cat_table, $params);
@@ -980,14 +987,14 @@ class CourseRestorer
 	/**
 	 * Restore a forum-topic
 	 */
-    public function restore_topic($thread_id, $forum_id)
+    public function restore_topic($thread_id, $forum_id, $sessionId = 0)
     {
 		$table = Database :: get_course_table(TABLE_FORUM_THREAD);
 		$topic = $this->course->resources[RESOURCE_FORUMTOPIC][$thread_id];
 
         $params = (array)$topic->obj;
         $params = self::DBUTF8_array($params);
-        $params['c_id']     = $this->destination_course_id;
+        $params['c_id'] = $this->destination_course_id;
         $params['forum_id'] = $forum_id;
         $params['thread_poster_id'] = $this->first_teacher_id;
         $params['thread_date'] = api_get_utc_datetime();
@@ -995,19 +1002,31 @@ class CourseRestorer
         $params['thread_last_post'] = 0;
         $params['thread_replies'] = 0;
         $params['thread_views'] = 0;
+        $params['session_id'] = intval($sessionId);
         unset($params['thread_id']);
 
         $new_id = Database::insert($table, $params);
-        api_item_property_update($this->destination_course_info, TOOL_FORUM_THREAD, $new_id, 'ThreadAdded', api_get_user_id(), 0, 0, null, null);
+        api_item_property_update(
+            $this->destination_course_info,
+            TOOL_FORUM_THREAD,
+            $new_id,
+            'ThreadAdded',
+            api_get_user_id(),
+            0,
+            0,
+            null,
+            null,
+            $sessionId
+        );
 
 		$this->course->resources[RESOURCE_FORUMTOPIC][$thread_id]->destination_id = $new_id;
 
 		$topic_replies = -1;
 
-		foreach ($this->course->resources[RESOURCE_FORUMPOST] as $post_id => $post){
+		foreach ($this->course->resources[RESOURCE_FORUMPOST] as $post_id => $post) {
 			if ($post->obj->thread_id == $thread_id) {
 				$topic_replies++;
-				$this->restore_post($post_id, $new_id, $forum_id);
+				$this->restore_post($post_id, $new_id, $forum_id, $sessionId);
 			}
 		}
 		return $new_id;
@@ -1017,7 +1036,7 @@ class CourseRestorer
 	 * Restore a forum-post
 	 * @TODO Restore tree-structure of posts. For example: attachments to posts.
 	 */
-    public function restore_post($id, $topic_id, $forum_id)
+    public function restore_post($id, $topic_id, $forum_id, $sessionId = 0)
     {
 		$table_post = Database :: get_course_table(TABLE_FORUM_POST);
 		$post = $this->course->resources[RESOURCE_FORUMPOST][$id];
@@ -1028,9 +1047,26 @@ class CourseRestorer
         $params['poster_id'] = $this->first_teacher_id;
         $params['post_date'] = api_get_utc_datetime();
         unset($params['post_id']);
-        $params['post_text']    = DocumentManager::replace_urls_inside_content_html_from_copy_course($params['post_text'], $this->course->code, $this->course->destination_path, $this->course->backup_path, $this->course->info['path']);
+        $params['post_text'] = DocumentManager::replace_urls_inside_content_html_from_copy_course(
+            $params['post_text'],
+            $this->course->code,
+            $this->course->destination_path,
+            $this->course->backup_path,
+            $this->course->info['path']
+        );
         $new_id = Database::insert($table_post, $params);
-        api_item_property_update($this->destination_course_info, TOOL_FORUM_POST, $new_id, 'PostAdded', api_get_user_id(), 0, 0, null, null);
+        api_item_property_update(
+            $this->destination_course_info,
+            TOOL_FORUM_POST,
+            $new_id,
+            'PostAdded',
+            api_get_user_id(),
+            0,
+            0,
+            null,
+            null,
+            $sessionId
+        );
 		$this->course->resources[RESOURCE_FORUMPOST][$id]->destination_id = $new_id;
 		return $new_id;
 	}
@@ -1045,7 +1081,8 @@ class CourseRestorer
 			$resources = $this->course->resources;
 			foreach ($resources[RESOURCE_LINK] as $id => $link) {
 				$cat_id = $this->restore_link_category($link->category_id, $session_id);
-				$sql = "SELECT MAX(display_order) FROM  $link_table WHERE c_id = ".$this->destination_course_id."  AND category_id='" . self::DBUTF8escapestring($cat_id). "'";
+				$sql = "SELECT MAX(display_order) FROM  $link_table
+				        WHERE c_id = ".$this->destination_course_id." AND category_id='" . self::DBUTF8escapestring($cat_id). "'";
 				$result = Database::query($sql);
     			list($max_order) = Database::fetch_array($result);
 
@@ -1055,13 +1092,13 @@ class CourseRestorer
     			}
 
 				$sql = "INSERT INTO ".$link_table." SET
-				            c_id            = ".$this->destination_course_id." ,
-				            url             = '".self::DBUTF8escapestring($link->url)."',
-				            title           = '".self::DBUTF8escapestring($link->title)."',
-				            description     = '".self::DBUTF8escapestring($link->description)."',
-				            category_id     = '".$cat_id."',
-				            on_homepage     = '".$link->on_homepage."',
-				            display_order   = '".($max_order+1)."' $condition_session";
+                        c_id            = ".$this->destination_course_id." ,
+                        url             = '".self::DBUTF8escapestring($link->url)."',
+                        title           = '".self::DBUTF8escapestring($link->title)."',
+                        description     = '".self::DBUTF8escapestring($link->description)."',
+                        category_id     = '".$cat_id."',
+                        on_homepage     = '".$link->on_homepage."',
+                        display_order   = '".($max_order+1)."' $condition_session";
 
 				Database::query($sql);
 				$this->course->resources[RESOURCE_LINK][$id]->destination_id = Database::insert_id();
@@ -2632,14 +2669,15 @@ class CourseRestorer
                                     api_get_user_id(),
                                     $this->destination_course_info,
                                     0,
-                                    0
+                                    $sessionId
                                 );
                             } else {
                                 $workId = $workData['id'];
                                 updateWork(
                                     $workId,
                                     $obj->params,
-                                    $this->destination_course_info
+                                    $this->destination_course_info,
+                                    $sessionId
                                 );
                                 updatePublicationAssignment(
                                     $workId,

+ 2 - 2
main/cron/create_course_sessions.php

@@ -80,9 +80,9 @@ if (!$lastingAdministrators) {
 $administratorId = intval($administrators[$lastingAdministrators - 1]['user_id']);
 
 // Creates course sessions for the current month
-$dates = getMonthFirstAndLastDates();
+$dates = getMonthFirstAndLastDates(date('Y-m-').'01');
 // Get courses that don't have any session
-$courses = CourseManager::getCoursesWithoutSession();
+$courses = CourseManager::getCoursesWithoutSession($dates['startDate'], $dates['endDate']);
 createCourseSessions($courses, $administratorId, $dates['startDate'], $dates['endDate']);
 
 // Creates course sessions for the following month

+ 132 - 0
main/cron/remind_course_expiration.php

@@ -0,0 +1,132 @@
+<?php
+/* For licensing terms, see /license.txt */
+/**
+ * Course expiration reminder.
+ * @package chamilo.cron
+ * @author Imanol Losada <imanol.losada@beeznest.com>
+ */
+require_once __DIR__ . '/../inc/global.inc.php';
+
+/**
+ * Initialization
+ */
+if (php_sapi_name() != 'cli') {
+    exit; //do not run from browser
+}
+
+// Days before expiration date to send reminders
+define("OFFSET", 2);
+$today = gmdate("Y-m-d");
+$expirationDate = gmdate("Y-m-d", strtotime($today." + ".OFFSET." day"));
+
+$query = "SELECT DISTINCT category.session_id, certificate.user_id FROM ".
+    Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY)." AS category
+    LEFT JOIN ".Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE).
+    " AS certificate ON category.id = certificate.cat_id
+    INNER JOIN ".Database::get_main_table(TABLE_MAIN_SESSION).
+    " AS session ON category.session_id = session.id
+    WHERE session.date_end BETWEEN '$today' AND '$expirationDate' AND category.session_id IS NOT NULL";
+
+$sessionId = 0;
+$userIds = array();
+$sessions = array();
+$result = Database::query($query);
+
+while ($row = Database::fetch_array($result)) {
+    if ($sessionId != $row['session_id']) {
+        $sessionId = $row['session_id'];
+        $userIds = array();
+    }
+    if (!is_null($row['user_id'])) {
+        array_push($userIds, $row['user_id']);
+    }
+    $sessions[$sessionId] = $userIds;
+}
+
+$usersToBeReminded = array();
+
+foreach ($sessions as $sessionId => $userIds) {
+    $userId = 0;
+    $userIds = $userIds ? " AND id_user NOT IN (".implode(",", $userIds).")" : null;
+    $query = "SELECT sessionUser.id_session, sessionUser.id_user, session.name, session.date_end FROM ".
+        Database::get_main_table(TABLE_MAIN_SESSION_USER)." AS sessionUser
+        INNER JOIN ".Database::get_main_table(TABLE_MAIN_SESSION).
+        " AS session ON sessionUser.id_session = session.id
+        WHERE id_session = $sessionId$userIds";
+    $result = Database::query($query);
+    while ($row = Database::fetch_array($result)) {
+        $usersToBeReminded[$row['id_user']][$row['id_session']] =
+            array(
+                'name' => $row['name'],
+                'date_end' => $row['date_end']
+            );
+    }
+}
+
+if ($usersToBeReminded) {
+    $today = date_create($today);
+    $platformLanguage = api_get_setting("platformLanguage");
+    $subject = sprintf(
+        get_lang("MailCronCourseExpirationReminderSubject", null, $platformLanguage),
+        api_get_setting("Institution")
+    );
+    $administrator = array(
+        'completeName' => api_get_person_name(
+            api_get_setting("administratorName"),
+            api_get_setting("administratorSurname"),
+            null,
+            PERSON_NAME_EMAIL_ADDRESS
+        ),
+        'email' => api_get_setting("emailAdministrator")
+    );
+    echo "\n======================================================================\n\n";
+    foreach ($usersToBeReminded as $userId => $sessions) {
+        $user = api_get_user_info($userId);
+        $userCompleteName = api_get_person_name(
+            $user['firstname'],
+            $user['lastname'],
+            null,
+            PERSON_NAME_EMAIL_ADDRESS
+        );
+        foreach ($sessions as $sessionId => $session) {
+            $daysRemaining = date_diff($today, date_create($session['date_end']));
+            $join = " INNER JOIN ".Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION)."ON id = access_url_id";
+            $result = Database::select(
+                'url',
+                Database::get_main_table(TABLE_MAIN_ACCESS_URL).$join,
+                array(
+                    'where' => array(
+                        'session_id = ?' => array(
+                            $sessionId
+                        )
+                    ),
+                    'limit' => '1'
+                )
+            );
+            $body = sprintf(
+                get_lang('MailCronCourseExpirationReminderBody', null, $platformLanguage),
+                $userCompleteName,
+                $session['name'],
+                $session['date_end'],
+                $daysRemaining->format("%d"),
+                $result[0]['url'],
+                api_get_setting("siteName")
+            );
+            api_mail_html(
+                $userCompleteName,
+                $user['email'],
+                $subject,
+                $body,
+                $administrator['completeName'],
+                $administrator['email']
+            );
+            echo "Email sent to $userCompleteName (".$user['email'].")\n";
+            echo "Session: ".$session['name']."\n";
+            echo "Date end: ".$session['date_end']."\n";
+            echo "Days remaining: ".$daysRemaining->format("%d")."\n\n";
+        }
+        echo "======================================================================\n\n";
+    }
+} else {
+    echo "No users to be reminded\n";
+}

+ 5 - 5
main/gradebook/gradebook_flatview.php

@@ -97,6 +97,11 @@ if (!empty($keyword)) {
 }
 $offset = isset($_GET['offset']) ? $_GET['offset'] : '0';
 
+$addparams = array('selectcat' => $cat[0]->get_id());
+if (isset($_GET['search'])) {
+    $addparams['search'] = $keyword;
+}
+
 // Main course category
 $mainCourseCategory = Category::load(
     null,
@@ -237,11 +242,6 @@ if (!empty($_GET['export_report']) && $_GET['export_report'] == 'export_report')
     }
 }
 
-$addparams = array ('selectcat' => $cat[0]->get_id());
-if (isset($_GET['search'])) {
-    $addparams['search'] = $keyword;
-}
-
 $this_section = SECTION_COURSES;
 
 if (isset($_GET['exportpdf'])) {

+ 1 - 2
main/gradebook/gradebook_view_result.php

@@ -85,7 +85,7 @@ if (isset($_GET['editres'])) {
         exit;
     }
 }
-
+$file_type = null;
 if (isset($_GET['import'])) {
     $interbreadcrumb[] = array('url' => 'gradebook_view_result.php?selecteval=' . Security::remove_XSS($_GET['selecteval']), 'name' => get_lang('ViewResult'));
     $import_result_form = new DataForm(
@@ -101,7 +101,6 @@ if (isset($_GET['import'])) {
     }
 
     $eval[0]->check_lock_permissions();
-
     if ($_POST['formSent']) {
         if (!empty($_FILES['import_file']['name'])) {
             $values = $import_result_form->exportValues();

+ 1 - 1
main/gradebook/index.php

@@ -6,7 +6,7 @@
  * @package chamilo.gradebook
  */
 
-$language_file = array('gradebook', 'exercice');
+$language_file = array('gradebook', 'exercice', 'tracking');
 
 // $cidReset : This is the main difference with gradebook.php, here we say,
 // basically, that we are inside a course, and many things depend from that

+ 25 - 0
main/gradebook/lib/be/abstractlink.class.php

@@ -521,4 +521,29 @@ abstract class AbstractLink implements GradebookItem
         $sql = "UPDATE $table SET locked = '".intval($locked)."' WHERE id='".$this->id."'";
         Database::query($sql);
     }
+
+    /**
+     * Get current user ranking
+     * @param array $studentList Array with user id and scores
+     * Example: [1 => 5.00, 2 => 8.00]
+     */
+    public static function getCurrentUserRanking($studentList)
+    {
+        $ranking = null;
+        $currentUserId = api_get_user_id();
+        if (!empty($studentList) && !empty($currentUserId)) {
+            asort($studentList);
+            $ranking = $count = count($studentList);
+
+            foreach ($studentList as $userId => $position) {
+                if ($currentUserId == $userId) {
+                    break;
+                }
+                $ranking--;
+            }
+            return array($ranking, $count);
+        }
+
+        return array();
+    }
 }

+ 42 - 21
main/gradebook/lib/be/attendancelink.class.php

@@ -131,7 +131,10 @@ class AttendanceLink extends AbstractLink
 
 		// get attendance qualify max
 		$sql = 'SELECT att.attendance_qualify_max FROM '.$this->get_attendance_table().' att
-  				WHERE att.c_id = '.$this->course_id.' AND att.id = '.intval($this->get_ref_id()).' AND att.session_id='.intval($session_id).'';
+  				WHERE
+  					att.c_id = '.$this->course_id.' AND
+  					att.id = '.intval($this->get_ref_id()).' AND
+  					att.session_id='.intval($session_id).'';
 		$query = Database::query($sql);
 		$attendance = Database::fetch_array($query);
 
@@ -175,23 +178,29 @@ class AttendanceLink extends AbstractLink
 			if ($rescount == 0) {
 				return null;
 			} else {
-				if ($type == 'best') {
-					return array($bestResult, $weight);
+				switch ($type) {
+					case 'best':
+						return array($bestResult, $weight);
+						break;
+					case 'average':
+						return array($sumResult / $rescount, $weight);
+						break;
+					case 'ranking':
+						return AbstractLink::getCurrentUserRanking($students);
+						break;
+					default:
+						return array($sum, $rescount);
+						break;
 				}
-				if ($type == 'average') {
-					return array($sumResult/$rescount, $weight);
-				}
-				return array($sum , $rescount);
 			}
 		}
 	}
 
-	// INTERNAL FUNCTIONS
-
 	/**
 	 * Lazy load function to get the database table of the student publications
 	 */
-	private function get_attendance_table() {
+	private function get_attendance_table()
+	{
 		$this->attendance_table = Database :: get_course_table(TABLE_ATTENDANCE);
 		return $this->attendance_table;
 	}
@@ -199,19 +208,24 @@ class AttendanceLink extends AbstractLink
 	/**
 	 * Lazy load function to get the database table of the item properties
 	 */
-	private function get_itemprop_table () {
+	private function get_itemprop_table()
+	{
 		$this->itemprop_table = Database :: get_course_table(TABLE_ITEM_PROPERTY);
 		return $this->itemprop_table;
 	}
 
-	public function needs_name_and_description() {
+	public function needs_name_and_description()
+	{
 		return false;
 	}
-	public function needs_max() {
+
+	public function needs_max()
+	{
 		return false;
 	}
 
-	public function needs_results() {
+	public function needs_results()
+	{
 		return false;
 	}
 
@@ -227,13 +241,16 @@ class AttendanceLink extends AbstractLink
 		}
 	}
 
-	public function get_description() {
+	public function get_description()
+	{
 		return '';
 	}
+
 	/**
 	 * Check if this still links to an exercise
 	 */
-	public function is_valid_link() {
+	public function is_valid_link()
+	{
 		$session_id = api_get_session_id();
 		$sql = 'SELECT count(att.id) FROM '.$this->get_attendance_table().' att
         		 WHERE att.c_id = '.$this->course_id.' AND att.id = '.intval($this->get_ref_id()).' ';
@@ -242,23 +259,26 @@ class AttendanceLink extends AbstractLink
 		return ($number[0] != 0);
 	}
 
-	public function get_test_id() {
+	public function get_test_id()
+	{
 		return 'DEBUG:ID';
 	}
 
-	public function get_link() {
+	public function get_link()
+	{
 		//it was extracts the attendance id
 		$session_id = api_get_session_id();
 		$sql = 'SELECT * FROM '.$this->get_attendance_table().' att
     			WHERE att.c_id = '.$this->course_id.' AND att.id = '.intval($this->get_ref_id()).' ';
 		$result = Database::query($sql);
-		$row    = Database::fetch_array($result,'ASSOC');
+		$row = Database::fetch_array($result,'ASSOC');
 		$attendance_id = $row['id'];
 		$url = api_get_path(WEB_PATH).'main/attendance/index.php?action=attendance_sheet_list&gradebook=view&attendance_id='.$attendance_id.'&session_id='.$session_id.'&cidReq='.$this->get_course_code();
 		return $url;
 	}
 
-	private function get_attendance_data() {
+	private function get_attendance_data()
+	{
 		$tbl_name = $this->get_attendance_table();
 		$session_id = api_get_session_id();
 		if ($tbl_name == '') {
@@ -272,7 +292,8 @@ class AttendanceLink extends AbstractLink
 		return $this->attendance_data;
 	}
 
-	public function get_icon_name() {
+	public function get_icon_name()
+	{
 		return 'attendance';
 	}
 }

+ 158 - 45
main/gradebook/lib/be/category.class.php

@@ -22,30 +22,48 @@ class Category implements GradebookItem
     private $grade_model_id;
     private $generateCertificates;
 
+    /**
+     * Consctructor
+     */
     public function __construct()
     {
     }
 
+    /**
+     * @return int
+     */
     public function get_id()
     {
         return $this->id;
     }
 
+    /**
+     * @return string
+     */
     public function get_name()
     {
         return $this->name;
     }
 
+    /**
+     * @return string
+     */
     public function get_description()
     {
         return $this->description;
     }
 
+    /**
+     * @return int
+     */
     public function get_user_id()
     {
         return $this->user_id;
     }
 
+    /**
+     * @return float
+     */
     public function get_certificate_min_score()
     {
         if (!empty($this->certificate_min_score)) {
@@ -63,56 +81,89 @@ class Category implements GradebookItem
         return $this->course_code;
     }
 
+    /**
+     * @return mixed
+     */
     public function get_parent_id()
     {
         return $this->parent;
     }
 
+    /**
+     * @return mixed
+     */
     public function get_weight()
     {
         return $this->weight;
     }
 
+    /**
+     * @return bool
+     */
     public function is_locked()
     {
         return isset($this->locked) && $this->locked == 1 ? true : false;
     }
 
+    /**
+     * @return mixed
+     */
     public function is_visible()
     {
         return $this->visible;
     }
 
+    /**
+     * @param int $id
+     */
     public function set_id($id)
     {
         $this->id = $id;
     }
 
+    /**
+     * @param string $name
+     */
     public function set_name($name)
     {
         $this->name = $name;
     }
 
+    /**
+     * @param string $description
+     */
     public function set_description($description)
     {
         $this->description = $description;
     }
 
+    /**
+     * @param int $user_id
+     */
     public function set_user_id($user_id)
     {
         $this->user_id = $user_id;
     }
 
+    /**
+     * @param string $course_code
+     */
     public function set_course_code($course_code)
     {
         $this->course_code = $course_code;
     }
 
-    public function set_certificate_min_score ($min_score = null)
+    /**
+     * @param float $min_score
+     */
+    public function set_certificate_min_score($min_score = null)
     {
         $this->certificate_min_score = $min_score;
     }
 
+    /**
+     * @param int $parent
+     */
     public function set_parent_id($parent)
     {
         $this->parent = intval($parent);
@@ -157,6 +208,10 @@ class Category implements GradebookItem
         return 'category';
     }
 
+    /**
+     * @param bool $from_db
+     * @return array|resource
+     */
     public function get_skills($from_db = true)
     {
         if ($from_db) {
@@ -170,6 +225,9 @@ class Category implements GradebookItem
         return $skills;
     }
 
+    /**
+     * @return array
+     */
     public function get_skills_for_select()
     {
         $skills = $this->get_skills();
@@ -249,7 +307,8 @@ class Category implements GradebookItem
      * @param int      $parent_id parent category
      * @param bool     $visible
      * @param int      $session_id (in case we are in a session)
-     * @param bool     $order_by Whether to show all "session" categories (true) or hide them (false) in case there is no session id
+     * @param bool     $order_by Whether to show all "session"
+     * categories (true) or hide them (false) in case there is no session id
      */
     public static function load(
         $id = null,
@@ -522,7 +581,9 @@ class Category implements GradebookItem
     {
         $tbl_grade_categories = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
 
-        $sql = 'UPDATE '.$tbl_grade_categories." SET name = '".Database::escape_string($this->get_name())."'".', description = ';
+        $sql = 'UPDATE '.$tbl_grade_categories." SET
+                name = '".Database::escape_string($this->get_name())."'".',
+                description = ';
         if (isset($this->description)) {
             $sql .= "'".Database::escape_string($this->get_description())."'";
         } else {
@@ -604,7 +665,7 @@ class Category implements GradebookItem
      * Update link weights see #5168
      * @param type $new_weight
      */
-    function update_children_weight($new_weight)
+    public function update_children_weight($new_weight)
     {
         $links = $this->get_links();
         $old_weight = $this->get_weight();
@@ -636,7 +697,8 @@ class Category implements GradebookItem
     public function update_category_delete($course_id)
     {
         $tbl_grade_categories = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
-        $sql = 'UPDATE '.$tbl_grade_categories.' SET visible=3 WHERE course_code ="'.Database::escape_string($course_id).'"';
+        $sql = 'UPDATE '.$tbl_grade_categories.' SET visible=3
+                WHERE course_code ="'.Database::escape_string($course_id).'"';
         Database::query($sql);
     }
 
@@ -668,7 +730,9 @@ class Category implements GradebookItem
             return null;
         } else {
             $tbl_category = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
-            $sql='SELECT name,description,user_id,course_code,parent_id,weight,visible,certif_min_score,session_id, generate_certificates FROM '.$tbl_category.' c WHERE c.id='.intval($selectcat);
+            $sql = 'SELECT name,description,user_id,course_code,parent_id,weight,visible,certif_min_score,session_id, generate_certificates
+                    FROM '.$tbl_category.' c
+                    WHERE c.id='.intval($selectcat);
             $result       = Database::query($sql);
             $row          = Database::fetch_array($result, 'ASSOC');
 
@@ -727,7 +791,7 @@ class Category implements GradebookItem
      */
     public function is_certificate_available($user_id)
     {
-        $score = $this->calc_score($user_id, $this->course_code);
+        $score = $this->calc_score($user_id, null, $this->course_code);
 
         if (isset($score) && isset($score[0])) {
             // Get a percentage score to compare to minimum certificate score
@@ -760,8 +824,12 @@ class Category implements GradebookItem
      * @return    array (score sum, weight sum)
      *             or null if no scores available
      */
-    public function calc_score($stud_id = null, $course_code = '', $session_id = null)
-    {
+    public function calc_score(
+        $stud_id = null,
+        $type = null,
+        $course_code = '',
+        $session_id = null
+    ) {
         // Get appropriate subcategories, evaluations and links
         if (!empty($course_code)) {
             $cats  = $this->get_subcategories($stud_id, $course_code, $session_id);
@@ -773,22 +841,32 @@ class Category implements GradebookItem
             $links = $this->get_links($stud_id);
         }
 
-        // calculate score
-        $rescount  = 0;
-        $ressum    = 0;
+        // Calculate score
+        $rescount = 0;
+        $ressum = 0;
         $weightsum = 0;
+        $bestResult = 0;
+        $students = [];
 
         if (!empty($cats)) {
             /** @var Category $cat */
             foreach ($cats as $cat) {
-                $catres = $cat->calc_score($stud_id, $course_code, $session_id);
+                $score = $cat->calc_score(
+                    $stud_id,
+                    $type,
+                    $course_code,
+                    $session_id
+                );
+
                 if ($cat->get_weight() != 0) {
                     $catweight = $cat->get_weight();
                     $rescount++;
                     $weightsum += $catweight;
                 }
-                if (isset($catres)) {
-                    $ressum += (($catres[0]/$catres[1]) * $catweight);
+
+                if (isset($score)) {
+                    $ressum += $score[0]/$score[1] * $catweight;
+                    $bestResult += $ressum;
                 }
             }
         }
@@ -796,13 +874,16 @@ class Category implements GradebookItem
         if (!empty($evals)) {
             /** @var Evaluation $eval */
             foreach ($evals as $eval) {
-                $evalres = $eval->calc_score($stud_id);
-
+                $evalres = $eval->calc_score($stud_id, $type);
+                if ($type == 'ranking') {
+                    var_dump($evalres);
+                }
+                $students[$stud_id] = $evalres[0];
                 if (isset($evalres) && $eval->get_weight() != 0) {
                     $evalweight = $eval->get_weight();
                     $weightsum += $evalweight;
                     $rescount++;
-                    $ressum += (($evalres[0]/$evalres[1]) * $evalweight);
+                    $ressum += $evalres[0] / $evalres[1] * $evalweight;
                 } else {
                     if ($eval->get_weight() != 0) {
                         $evalweight = $eval->get_weight();
@@ -813,15 +894,20 @@ class Category implements GradebookItem
         }
 
         if (!empty($links)) {
+
+            $bestResult = 0;
+            $weight = 0;
+
             /** @var EvalLink|ExerciseLink $link */
-            foreach ($links as $link) {
-                $linkres = $link->calc_score($stud_id);
+           foreach ($links as $link) {
+                $linkres = $link->calc_score($stud_id, $type);
                 if (!empty($linkres) && $link->get_weight() != 0) {
+                    $students[$stud_id] = $linkres[0];
                     $linkweight = $link->get_weight();
                     $link_res_denom = $linkres[1] == 0 ? 1 : $linkres[1];
                     $rescount++;
                     $weightsum += $linkweight;
-                    $ressum += ($linkres[0] / $link_res_denom) * $linkweight;
+                    $ressum += $linkres[0] / $link_res_denom * $linkweight;
                 } else {
                     // Adding if result does not exists
                     if ($link->get_weight() != 0) {
@@ -835,7 +921,22 @@ class Category implements GradebookItem
         if ($rescount == 0) {
             return null;
         } else {
-            return array($ressum, $weightsum);
+            switch ($type) {
+                case 'best':
+                    return array($ressum, $weightsum);
+                    break;
+                case 'average':
+                    return array($ressum, $weightsum);
+                    break;
+                case 'ranking':
+                    //var_dump($students);
+                    return null;
+                    return AbstractLink::getCurrentUserRanking($students);
+                    break;
+                default:
+                    return array($ressum, $weightsum);
+                    break;
+            }
         }
     }
 
@@ -872,7 +973,7 @@ class Category implements GradebookItem
      * @param string    Course code
      * @param int       Session id
      */
-    public function get_root_categories_for_student ($stud_id, $course_code = null, $session_id = null)
+    public function get_root_categories_for_student($stud_id, $course_code = null, $session_id = null)
     {
         $main_course_user_table = Database :: get_main_table(TABLE_MAIN_COURSE_USER);
         $tbl_grade_categories = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
@@ -1063,7 +1164,7 @@ class Category implements GradebookItem
      */
     private function can_be_moved_to_cat ($cat)
     {
-        return ($cat->get_id() != $this->get_id());
+        return $cat->get_id() != $this->get_id();
     }
 
     /**
@@ -1073,7 +1174,7 @@ class Category implements GradebookItem
      * of all underlying categories and evaluations. All links will
      * be deleted as well !
      */
-    public function move_to_cat ($cat)
+    public function move_to_cat($cat)
     {
         $this->set_parent_id($cat->get_id());
         if ($this->get_course_code() != $cat->get_course_code()) {
@@ -1086,7 +1187,7 @@ class Category implements GradebookItem
     /**
      * Internal function used by move_to_cat()
      */
-    private function apply_course_code_to_children ()
+    private function apply_course_code_to_children()
     {
         $cats = Category::load(null, null, null, $this->id, null);
         $evals = Evaluation::load(null, null, null, $this->id, null);
@@ -1305,11 +1406,12 @@ class Category implements GradebookItem
 
     /**
      * Get appropriate subcategories visible for the user (and optionally the course and session)
-     * @param int      $stud_id student id (default: all students)
-     * @param string   Course code (optional)
-     * @param int      Session ID (optional)
-     *
-     * @return array   Array of subcategories
+     * @param int    $stud_id student id (default: all students)
+     * @param string $course_code Course code (optional)
+     * @param int    $session_id Session ID (optional)
+     * @param bool   $order
+
+     * @return array Array of subcategories
      */
     public function get_subcategories($stud_id = null, $course_code = null, $session_id = null, $order = null)
     {
@@ -1374,8 +1476,11 @@ class Category implements GradebookItem
      *
      * @return array
      */
-    public function get_evaluations($stud_id = null, $recursive = false, $course_code = '')
-    {
+    public function get_evaluations(
+        $stud_id = null,
+        $recursive = false,
+        $course_code = ''
+    ) {
         $evals = array();
 
         if (empty($course_code)) {
@@ -1384,14 +1489,14 @@ class Category implements GradebookItem
 
         // 1 student
         if (isset($stud_id) && !empty($stud_id)) {
-            // special case: this is the root
+            // Special case: this is the root
             if ($this->id == 0) {
                 $evals = Evaluation::get_evaluations_with_result_for_student(0, $stud_id);
             } else {
                 $evals = Evaluation::load(null,null, $course_code, $this->id, api_is_allowed_to_edit() ? null : 1);
             }
         } else {
-            // all students
+            // All students
             // course admin
             if ((api_is_allowed_to_edit() || api_is_drh() || api_is_session_admin()) && !api_is_platform_admin()) {
                 // root
@@ -1404,8 +1509,7 @@ class Category implements GradebookItem
                     // course independent
                     $evals = Evaluation::load(null, api_get_user_id(), null, $this->id, null);
                 }
-            } elseif (api_is_platform_admin()) {
-                //platform admin
+            } else {
                 $evals = Evaluation::load(null, null, $course_code, $this->id, null);
             }
         }
@@ -1433,8 +1537,12 @@ class Category implements GradebookItem
      *
      * @return array
      */
-    public function get_links($stud_id = null, $recursive = false, $course_code = '', $sessionId = null)
-    {
+    public function get_links(
+        $stud_id = null,
+        $recursive = false,
+        $course_code = '',
+        $sessionId = null
+    ) {
         $links = array();
 
         if (empty($course_code)) {
@@ -1458,8 +1566,9 @@ class Category implements GradebookItem
                 $this->id,
                 api_is_allowed_to_edit() ? null : 1
             );
-        } elseif (api_is_allowed_to_edit() || api_is_drh() || api_is_session_admin()) {
-            // all students -> only for course/platform admin
+            //} elseif (api_is_allowed_to_edit() || api_is_drh() || api_is_session_admin()) {
+        } else {
+            // All students -> only for course/platform admin
             $links = LinkFactory::load(
                 null,
                 null,
@@ -1502,7 +1611,8 @@ class Category implements GradebookItem
     public function getCategories($catId)
     {
         $tblGradeCategories = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
-        $sql='SELECT * FROM '.$tblGradeCategories.' WHERE parent_id = '.intval($catId);
+        $sql = 'SELECT * FROM '.$tblGradeCategories.'
+                WHERE parent_id = '.intval($catId);
 
         $result = Database::query($sql);
         $allcats = Category::create_category_objects_from_sql_result($result);
@@ -1561,7 +1671,8 @@ class Category implements GradebookItem
     }
 
     /**
-     * This function, locks a category , only one who can unlock it is the platform administrator.
+     * This function, locks a category , only one who can unlock it is
+     * the platform administrator.
      * @param int locked 1 or unlocked 0
 
      * @return bool
@@ -1569,7 +1680,8 @@ class Category implements GradebookItem
     public function lock($locked)
     {
         $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
-        $sql = "UPDATE $table SET locked = '".intval($locked)."' WHERE id='".intval($this->id)."'";
+        $sql = "UPDATE $table SET locked = '".intval($locked)."'
+                WHERE id='".intval($this->id)."'";
         Database::query($sql);
     }
 
@@ -1673,7 +1785,8 @@ class Category implements GradebookItem
         $cattotal = Category::load($category_id);
         $scoretotal = $cattotal[0]->calc_score($user_id);
 
-        //Do not remove this the gradebook/lib/fe/gradebooktable.class.php file load this variable as a global
+        // Do not remove this the gradebook/lib/fe/gradebooktable.class.php
+        // file load this variable as a global
         $scoredisplay = ScoreDisplay::instance();
         $my_score_in_gradebook = $scoredisplay->display_score($scoretotal, SCORE_SIMPLE);
 

+ 32 - 13
main/gradebook/lib/be/evaluation.class.php

@@ -268,7 +268,12 @@ class Evaluation implements GradebookItem
 	 */
 	public function add()
 	{
-		if (isset($this->name) && isset($this->user_id) && isset($this->weight) && isset ($this->eval_max) && isset($this->visible)) {
+		if (isset($this->name) &&
+			isset($this->user_id) &&
+			isset($this->weight) &&
+			isset ($this->eval_max) &&
+			isset($this->visible)
+		) {
 			$tbl_grade_evaluations = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_EVALUATION);
 
 			$sql = 'INSERT INTO '.$tbl_grade_evaluations
@@ -424,14 +429,15 @@ class Evaluation implements GradebookItem
 		}
 		$result = Database::query($sql);
 		$number=Database::fetch_row($result);
-		return ($number[0] != 0);
+		return $number[0] != 0;
 	}
 
 	/**
 	 * Are there any results for this evaluation yet ?
 	 * The 'max' property should not be changed then.
 	 */
-	public function has_results() {
+	public function has_results()
+	{
 		$tbl_grade_results = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_RESULT);
 		$sql='SELECT count(id) AS number FROM '.$tbl_grade_results
 			.' WHERE evaluation_id = '.intval($this->id);
@@ -444,7 +450,8 @@ class Evaluation implements GradebookItem
 	/**
 	 * Delete all results for this evaluation
 	 */
-	public function delete_results() {
+	public function delete_results()
+	{
 		$tbl_grade_results = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_RESULT);
 		$sql = 'DELETE FROM '.$tbl_grade_results.' WHERE evaluation_id = '.intval($this->id);
 		Database::query($sql);
@@ -453,7 +460,8 @@ class Evaluation implements GradebookItem
 	/**
 	 * Delete this evaluation and all underlying results.
 	 */
-	public function delete_with_results(){
+	public function delete_with_results()
+	{
 		$this->delete_results();
 		$this->delete();
 	}
@@ -461,7 +469,8 @@ class Evaluation implements GradebookItem
 	/**
 	 * Check if the given score is possible for this evaluation
 	 */
-	public function is_valid_score ($score) {
+	public function is_valid_score ($score)
+	{
 		return (is_numeric($score) && $score >= 0 && $score <= $this->eval_max);
 	}
 
@@ -481,9 +490,11 @@ class Evaluation implements GradebookItem
 		$weight = 0;
 		$sumResult = 0;
 
+		$students = [];
+		/** @var Result $res */
 		foreach ($results as $res) {
 			$score = $res->get_score();
-			if ((!empty ($score)) || ($score == '0')) {
+			if (!empty($score) || $score == '0') {
 				$rescount++;
 				$sum += $score / $this->get_max();
 				$sumResult += $score;
@@ -492,6 +503,7 @@ class Evaluation implements GradebookItem
 				}
 				$weight = $this->get_max();
 			}
+			$students[$res->get_user_id()] = $score;
 		}
 
 		if ($rescount == 0) {
@@ -499,13 +511,20 @@ class Evaluation implements GradebookItem
 		} else if (isset($stud_id)) {
 			return array($score, $this->get_max());
 		} else {
-			if ($type == 'best') {
-				return array($bestResult, $weight);
-			}
-			if ($type == 'average') {
-				return array($sumResult/$rescount, $weight);
+			switch ($type) {
+				case 'best':
+					return array($bestResult, $weight);
+					break;
+				case 'average':
+					return array($sumResult/$rescount, $weight);
+					break;
+				case 'ranking':
+					return AbstractLink::getCurrentUserRanking($students);
+					break;
+				default:
+					return array($sum, $rescount);
+					break;
 			}
-			return array($sum, $rescount);
 		}
 	}
 

+ 22 - 12
main/gradebook/lib/be/exerciselink.class.php

@@ -194,13 +194,14 @@ class ExerciseLink extends AbstractLink
         in exercice/exercice.php, look for note-query-exe-results marker*/
         $session_id = api_get_session_id();
         $courseId = $this->getCourseId();
+
         if (!$this->is_hp) {
             $sql = "SELECT * FROM $tblStats
                     WHERE
-                        exe_exo_id      = ".intval($this->get_ref_id())." AND
-                        orig_lp_id      = 0 AND
+                        exe_exo_id = ".intval($this->get_ref_id())." AND
+                        orig_lp_id = 0 AND
                         orig_lp_item_id = 0 AND
-                        status      <> 'incomplete' AND
+                        status <> 'incomplete' AND
                         session_id = $session_id";
 
             if (isset($stud_id)) {
@@ -210,7 +211,7 @@ class ExerciseLink extends AbstractLink
 
         } else {
             $sql = "SELECT * FROM $tblHp hp, $tblDoc doc
-                     WHERE
+                    WHERE
                         hp.c_id = $courseId AND
                         hp.exe_user_id = $stud_id  AND
                         hp.exe_name = doc.path AND
@@ -237,10 +238,11 @@ class ExerciseLink extends AbstractLink
             $bestResult = 0;
             $weight = 0;
             $sumResult = 0;
+
             while ($data = Database::fetch_array($scores, 'ASSOC')) {
-                if (!in_array($data['exe_user_id'], $students)) {
+                if (!isset($students[$data['exe_user_id']])) {
                     if ($data['exe_weighting'] != 0) {
-                        $students[] = $data['exe_user_id'];
+                        $students[$data['exe_user_id']] = $data['exe_result'];
                         $student_count++;
                         if ($data['exe_result'] > $bestResult) {
                             $bestResult = $data['exe_result'];
@@ -251,16 +253,24 @@ class ExerciseLink extends AbstractLink
                     }
                 }
             }
+
             if ($student_count == 0) {
                 return null;
             } else {
-                if ($type == 'best') {
-                    return array($bestResult, $weight);
-                }
-                if ($type == 'average') {
-                    return array($sumResult/$student_count, $weight);
+                switch ($type) {
+                    case 'best':
+                        return array($bestResult, $weight);
+                        break;
+                    case 'average':
+                        return array($sumResult/$student_count, $weight);
+                        break;
+                    case 'ranking':
+                        return AbstractLink::getCurrentUserRanking($students);
+                        break;
+                    default:
+                        return array($sum, $student_count);
+                        break;
                 }
-                return array($sum, $student_count);
             }
         }
     }

+ 23 - 13
main/gradebook/lib/be/forumthreadlink.class.php

@@ -1,5 +1,6 @@
 <?php
 /* For licensing terms, see /license.txt */
+
 /**
  * Gradebook link to student publication item
  * @author Bert Steppé
@@ -51,12 +52,14 @@ class ForumThreadLink extends AbstractLink
 
 		$result = Database::query($sql);
 
-		$cats=array();
+		$cats = array();
 		while ($data=Database::fetch_array($result)) {
-			if ( isset($data['thread_title_qualify']) and $data['thread_title_qualify']!=""){
-				$cats[] = array ($data['thread_id'], $data['thread_title_qualify']);
+			if (isset($data['thread_title_qualify']) and
+				$data['thread_title_qualify'] != ""
+			) {
+				$cats[] = array($data['thread_id'], $data['thread_title_qualify']);
 			} else {
-				$cats[] = array ($data['thread_id'], $data['thread_title']);
+				$cats[] = array($data['thread_id'], $data['thread_title']);
 			}
 		}
 		return $cats;
@@ -71,6 +74,7 @@ class ForumThreadLink extends AbstractLink
 		if (empty($this->course_code)) {
 			die('Error in get_not_created_links() : course code not set');
 		}
+
 		$tbl_grade_links 	= Database :: get_course_table(TABLE_FORUM_THREAD);
 		$tbl_item_property	= Database :: get_course_table(TABLE_ITEM_PROPERTY);
 		$session_id = api_get_session_id();
@@ -98,12 +102,11 @@ class ForumThreadLink extends AbstractLink
 				$cats[] = array ($data['thread_id'], $data['thread_title']);
 			}
 		}
-		$my_cats=isset($cats)?$cats:null;
+		$my_cats = isset($cats) ? $cats : null;
 
 		return $my_cats;
 	}
 
-
 	/**
 	 * Has anyone done this exercise yet ?
 	 */
@@ -162,7 +165,7 @@ class ForumThreadLink extends AbstractLink
 			$sumResult = 0;
 
 			while ($data = Database::fetch_array($scores)) {
-				if (!(array_key_exists($data['user_id'],$students))) {
+				if (!(array_key_exists($data['user_id'], $students))) {
 					if ($assignment['thread_qualify_max'] != 0) {
 						$students[$data['user_id']] = $data['qualify'];
 						$rescount++;
@@ -179,13 +182,20 @@ class ForumThreadLink extends AbstractLink
 			if ($rescount == 0) {
 				return null;
 			} else {
-				if ($type == 'average') {
-					return array($sumResult/$rescount, $weight);
-				}
-				if ($type == 'best') {
-					return array($bestResult, $weight);
+				switch ($type) {
+					case 'best':
+						return array($bestResult, $weight);
+						break;
+					case 'average':
+						return array($sumResult/$rescount, $weight);
+						break;
+					case 'ranking':
+						return AbstractLink::getCurrentUserRanking($students);
+						break;
+					default:
+						return array($sum, $rescount);
+						break;
 				}
-				return array($sum , $rescount);
 			}
 		}
 	}

+ 14 - 6
main/gradebook/lib/be/learnpathlink.class.php

@@ -150,13 +150,21 @@ class LearnpathLink extends AbstractLink
 			if ($rescount == 0) {
 				return null;
 			} else {
-				if ($type == 'average') {
-					return array($sumResult/$rescount, 100);
-				}
-				if ($type == 'best') {
-					return array($bestResult, 100);
+
+				switch ($type) {
+					case 'best':
+						return array($bestResult, 100);
+						break;
+					case 'average':
+						return array($sumResult/$rescount, 100);
+						break;
+					case 'ranking':
+						return AbstractLink::getCurrentUserRanking($students);
+						break;
+					default:
+						return array($sum, $rescount);
+						break;
 				}
-				return array($sum, $rescount);
 			}
 		}
 	}

+ 2 - 3
main/gradebook/lib/be/result.class.php

@@ -69,8 +69,6 @@ class Result
         $this->score = $score;
     }
 
-// CRUD FUNCTIONS
-
     /**
      * Retrieve results and return them as an array of Result objects
      * @param $id result id
@@ -87,7 +85,8 @@ class Result
 
         if (is_null($id) && is_null($user_id) && !is_null($evaluation_id)) {
             // Verified_if_exist_evaluation
-            $sql = 'SELECT COUNT(*) AS count FROM ' . $tbl_grade_results . '
+            $sql = 'SELECT COUNT(*) AS count
+                    FROM ' . $tbl_grade_results . '
                     WHERE evaluation_id="' . Database::escape_string($evaluation_id) . '";';
             $result = Database::query($sql);
             $existEvaluation = Database::result($result, 0, 0);

+ 14 - 6
main/gradebook/lib/be/studentpublicationlink.class.php

@@ -219,6 +219,7 @@ class StudentPublicationLink extends AbstractLink
 			$bestResult = 0;
 			$weight = 0;
 			$sumResult = 0;
+			$myResult = 0;
 
 			while ($data = Database::fetch_array($scores)) {
 				if (!(array_key_exists($data['user_id'], $students))) {
@@ -239,13 +240,20 @@ class StudentPublicationLink extends AbstractLink
 			if ($rescount == 0) {
 				return null;
 			} else {
-				if ($type == 'best') {
-					return array($bestResult, $weight);
+				switch ($type) {
+					case 'best':
+						return array($bestResult, $weight);
+						break;
+					case 'average':
+						return array($sumResult/$rescount, $weight);
+						break;
+					case 'ranking':
+						return AbstractLink::getCurrentUserRanking($students);
+						break;
+					default:
+						return array($sum, $rescount);
+						break;
 				}
-				if ($type == 'average') {
-					return array($sumResult/$rescount, $weight);
-				}
-				return array($sum, $rescount);
 			}
 		}
 	}

+ 26 - 10
main/gradebook/lib/be/surveylink.class.php

@@ -10,6 +10,9 @@ class SurveyLink extends AbstractLink
 {
 	private $survey_table = null;
 
+	/**
+	 * Constructor
+	 */
 	public function __construct()
 	{
 		parent::__construct();
@@ -95,9 +98,14 @@ class SurveyLink extends AbstractLink
 		$sql = 'SELECT survey_id, title, code
     			FROM '.$this->get_survey_table().' AS srv
 				WHERE survey_id NOT IN
-					(SELECT ref_id FROM '.$tbl_grade_links.'
-					WHERE type = '.LINK_SURVEY." AND course_code = '".$this->get_course_code()."'"
-			.') AND srv.session_id='.api_get_session_id().'';
+					(
+					SELECT ref_id FROM '.$tbl_grade_links.'
+					WHERE
+						type = '.LINK_SURVEY.' AND
+						course_code = '".$this->get_course_code()."'
+					)
+					AND srv.session_id = '.api_get_session_id();
+
 		$result = Database::query($sql);
 
 		$links = array();
@@ -172,7 +180,7 @@ class SurveyLink extends AbstractLink
 		if ($get_individual_score) {
 			// for 1 student
 			if ($data = Database::fetch_array($sql_result)) {
-				return array ($data['answered'] ? $max_score : 0, $max_score);
+				return array($data['answered'] ? $max_score : 0, $max_score);
 			}
 			return array(0, $max_score);
 		} else {
@@ -194,13 +202,21 @@ class SurveyLink extends AbstractLink
 			if ($rescount == 0) {
 				return null;
 			}
-			if ($type == 'best') {
-				return array($bestResult, $rescount);
-			}
-			if ($type == 'average') {
-				return array($sum, $rescount);
+
+			switch ($type) {
+				case 'best':
+					return array($bestResult, $rescount);
+					break;
+				case 'average':
+					return array($sum, $rescount);
+					break;
+				case 'ranking':
+					return null;
+					break;
+				default:
+					return array($sum, $rescount);
+					break;
 			}
-			return array($sum, $rescount);
 		}
 	}
 

+ 175 - 67
main/gradebook/lib/fe/gradebooktable.class.php

@@ -54,7 +54,7 @@ class GradebookTable extends SortableTable
         } else {
             $this->set_header($column++, get_lang('Weight'), false);
             $this->set_header($column++, get_lang('Result'), false);
-            $this->set_header($column++, get_lang('Global'), false);
+            $this->set_header($column++, get_lang('Ranking'), false);
             $this->set_header($column++, get_lang('Best'), false);
             $this->set_header($column++, get_lang('Average'), false);
 
@@ -114,7 +114,7 @@ class GradebookTable extends SortableTable
         //variables load in index.php
         global $certificate_min_score;
         // determine sorting type
-        $col_adjust = (api_is_allowed_to_edit() ? 1 : 0);
+        $col_adjust = api_is_allowed_to_edit() ? 1 : 0;
         // By id
         $this->column = 5;
 
@@ -150,10 +150,27 @@ class GradebookTable extends SortableTable
         $course_code = api_get_course_id();
         $session_id = api_get_session_id();
         $status_user = api_get_status_of_user_in_course($user_id, $course_code);
+
+        if (empty($session_id)) {
+            $statusToFilter = STUDENT;
+        } else {
+            $statusToFilter = 0;
+        }
+
+        $studentList = CourseManager::get_user_list_from_course_code(
+            $course_code,
+            $session_id,
+            null,
+            null,
+            $statusToFilter
+        );
+
         $data_array = $this->datagen->get_data(
             $sorting,
             $from,
-            $this->per_page
+            $this->per_page,
+            false,
+            $studentList
         );
 
         // generate the data to display
@@ -164,17 +181,22 @@ class GradebookTable extends SortableTable
         $total_categories_weight = 0;
         $scoredisplay = ScoreDisplay :: instance();
 
+        $totalResult = [0, 0];
+        $totalGlobal = [0, 0];
+        $totalBest = [0, 0];
+        $totalAverage = [0, 0];
+
         // Categories.
         foreach ($data_array as $data) {
 
             // list of items inside the gradebook (exercises, lps, forums, etc)
             $row  = array();
             /** @var AbstractLink $item */
-            $item = $item_category = $data[0];
+            $item = $mainCategory = $data[0];
 
             //if the item is invisible, wrap it in a span with class invisible
-            $invisibility_span_open  = (api_is_allowed_to_edit() && $item->is_visible() == '0') ? '<span class="invisible">' : '';
-            $invisibility_span_close = (api_is_allowed_to_edit() && $item->is_visible() == '0') ? '</span>' : '';
+            $invisibility_span_open  = api_is_allowed_to_edit() && $item->is_visible() == '0' ? '<span class="invisible">' : '';
+            $invisibility_span_close = api_is_allowed_to_edit() && $item->is_visible() == '0' ? '</span>' : '';
 
             // Id
             if (api_is_allowed_to_edit(null, true)) {
@@ -223,7 +245,7 @@ class GradebookTable extends SortableTable
             if (api_is_allowed_to_edit(null, true)) {
                 $weight_total_links += $data[3];
             } else {
-                $cattotal   = Category :: load($_GET['selectcat']);
+                $cattotal = Category::load($_GET['selectcat']);
                 $scoretotal = $cattotal[0]->calc_score(api_get_user_id());
                 $item_value = $scoredisplay->display_score($scoretotal, SCORE_SIMPLE);
             }
@@ -239,13 +261,18 @@ class GradebookTable extends SortableTable
                 $score = $item->calc_score(api_get_user_id());
 
                 if (!empty($score[1])) {
+                    $categoryScoreArray = $score;
                     $completeScore = $scoredisplay->display_score($score, SCORE_DIV_PERCENT);
                     $score = $score[0]/$score[1]*$item->get_weight();
-
                     $score = $scoredisplay->display_score(array($score, null), SCORE_SIMPLE);
-                    $categoryScore = $scoredisplay->display_score(array($score, $mainCategoryWeight), SCORE_DIV);
+
+                    $categoryScore = $scoredisplay->display_score(
+                        array($score, $mainCategoryWeight),
+                        SCORE_DIV
+                    );
                     $scoreToDisplay = Display::tip($score, $completeScore);
                 } else {
+                    $categoryScoreArray = [0, $item->get_weight()];
                     $scoreToDisplay = '-';
                     $categoryScore = null;
                 }
@@ -255,22 +282,35 @@ class GradebookTable extends SortableTable
                     $value_data = isset($data[4]) ? $data[4] : null;
                     $best = isset($data['best']) ? $data['best'] : null;
                     $average = isset($data['average']) ? $data['average'] : null;
-
-                    //if (!is_null($value_data)) {
-                        // Student result
-                        $row[] = Display::tag('h4', $value_data);
-                        // Global
-                        $row[] = Display::tag('h4', $categoryScore);
-                        // Best
-                        $row[] = Display::tag('h4', $best);
-                        // Average
-                        $row[] = Display::tag('h4', $average);
-
+                    $ranking = isset($data['ranking']) ? $data['ranking'] : null;
+
+                    $totalResult = [
+                        $totalResult[0] + $data['result_score'][0],
+                        $totalResult[1] + $data['result_score'][1],
+                    ];
+
+                    $totalBest = [
+                        $totalBest[0] + $data['best_score'][0],
+                        $totalBest[1] + $data['best_score'][1],
+                    ];
+
+                    $totalAverage = [
+                        $totalAverage[0] + $data['average_score'][0],
+                        $totalAverage[1] + $data['average_score'][1],
+                    ];
+
+                    // Student result
+                    $row[] = $value_data;
+                    // Ranking
+                    $row[] = $ranking;
+                    // Best
+                    $row[] = $best;
+                    // Average
+                    $row[] = $average;
+
+                    if (get_class($item) == 'Category') {
                         $row[] = $this->build_edit_column($item);
-
-                    /*} else {
-
-                    }*/
+                    }
                 } else {
                     $row[] = $scoreToDisplay;
 
@@ -300,20 +340,20 @@ class GradebookTable extends SortableTable
                     $data_array = $sub_cat_info->get_data($sorting, $from, $this->per_page);
                     $total_weight = 0;
 
-                    //$score = $cats[0]->calc_score(api_get_user_id());
-                    //$categoryScore = $scoredisplay->display_score(array($score, $cats[0]->get_weight()), SCORE_DIV);
-
                     // Links.
                     foreach ($data_array as $data) {
+
                         $row = array();
                         $item = $data[0];
 
                         //if the item is invisible, wrap it in a span with class invisible
-                        $invisibility_span_open  = (api_is_allowed_to_edit() && $item->is_visible() == '0') ? '<span class="invisible">' : '';
-                        $invisibility_span_close = (api_is_allowed_to_edit() && $item->is_visible() == '0') ? '</span>' : '';
+                        $invisibility_span_open = api_is_allowed_to_edit() && $item->is_visible() == '0' ? '<span class="invisible">' : '';
+                        $invisibility_span_close = api_is_allowed_to_edit() && $item->is_visible() == '0' ? '</span>' : '';
 
-                        $main_categories[$parent_id]['children'][$item->get_id()]['name']   = $item->get_name();
-                        $main_categories[$parent_id]['children'][$item->get_id()]['weight'] = $item->get_weight();
+                        if (isset($item)) {
+                            $main_categories[$parent_id]['children'][$item->get_id()]['name'] = $item->get_name();
+                            $main_categories[$parent_id]['children'][$item->get_id()]['weight'] = $item->get_weight();
+                        }
 
                         if (api_is_allowed_to_edit(null, true)) {
                             $row[] = $this->build_id_column($item);
@@ -348,33 +388,33 @@ class GradebookTable extends SortableTable
                                 $row[] = $this->build_edit_column($item);
                             }
                         } else {
-                            //students get the results and certificates columns
+                            // Students get the results and certificates columns
                             $eval_n_links = array_merge($alleval, $alllink);
 
-                            if (count($eval_n_links)> 0 && $status_user!=1 ) {
+                            if (count($eval_n_links)> 0 && $status_user != 1) {
                                 $value_data = isset($data[4]) ? $data[4] : null;
+
                                 if (!is_null($value_data)) {
-                                    $score = $item->calc_score(api_get_user_id());
-                                    $new_score = $data[3] * $score[0] / $score[1];
-                                    $new_score = floatval(number_format($new_score, api_get_setting('gradebook_number_decimals')));
-                                    //$row[] = Display::tip($new_score, $data[4]);
+                                    //$score = $item->calc_score(api_get_user_id());
+                                    //$new_score = $data[3] * $score[0] / $score[1];
+                                    //$new_score = floatval(number_format($new_score, api_get_setting('gradebook_number_decimals')));
+
+                                    // Result
                                     $row[] = $value_data;
 
                                     $best = isset($data['best']) ? $data['best'] : null;
                                     $average = isset($data['average']) ? $data['average'] : null;
+                                    $ranking = isset($data['ranking']) ? $data['ranking'] : null;
 
-                                    // Global
-                                    $categoryScore = $scoredisplay->display_score(
-                                        array($new_score, $cattotal[0]->get_weight()),
-                                        SCORE_DIV
-                                    );
-                                    $row[] = Display::tag('h4', $categoryScore);
+                                    // Ranking
+                                    $row[] = $ranking;
                                     // Best
-                                    $row[] = Display::tag('h4', $best);
+                                    $row[] = $best;
                                     // Average
-                                    $row[] = Display::tag('h4', $average);
+                                    $row[] = $average;
                                 }
                             }
+
                             if (!empty($cats)) {
                                 $row[] = null;
                             }
@@ -389,7 +429,12 @@ class GradebookTable extends SortableTable
                             // Compare the category weight to the sum of all weights inside the category
                             if (intval($total_weight) == $category_weight) {
                                 $label = null;
-                                $total = GradebookUtils::score_badges(array($total_weight.' / '.$category_weight, '100'));
+                                $total = GradebookUtils::score_badges(
+                                    array(
+                                        $total_weight.' / '.$category_weight,
+                                        '100'
+                                    )
+                                );
                             } else {
                                 $label = Display::return_icon(
                                     'warning.png',
@@ -413,24 +458,85 @@ class GradebookTable extends SortableTable
         } //end looping categories
 
         if (api_is_allowed_to_edit()) {
+            // Total for teacher.
             if (count($main_cat) > 1) {
                 $main_weight = intval($main_cat[0]->get_weight());
 
                 if (intval($total_categories_weight) == $main_weight) {
-                    $total = GradebookUtils::score_badges(array($total_categories_weight.' / '.$main_weight, '100'));
+                    $total = GradebookUtils::score_badges(
+                        array($total_categories_weight.' / '.$main_weight, '100')
+                    );
                 } else {
                     $total = Display::badge($total_categories_weight.' / '.$main_weight, 'warning');
                 }
-                $row = array(null, null, '<h3>'.get_lang('Total').'</h3>', null, $total);
+                $row = array(
+                    null,
+                    null,
+                    '<h3>' . get_lang('Total') . '</h3>',
+                    null,
+                    $total
+                );
+                $sortable_data[] = $row;
+            }
+        } else {
+            // Total for student.
+            if (count($main_cat) > 1) {
+                $main_weight = intval($main_cat[0]->get_weight());
+
+                $global = null;
+                $average = null;
+
+                $totalResult = $scoredisplay->display_score(
+                    $totalResult,
+                    SCORE_DIV
+                );
+
+                $totalRanking = array();
+                foreach ($studentList as $student) {
+                    $score = $main_cat[0]->calc_score($student['user_id']);
+                    $totalRanking[$student['user_id']] = $score[0];
+                }
+
+                $totalRanking = AbstractLink::getCurrentUserRanking($totalRanking);
+
+                $totalRanking = $scoredisplay->display_score(
+                    $totalRanking,
+                    SCORE_DIV
+                );
+
+                $totalBest = $scoredisplay->display_score(
+                    $totalBest,
+                    SCORE_DIV
+                );
+
+                $totalAverage = $scoredisplay->display_score(
+                    $totalAverage,
+                    SCORE_DIV
+                );
+
+                $row = array(
+                    null,
+                    '<h3>' . get_lang('Total') . '</h3>',
+                    null,
+                    $main_weight,
+                    $totalResult,
+                    $totalRanking,
+                    $totalBest,
+                    $totalAverage,
+                );
+
                 $sortable_data[] = $row;
             }
         }
 
-        // warning messages
+        // Warning messages
         $view = isset($_GET['view']) ? $_GET['view']: null;
-        if (api_is_allowed_to_edit()) {
 
-            if (isset($_GET['selectcat']) && $_GET['selectcat'] > 0 && $view <> 'presence') {
+        if (api_is_allowed_to_edit()) {
+            if (isset($_GET['selectcat']) &&
+                $_GET['selectcat'] > 0 &&
+                $view <> 'presence'
+            ) {
                 $id_cat = intval($_GET['selectcat']);
                 $category = Category::load($id_cat);
 
@@ -485,14 +591,19 @@ class GradebookTable extends SortableTable
                     }
                 }
 
-                if (is_array($weight_categories) && is_array($certificate_min_scores) && is_array($course_codes)) {
+                if (is_array($weight_categories) &&
+                    is_array($certificate_min_scores) &&
+                    is_array($course_codes)
+                ) {
                     $warning_message = '';
                     for ($x = 0; $x<count($weight_categories);$x++) {
                         $weight_category = intval($weight_categories[$x]);
                         $certificate_min_score = intval($certificate_min_scores[$x]);
                         $course_code = $course_codes[$x];
 
-                        if (empty($certificate_min_score) || ($certificate_min_score > $weight_category)) {
+                        if (empty($certificate_min_score) ||
+                            ($certificate_min_score > $weight_category)
+                        ) {
                             $warning_message .= $course_code .'&nbsp;-&nbsp;'.get_lang('CertificateMinimunScoreIsRequiredAndMustNotBeMoreThan').'&nbsp;'.$weight_category.'<br />';
                         }
                     }
@@ -511,7 +622,7 @@ class GradebookTable extends SortableTable
      * @param $item
      * @return mixed
      */
-    private function build_certificate_min_score ($item)
+    private function build_certificate_min_score($item)
     {
         return $item->get_certificate_min_score();
     }
@@ -529,7 +640,7 @@ class GradebookTable extends SortableTable
      * @param $item
      * @return mixed
      */
-    private function build_course_code ($item)
+    private function build_course_code($item)
     {
         return $item->get_course_code();
     }
@@ -538,7 +649,7 @@ class GradebookTable extends SortableTable
      * @param $item
      * @return string
      */
-    private function build_id_column ($item)
+    private function build_id_column($item)
     {
         switch ($item->get_item_type()) {
             // category
@@ -558,7 +669,7 @@ class GradebookTable extends SortableTable
      * @param array $attributes
      * @return string
      */
-    private function build_type_column ($item, $attributes = array())
+    private function build_type_column($item, $attributes = array())
     {
         return GradebookUtils::build_type_icon_tag($item->get_icon_name(), $attributes);
     }
@@ -592,11 +703,9 @@ class GradebookTable extends SortableTable
             case 'E' :
                 $cat = new Category();
                 $course_id = CourseManager::get_course_by_category($_GET['selectcat']);
-
                 $show_message = $cat->show_message_resource_delete($course_id);
 
                 // course/platform admin can go to the view_results page
-
                 if (api_is_allowed_to_edit() && $show_message===false) {
                     if ($item->get_type() == 'presence') {
                         return '&nbsp;'
@@ -616,24 +725,23 @@ class GradebookTable extends SortableTable
                     . $item->get_name()
                     . '</a>';
 
-                } elseif ($show_message===false && !api_is_allowed_to_edit() && !(ScoreDisplay :: instance()->is_custom())) {
+                } elseif ($show_message === false && !api_is_allowed_to_edit() && !ScoreDisplay :: instance()->is_custom()) {
                     return '&nbsp;'
                     . '<a href="gradebook_statistics.php?selecteval=' . $item->get_id() . '">'
                     . $item->get_name()
                     . '</a>';
-
                 } else {
                     return '['.get_lang('Evaluation').']&nbsp;&nbsp;'.$item->get_name().$show_message;
                 }
-            // link
-            case 'L' :
-                $cat 			= new Category();
-                $course_id	 	= CourseManager::get_course_by_category($_GET['selectcat']);
-                $show_message	= $cat->show_message_resource_delete($course_id);
+            case 'L':
+                // link
+                $cat = new Category();
+                $course_id = CourseManager::get_course_by_category($_GET['selectcat']);
+                $show_message = $cat->show_message_resource_delete($course_id);
 
                 $url = $item->get_link();
 
-                if (isset($url) && $show_message===false) {
+                if (isset($url) && $show_message === false) {
                     $text = '&nbsp;<a href="' . $item->get_link() . '">'
                         . $item->get_name()
                         . '</a>';

+ 2 - 2
main/gradebook/lib/flatview_data_generator.class.php

@@ -553,12 +553,12 @@ class FlatViewDataGenerator
             $score = $item->calc_score($user_id);
 
             $real_score = $score;
-            $divide = (($score[1])==0 ) ? 1 : $score[1];
+            $divide = isset($score[1]) && !empty($score[1]) ? $score[1] : 1;
 
             // Sub cat weight
             $sub_cat_percentage = $sum_categories_weight_array[$item->get_category_id()];
 
-            $item_value = $score[0]/$divide;
+            $item_value = isset($score[0]) ? $score[0]/$divide : 0;
 
             // Fixing total when using one or multiple gradebooks.
             if (empty($parentCategoryIdFilter)) {

+ 142 - 22
main/gradebook/lib/gradebook_data_generator.class.php

@@ -80,7 +80,7 @@ class GradebookDataGenerator
      * 4: date
      * 5: student's score (if student logged in)
      */
-    public function get_data($sorting = 0, $start = 0, $count = null, $ignore_score_color = false)
+    public function get_data($sorting = 0, $start = 0, $count = null, $ignore_score_color = false, $studentList = array())
     {
         //$status = CourseManager::get_user_in_course_status(api_get_user_id(), api_get_course_id());
         // do some checks on count, redefine if invalid value
@@ -112,39 +112,111 @@ class GradebookDataGenerator
         // get selected items
         $visibleitems = array_slice($allitems, $start, $count);
         //status de user in course
-        $user_id = api_get_user_id();
+        $userId = api_get_user_id();
         $course_code = api_get_course_id();
-        $status_user = api_get_status_of_user_in_course($user_id, $course_code);
+        $sessionId = api_get_session_id();
+        $status_user = api_get_status_of_user_in_course($userId, $course_code);
+
+        if (empty($sessionId)) {
+            $statusToFilter = STUDENT;
+        } else {
+            $statusToFilter = 0;
+        }
+
+        $userCount = CourseManager::get_user_list_from_course_code(
+            $course_code,
+            $sessionId,
+            null,
+            null,
+            $statusToFilter,
+            true
+        );
 
         // Generate the data to display
         $data = array();
+
         /** @var GradebookItem $item */
+        $totalWeight = 0;
         foreach ($visibleitems as $item) {
-            $row = array ();
+            $row = array();
             $row[] = $item;
             $row[] = $item->get_name();
             // display the 2 first line of description, and all description on mouseover (https://support.chamilo.org/issues/6588)
             $row[] = '<span title="'.api_remove_tags_with_space($item->get_description()).'">'.
                 api_get_short_text_from_html($item->get_description(), 160).'</span>';
+            $totalWeight += $item->get_weight();
             $row[] = $item->get_weight();
             if (count($this->evals_links) > 0) {
+                // Items inside a category.
                 if (!api_is_allowed_to_edit() || $status_user != 1 ) {
-                    $row[] = $this->build_result_column($item, $ignore_score_color);
-                    $row['best'] = $this->buildBestResultColumn($item);
-                    $row['average'] = $this->buildAverageResultColumn($item);
+                    $resultColumn = $this->build_result_column(
+                        api_get_user_id(),
+                        $item,
+                        $ignore_score_color
+                    );
+
+                    $row[] = $resultColumn['display'];
+                    $row['result_score'] = $resultColumn['score'];
+
+                    // Best
+                    $best = $this->buildBestResultColumn($item);
+                    $row['best'] = $best['display'];
+                    $row['best_score'] = $best['score'];
+
+                    // Average
+                    $average = $this->buildAverageResultColumn($item);
+                    $row['average'] = $average['display'];
+                    $row['average_score'] = $average['score'];
+
+                    // Ranking
+                    $ranking = $this->buildRankingColumn($item, $userCount);
+
+                    $row['ranking'] = $ranking['display'];
+                    $row['ranking_score'] = $ranking['score'];
+
                     $row[] = $item;
                 }
             } else {
-                $row[] = $this->build_result_column($item, $ignore_score_color, true);
-                $row['best'] = $this->buildBestResultColumn($item);
-                $row['average'] = $this->buildAverageResultColumn($item);
+                // Category.
+
+                // Result
+                $result = $this->build_result_column($userId, $item, $ignore_score_color, true);
+                $row[] = $result['display'];
+                $row['result_score'] = $result['score'];
+
+                // Best
+                $best = $this->buildBestResultColumn($item);
+                $row['best'] = $best['display'];
+                $row['best_score'] = $best['score'];
+
+                // Average
+                $average = $this->buildAverageResultColumn($item);
+                $row['average'] = $average['display'];
+                $row['average_score'] = $average['score'];
+
+                // Ranking
+                $rankingStudentList = array();
+                foreach ($studentList as $user) {
+                    $score = $this->build_result_column(
+                        $user['user_id'],
+                        $item,
+                        $ignore_score_color,
+                        true
+                    );
+                    $rankingStudentList[$user['user_id']] = $score['display'][0];
+                }
+                $scoreDisplay = ScoreDisplay::instance();
+
+                $score = AbstractLink::getCurrentUserRanking($rankingStudentList);
+                $row['ranking'] = $scoreDisplay->display_score($score, SCORE_DIV);
+
             }
             $data[] = $row;
         }
+
         return $data;
     }
 
-
     /**
      * Get best result of an item
      * @param GradebookItem $item
@@ -161,7 +233,10 @@ class GradebookDataGenerator
 
         $scoreDisplay = ScoreDisplay :: instance();
 
-        return $scoreDisplay->display_score($score, SCORE_DIV);
+        return array(
+            'display' => $scoreDisplay->display_score($score, SCORE_DIV),
+            'score' => $score
+        );
     }
 
     /**
@@ -172,18 +247,43 @@ class GradebookDataGenerator
     {
         $score = $item->calc_score(null, 'average');
         $scoreDisplay = ScoreDisplay :: instance();
-        return $scoreDisplay->display_score($score, SCORE_DIV);
+
+        return array(
+            'display' => $scoreDisplay->display_score($score, SCORE_DIV),
+            'score' => $score
+        );
     }
 
     /**
+     * @param GradebookItem $item
+     * @return string
+     */
+    private function buildRankingColumn(GradebookItem $item, $userCount = 0)
+    {
+        $score = $item->calc_score(null, 'ranking');
+        $score[1] = $userCount;
+        $scoreDisplay = ScoreDisplay::instance();
+
+        return array(
+            'display' => $scoreDisplay->display_score($score, SCORE_DIV),
+            'score' => $score
+        );
+    }
+
+    /**
+     * @param int $userId
      * @param GradebookItem $item
      * @param $ignore_score_color
      * @return null|string
      */
-    private function build_result_column($item, $ignore_score_color, $forceSimpleResult = false)
-    {
-        $scoredisplay = ScoreDisplay :: instance();
-        $score = $item->calc_score(api_get_user_id());
+    private function build_result_column(
+        $userId,
+        $item,
+        $ignore_score_color,
+        $forceSimpleResult = false
+    ) {
+        $scoredisplay = ScoreDisplay::instance();
+        $score = $item->calc_score($userId);
 
         if (!empty($score)) {
             switch ($item->get_item_type()) {
@@ -195,11 +295,24 @@ class GradebookDataGenerator
                             $displaytype |= SCORE_IGNORE_SPLIT;
                         }
                         if ($forceSimpleResult) {
-                            return $scoredisplay->display_score($score, SCORE_DIV);
+                            return
+                                array(
+                                    'display' => $scoredisplay->display_score(
+                                        $score,
+                                        SCORE_DIV
+                                    ),
+                                    'score' => $score
+                                );
                         }
-                        return get_lang('Total') . ' : '. $scoredisplay->display_score($score, $displaytype);
+                        return array(
+                            'display' => get_lang('Total') . ' : '. $scoredisplay->display_score($score, $displaytype),
+                            'score' => $score
+                        );
                     } else {
-                        return '';
+                        return array(
+                            'display' => null,
+                            'score' => $score
+                        );
                     }
                     break;
                 // evaluation and link
@@ -209,10 +322,17 @@ class GradebookDataGenerator
                     if ($ignore_score_color) {
                         $displaytype |= SCORE_IGNORE_SPLIT;
                     }*/
-                    return $scoredisplay->display_score($score, SCORE_DIV_PERCENT_WITH_CUSTOM);
+                    return array(
+                        'display' => $scoredisplay->display_score($score, SCORE_DIV),
+                        'score' => $score,
+                    );
             }
         }
-        return null;
+
+        return array(
+            'display' => null,
+            'score' => null
+        );
     }
 
     /**

+ 26 - 12
main/gradebook/lib/scoredisplay.class.php

@@ -3,7 +3,7 @@
 
 /**
  * Class ScoreDisplay
- * Class to display scores according to the settings made by the platform admin.
+ * Display scores according to the settings made by the platform admin.
  * This class works as a singleton: call instance() to retrieve an object.
  * @author Bert Steppé
  * @package chamilo.gradebook
@@ -19,6 +19,7 @@ class ScoreDisplay
 
     /**
      * Get the instance of this class
+     * @param int $category_id
      */
     public static function instance($category_id = 0)
     {
@@ -48,7 +49,6 @@ class ScoreDisplay
             } else {
                 return (($score1[0]/$score1[1]) < ($score2[0]/$score2[1]) ? -1 : 1);
             }
-
         }
     }
 
@@ -224,6 +224,10 @@ class ScoreDisplay
         Database::query($sql);
     }
 
+    /**
+     * @param int $category_id
+     * @return bool
+     */
     public function insert_defaults($category_id)
     {
         if (empty($category_id)) {
@@ -252,6 +256,9 @@ class ScoreDisplay
         }
     }
 
+    /**
+     * @return int|null|string
+     */
     public function get_number_decimals()
     {
         $number_decimals = api_get_setting('gradebook_number_decimals');
@@ -284,8 +291,12 @@ class ScoreDisplay
      *
      * @return string
      */
-    public function display_score($score, $type = SCORE_DIV_PERCENT, $what = SCORE_BOTH, $no_color = false)
-    {
+    public function display_score(
+        $score,
+        $type = SCORE_DIV_PERCENT,
+        $what = SCORE_BOTH,
+        $no_color = false
+    ) {
         $my_score = $score == 0 ? 1 : $score;
 
         if ($type == SCORE_BAR) {
@@ -306,8 +317,9 @@ class ScoreDisplay
         }
 
         if ($this->coloring_enabled && $no_color == false) {
-            $my_score_denom = ($score[1]==0)?1:$score[1];
-            if (($score[0] / $my_score_denom) < ($this->color_split_value / 100)) {
+            $my_score_denom = isset($score[1]) && !empty($score[1]) ? $score[1] : 1;
+            $scoreCleaned = isset($score[0]) ? $score[0] : 0;
+            if (($scoreCleaned / $my_score_denom) < ($this->color_split_value / 100)) {
                 $display = Display::tag('font', $display, array('color'=>'red'));
             }
         }
@@ -319,7 +331,7 @@ class ScoreDisplay
      * @param $type
      * @return float|string
      */
-    private function display_default ($score, $type)
+    private function display_default($score, $type)
     {
         switch ($type) {
             case SCORE_DIV :                            // X / Y
@@ -410,10 +422,10 @@ class ScoreDisplay
     private function display_as_div($score)
     {
         if ($score == 1) {
-            return '0/0';
+            return '0 / 0';
         } else {
-            $score[0] =$this->format_score($score[0]);
-            $score[1] =$this->format_score($score[1]);
+            $score[0] = isset($score[0]) ? $this->format_score($score[0]) : 0;
+            $score[1] = isset($score[1]) ? $this->format_score($score[1]) : 0;
             return  $score[0] . ' / ' . $score[1];
         }
     }
@@ -422,7 +434,7 @@ class ScoreDisplay
      * Depends on the teacher's configuration of thresholds. i.e. [0 50] "Bad", [50:100] "Good"
      * @param array $score
      */
-    private function display_custom ($score)
+    private function display_custom($score)
     {
         $my_score_denom= ($score[1]==0) ? 1 : $score[1];
         $scaledscore = $score[0] / $my_score_denom;
@@ -480,7 +492,9 @@ class ScoreDisplay
         } else {
             $category_id = $this->get_current_gradebook_category_id();
         }
-        $sql = 'SELECT * FROM '.$tbl_display.' WHERE category_id = '.$category_id.' ORDER BY score';
+        $sql = 'SELECT * FROM '.$tbl_display.'
+                WHERE category_id = '.$category_id.'
+                ORDER BY score';
         $result = Database::query($sql);
         return Database::store_result($result,'ASSOC');
     }

+ 86 - 12
main/gradebook/lib/user_data_generator.class.php

@@ -154,9 +154,11 @@ class UserDataGenerator
 		return $data;
 	}
 
-	// Sort functions
-	// Make sure to only use functions as defined in the GradebookItem interface !
-
+	/**
+	 * @param $item1
+	 * @param $item2
+	 * @return int
+	 */
 	function sort_by_type($item1, $item2)
 	{
 		if ($item1->get_item_type() == $item2->get_item_type()) {
@@ -166,6 +168,11 @@ class UserDataGenerator
 		}
 	}
 
+	/**
+	 * @param $item1
+	 * @param $item2
+	 * @return int
+	 */
 	function sort_by_course($item1, $item2)
 	{
 		$name1 = api_strtolower($this->get_course_name_from_code_cached($item1->get_course_code()));
@@ -173,6 +180,11 @@ class UserDataGenerator
 		return api_strnatcmp($name1, $name2);
 	}
 
+	/**
+	 * @param $item1
+	 * @param $item2
+	 * @return int
+	 */
 	function sort_by_category($item1, $item2)
 	{
 		$cat1 = $this->get_category_cached($item1->get_category_id());
@@ -182,11 +194,21 @@ class UserDataGenerator
 		return api_strnatcmp($name1, $name2);
 	}
 
+	/**
+	 * @param $item1
+	 * @param $item2
+	 * @return int
+	 */
 	function sort_by_name($item1, $item2)
 	{
 		return api_strnatcmp($item1->get_name(),$item2->get_name());
 	}
 
+	/**
+	 * @param $item1
+	 * @param $item2
+	 * @return int
+	 */
 	function sort_by_average($item1, $item2)
 	{
 		$score1 = $this->avgcache[$item1->get_item_type() . $item1->get_id()];
@@ -194,6 +216,11 @@ class UserDataGenerator
 		return $this->compare_scores($score1, $score2);
 	}
 
+	/**
+	 * @param $item1
+	 * @param $item2
+	 * @return int
+	 */
 	function sort_by_score($item1, $item2)
 	{
 		$score1 = $this->scorecache[$item1->get_item_type() . $item1->get_id()];
@@ -201,6 +228,11 @@ class UserDataGenerator
 		return $this->compare_scores($score1, $score2);
 	}
 
+	/**
+	 * @param $item1
+	 * @param $item2
+	 * @return int
+	 */
 	function sort_by_mask($item1, $item2)
 	{
 		$score1 = $this->scorecache[$item1->get_item_type() . $item1->get_id()];
@@ -208,7 +240,12 @@ class UserDataGenerator
 		return ScoreDisplay :: compare_scores_by_custom_display($score1, $score2);
 	}
 
-	function compare_scores ($score1, $score2)
+	/**
+	 * @param $score1
+	 * @param $score2
+	 * @return int
+	 */
+	function compare_scores($score1, $score2)
 	{
 		if (!isset($score1)) {
 			return (isset($score2) ? 1 : 0);
@@ -221,19 +258,32 @@ class UserDataGenerator
 		}
 	}
 
-	private function build_course_name ($item)
+	/**
+	 * @param $item
+	 * @return mixed
+	 */
+	private function build_course_name($item)
 	{
 		return $this->get_course_name_from_code_cached($item->get_course_code());
 	}
 
-	private function build_category_name ($item)
+	/**
+	 * @param $item
+	 * @return string
+	 */
+	private function build_category_name($item)
 	{
 		$cat = $this->get_category_cached($item->get_category_id());
 
 		return $this->get_category_name_to_display($cat);
 	}
 
-	private function build_average_column ($item, $ignore_score_color)
+	/**
+	 * @param $item
+	 * @param $ignore_score_color
+	 * @return string
+	 */
+	private function build_average_column($item, $ignore_score_color)
 	{
 		if (isset($this->avgcache)) {
 			$avgscore = $this->avgcache[$item->get_item_type() . $item->get_id()];
@@ -249,7 +299,12 @@ class UserDataGenerator
 		return $scoredisplay->display_score($avgscore, $displaytype);
 	}
 
-	private function build_result_column ($item, $ignore_score_color)
+	/**
+	 * @param $item
+	 * @param $ignore_score_color
+	 * @return string
+	 */
+	private function build_result_column($item, $ignore_score_color)
 	{
 		$studscore = $this->scorecache[$item->get_item_type() . $item->get_id()];
 		$scoredisplay = ScoreDisplay :: instance();
@@ -260,6 +315,11 @@ class UserDataGenerator
 		return $scoredisplay->display_score($studscore, $displaytype, SCORE_ONLY_DEFAULT);
 	}
 
+	/**
+	 * @param $item
+	 * @param $ignore_score_color
+	 * @return string
+	 */
 	private function build_mask_column($item, $ignore_score_color)
 	{
 		$studscore = $this->scorecache[$item->get_item_type() . $item->get_id()];
@@ -271,8 +331,12 @@ class UserDataGenerator
 		return $scoredisplay->display_score($studscore, $displaytype, SCORE_ONLY_CUSTOM);
 	}
 
-
-	private function get_course_name_from_code_cached ($coursecode) {
+	/**
+	 * @param $coursecode
+	 * @return mixed
+	 */
+	private function get_course_name_from_code_cached($coursecode)
+	{
 		if (isset ($this->coursecodecache)
 			&& isset ($this->coursecodecache[$coursecode])) {
 			return $this->coursecodecache[$coursecode];
@@ -283,7 +347,12 @@ class UserDataGenerator
 		}
 	}
 
-	private function get_category_cached ($category_id) {
+	/**
+	 * @param $category_id
+	 * @return null
+	 */
+	private function get_category_cached($category_id)
+	{
 		if (isset ($this->categorycache)
 			&& isset ($this->categorycache[$category_id])) {
 			return $this->categorycache[$category_id];
@@ -297,7 +366,12 @@ class UserDataGenerator
 		}
 	}
 
-	private function get_category_name_to_display ($cat) {
+	/**
+	 * @param $cat
+	 * @return string
+	 */
+	private function get_category_name_to_display($cat)
+	{
 		if (isset($cat)) {
 			if ($cat->get_parent_id() == '0' || $cat->get_parent_id() == null){
 				return '';

+ 97 - 18
main/inc/lib/attendance.lib.php

@@ -101,7 +101,7 @@ class Attendance
 	 */
 	public static function get_attendance_data($from, $number_of_items, $column, $direction)
 	{
-		$tbl_attendance = Database :: get_course_table(TABLE_ATTENDANCE);
+		$tbl_attendance = Database::get_course_table(TABLE_ATTENDANCE);
 		$course_id = api_get_course_int_id();
 		$session_id = api_get_session_id();
 		$condition_session = api_get_session_condition($session_id);
@@ -114,8 +114,10 @@ class Attendance
 		}
 
 		$active_plus = '';
-		if ((isset($_GET['isStudentView']) && $_GET['isStudentView'] == 'true') || !api_is_allowed_to_edit(null, true)) {
-			$active_plus = 'AND att.active = 1';
+		if ((isset($_GET['isStudentView']) && $_GET['isStudentView'] == 'true') ||
+			!api_is_allowed_to_edit(null, true)
+		) {
+			$active_plus = ' AND att.active = 1';
 		}
 
 		$sql = "SELECT
@@ -127,7 +129,9 @@ class Attendance
                     att.active AS col5,
                     att.session_id
 				FROM $tbl_attendance att
-				WHERE c_id = $course_id $active_plus $condition_session
+				WHERE
+					att.active <> 2 AND
+					c_id = $course_id $active_plus $condition_session
 				ORDER BY col$column $direction
 				LIMIT $from,$number_of_items ";
 
@@ -139,11 +143,13 @@ class Attendance
 			$param_gradebook = '&gradebook='.$_SESSION['gradebook'];
 		}
 		$user_info = api_get_user_info();
+		$allowDelete = api_get_configuration_value('allow_delete_attendance');
+
 		while ($attendance = Database::fetch_row($res)) {
 
 			$student_param = '';
 			if (api_is_drh() && $_GET['student_id']) {
-				$student_param = '&student_id='.Security::remove_XSS($_GET['student_id']);
+				$student_param = '&student_id='.intval($_GET['student_id']);
 			}
 
 			$session_star = '';
@@ -182,21 +188,42 @@ class Attendance
 				$actions .= '<center>';
 
 				if (api_is_platform_admin()) {
-					$actions .= '<a href="index.php?'.api_get_cidreq().'&action=attendance_edit&attendance_id='.$attendance[0].$param_gradebook.'">'.Display::return_icon('edit.png', get_lang('Edit'), array(), ICON_SIZE_SMALL).'</a>&nbsp;';
+					$actions .= '<a href="index.php?'.api_get_cidreq().'&action=attendance_edit&attendance_id='.$attendance[0].$param_gradebook.'">'.
+						Display::return_icon('edit.png', get_lang('Edit'), array(), ICON_SIZE_SMALL).'</a>&nbsp;';
+					// Visible
 					if ($attendance[5] == 1) {
-						$actions .= '<a href="index.php?'.api_get_cidreq().'&action=attendance_delete&attendance_id='.$attendance[0].$param_gradebook.'">'.Display::return_icon('visible.png', get_lang('Hide'), array(), ICON_SIZE_SMALL).'</a>';
+						$actions .= '<a href="index.php?'.api_get_cidreq().'&action=attendance_set_invisible&attendance_id='.$attendance[0].$param_gradebook.'">'.
+							Display::return_icon('visible.png', get_lang('Hide'), array(), ICON_SIZE_SMALL).'</a>';
 					} else {
-						$actions .= '<a href="index.php?'.api_get_cidreq().'&action=attendance_restore&attendance_id='.$attendance[0].$param_gradebook.'">'.Display::return_icon('invisible.png', get_lang('Show'), array(), ICON_SIZE_SMALL).'</a>';
+						$actions .= '<a href="index.php?'.api_get_cidreq().'&action=attendance_set_visible&attendance_id='.$attendance[0].$param_gradebook.'">'.
+							Display::return_icon('invisible.png', get_lang('Show'), array(), ICON_SIZE_SMALL).'</a>';
 						$attendance[2] = '<span class="muted">'.$attendance[2].'</span>';
 					}
+					if ($allowDelete) {
+						$actions .= '<a href="index.php?' . api_get_cidreq() . '&action=attendance_delete&attendance_id=' . $attendance[0] . $param_gradebook . '">' .
+							Display::return_icon('delete.png', get_lang('Delete'), array(), ICON_SIZE_SMALL) . '</a>';
+					}
 				} else {
 					$is_locked_attendance = self::is_locked_attendance($attendance[0]);
 					if ($is_locked_attendance) {
 						$actions .= Display::return_icon('edit_na.png', get_lang('Edit')).'&nbsp;';
 						$actions .= Display::return_icon('visible.png', get_lang('Hide'));
 					} else {
-						$actions .= '<a href="index.php?'.api_get_cidreq().'&action=attendance_edit&attendance_id='.$attendance[0].$param_gradebook.'">'.Display::return_icon('edit.png', get_lang('Edit'), array(), ICON_SIZE_SMALL).'</a>&nbsp;';
-						$actions .= '<a href="index.php?'.api_get_cidreq().'&action=attendance_delete&attendance_id='.$attendance[0].$param_gradebook.'">'.Display::return_icon('visible.png', get_lang('Hide'), array(), ICON_SIZE_SMALL).'</a>';
+						$actions .= '<a href="index.php?'.api_get_cidreq().'&action=attendance_edit&attendance_id='.$attendance[0].$param_gradebook.'">'.
+							Display::return_icon('edit.png', get_lang('Edit'), array(), ICON_SIZE_SMALL).'</a>&nbsp;';
+
+						if ($attendance[5] == 1) {
+							$actions .= ' <a href="index.php?'.api_get_cidreq().'&action=attendance_set_invisible&attendance_id='.$attendance[0].$param_gradebook.'">'.
+								Display::return_icon('visible.png', get_lang('Hide'), array(), ICON_SIZE_SMALL).'</a>';
+						} else {
+							$actions .= ' <a href="index.php?'.api_get_cidreq().'&action=attendance_set_visible&attendance_id='.$attendance[0].$param_gradebook.'">'.
+								Display::return_icon('invisible.png', get_lang('Show'), array(), ICON_SIZE_SMALL).'</a>';
+							$attendance[2] = '<span class="muted">'.$attendance[2].'</span>';
+						}
+						if ($allowDelete) {
+							$actions .= ' <a href="index.php?' . api_get_cidreq() . '&action=attendance_delete&attendance_id=' . $attendance[0] . $param_gradebook . '">' .
+								Display::return_icon('delete.png', get_lang('Delete'), array(), ICON_SIZE_SMALL) . '</a>';
+						}
 					}
 				}
 
@@ -346,9 +373,9 @@ class Attendance
 			// add link to gradebook
 			if ($link_to_gradebook && !empty($this->category_id)) {
 				$description = '';
-				$link_id = GradebookUtils::is_resource_in_course_gradebook($course_code, 7, $attendance_id, $session_id);
+				$link_id = is_resource_in_course_gradebook($course_code, 7, $attendance_id, $session_id);
 				if (!$link_id) {
-					GradebookUtils::add_resource_to_course_gradebook(
+					add_resource_to_course_gradebook(
 						$this->category_id,
 						$course_code,
 						7,
@@ -409,7 +436,7 @@ class Attendance
 
 	/**
 	 * Delete attendances
-	 * @param 	int|array	   one or many attendances id
+	 * @param 	int|array	$attendance_id   one or many attendances id
 	 * @return 	int    		   affected rows
 	 */
 	public function attendance_delete($attendance_id)
@@ -421,19 +448,20 @@ class Attendance
 		if (is_array($attendance_id)) {
 			foreach ($attendance_id as $id) {
 				$id	= intval($id);
-				$sql = "UPDATE $tbl_attendance SET active = 0
+				$sql = "UPDATE $tbl_attendance SET active = 2
 						WHERE c_id = $course_id AND id = '$id'";
 				Database::query($sql);
 				$affected_rows = Database::affected_rows();
 				if (!empty($affected_rows)) {
 					// update row item property table
-					api_item_property_update($_course, TOOL_ATTENDANCE, $id,"delete", $user_id);
+					api_item_property_update($_course, TOOL_ATTENDANCE, $id, "delete", $user_id);
 				}
 			}
 		} else  {
 			$attendance_id	= intval($attendance_id);
-			$sql = "UPDATE $tbl_attendance SET active = 0
+			$sql = "UPDATE $tbl_attendance SET active = 2
 					WHERE c_id = $course_id AND id = '$attendance_id'";
+
 			Database::query($sql);
 			$affected_rows = Database::affected_rows();
 			if (!empty($affected_rows)) {
@@ -450,6 +478,58 @@ class Attendance
 		return $affected_rows;
 	}
 
+	/**
+	 * Changes visibility
+	 * @param 	int|array	$attendanceId   one or many attendances id
+	 * @param status
+	 * @return 	int affected rows
+	 */
+	public function changeVisibility($attendanceId, $status = 1)
+	{
+		$_course = api_get_course_info();
+		$tbl_attendance	= Database :: get_course_table(TABLE_ATTENDANCE);
+		$user_id = api_get_user_id();
+		$course_id = api_get_course_int_id();
+		$status = intval($status);
+
+		$action = 'visible';
+		if ($status == 0) {
+			$action = 'invisible';
+		}
+
+		if (is_array($attendanceId)) {
+			foreach ($attendanceId as $id) {
+				$id	= intval($id);
+				$sql = "UPDATE $tbl_attendance SET active = $status
+						WHERE c_id = $course_id AND id = '$id'";
+				Database::query($sql);
+				$affected_rows = Database::affected_rows();
+				if (!empty($affected_rows)) {
+					// update row item property table
+					api_item_property_update($_course, TOOL_ATTENDANCE, $id, $action, $user_id);
+				}
+			}
+		} else  {
+			$attendanceId	= intval($attendanceId);
+			$sql = "UPDATE $tbl_attendance SET active = $status
+					WHERE c_id = $course_id AND id = '$attendanceId'";
+			Database::query($sql);
+			$affected_rows = Database::affected_rows();
+			if (!empty($affected_rows)) {
+				// update row item property table
+				api_item_property_update(
+					$_course,
+					TOOL_ATTENDANCE,
+					$attendanceId,
+					$action,
+					$user_id
+				);
+			}
+		}
+
+		return $affected_rows;
+	}
+
 	/**
 	 * Lock or unlock an attendance
 	 * @param   int     attendance id
@@ -1520,7 +1600,6 @@ class Attendance
 
 		$sessionId = api_get_session_id();
 		$courseCode  = api_get_course_id();
-		$courseId = api_get_course_int_id();
 		if (!empty($sessionId)) {
 			$users = CourseManager:: get_user_list_from_course_code(
 				$courseCode,
@@ -1559,7 +1638,7 @@ class Attendance
 		}
 
 		$accessData = CourseManager::getCourseAccessPerCourseAndSession(
-			$courseId,
+			$courseCode,
 			$sessionId,
 			$dateTimeStartOriginal->format('Y-m-d H:i:s'),
 			$dateTimeEnd->format('Y-m-d H:i:s')

+ 50 - 231
main/inc/lib/course.lib.php

@@ -106,9 +106,14 @@ class CourseManager
 
                     if (api_get_setting('gradebook_enable_grade_model') == 'true') {
                         //Create gradebook_category for the new course and add a gradebook model for the course
-                        if (isset($params['gradebook_model_id']) && !empty($params['gradebook_model_id']) && $params['gradebook_model_id'] != '-1') {
-                            GradebookUtils::create_default_course_gradebook($course_info['code'],
-                                $params['gradebook_model_id']);
+                        if (isset($params['gradebook_model_id']) &&
+                            !empty($params['gradebook_model_id']) &&
+                            $params['gradebook_model_id'] != '-1'
+                        ) {
+                            GradebookUtils::create_default_course_gradebook(
+                                $course_info['code'],
+                                $params['gradebook_model_id']
+                            );
                         }
                     }
                     // If parameter defined, copy the contents from a specific
@@ -152,7 +157,7 @@ class CourseManager
     {
         return Database::fetch_array(Database::query(
                 "SELECT *, id as real_id FROM " . Database::get_main_table(TABLE_MAIN_COURSE) . "
-            WHERE code='" . Database::escape_string($course_code) . "'"), 'ASSOC'
+                WHERE code='" . Database::escape_string($course_code) . "'"), 'ASSOC'
         );
     }
 
@@ -288,9 +293,10 @@ class CourseManager
      */
     public static function get_access_settings($course_code)
     {
-        return Database::fetch_array(Database::query(
-                "SELECT visibility, subscribe, unsubscribe from " . Database::get_main_table(TABLE_MAIN_COURSE) . "
-            WHERE code = '" . Database::escape_string($course_code) . "'")
+        return Database::fetch_array(Database::query("
+                SELECT visibility, subscribe, unsubscribe
+                FROM " . Database::get_main_table(TABLE_MAIN_COURSE) . "
+                WHERE code = '" . Database::escape_string($course_code) . "'")
         );
     }
 
@@ -305,7 +311,7 @@ class CourseManager
     {
         $result = Database::fetch_array(Database::query(
                 "SELECT status FROM " . Database::get_main_table(TABLE_MAIN_COURSE_USER) . "
-            WHERE course_code = '" . Database::escape_string($course_code) . "' AND user_id = " . intval($user_id))
+                WHERE course_code = '" . Database::escape_string($course_code) . "' AND user_id = " . intval($user_id))
         );
 
         return $result['status'];
@@ -431,7 +437,10 @@ class CourseManager
                 if (Database::num_rows($rs) == 0) {
                     // Delete in table session_rel_user
                     $sql = "DELETE FROM " . Database::get_main_table(TABLE_MAIN_SESSION_USER) . "
-                            WHERE id_session ='" . $session_id . "' AND id_user='$uid' AND relation_type<>" . SESSION_RELATION_TYPE_RRHH . "";
+                            WHERE
+                                id_session ='" . $session_id . "' AND
+                                id_user='$uid' AND
+                                relation_type<>" . SESSION_RELATION_TYPE_RRHH . "";
                     Database::query($sql);
                 }
             }
@@ -527,7 +536,8 @@ class CourseManager
         //$role_id = ($status == COURSEMANAGER) ? COURSE_ADMIN : NORMAL_COURSE_MEMBER;
 
         // A preliminary check whether the user has bben already registered on the platform.
-        if (Database::num_rows(@Database::query("SELECT status FROM " . Database::get_main_table(TABLE_MAIN_USER) . "
+        if (Database::num_rows(@Database::query(
+                "SELECT status FROM " . Database::get_main_table(TABLE_MAIN_USER) . "
                 WHERE user_id = '$user_id' ")) == 0
         ) {
             return false; // The user has not been registered to the platform.
@@ -535,7 +545,8 @@ class CourseManager
 
         // Check whether the user has not been already subscribed to the course.
         if (empty($session_id)) {
-            if (Database::num_rows(@Database::query("SELECT * FROM " . Database::get_main_table(TABLE_MAIN_COURSE_USER) . "
+            if (Database::num_rows(@Database::query("
+                    SELECT * FROM " . Database::get_main_table(TABLE_MAIN_COURSE_USER) . "
                     WHERE user_id = '$user_id' AND relation_type<>" . COURSE_RELATION_TYPE_RRHH . " AND course_code = '$course_code'")) > 0
             ) {
                 return false; // The user has been already subscribed to the course.
@@ -545,10 +556,12 @@ class CourseManager
         if (!empty($session_id)) {
 
             // Check whether the user has not already been stored in the session_rel_course_user table
-            if (Database::num_rows(@Database::query("SELECT * FROM " . Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER) . "
-                    WHERE course_code = '" . $course_code . "'
-                    AND id_session ='" . $session_id . "'
-                    AND id_user = '" . $user_id . "'")) > 0
+            if (Database::num_rows(@Database::query("
+                    SELECT * FROM " . Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER) . "
+                    WHERE
+                        course_code = '" . $course_code . "' AND
+                        id_session ='" . $session_id . "' AND
+                        id_user = '" . $user_id . "'")) > 0
             ) {
                 return false;
             }
@@ -633,7 +646,9 @@ class CourseManager
     {
         $t_cfv = Database::get_main_table(TABLE_MAIN_COURSE_FIELD_VALUES);
         $table_field = Database::get_main_table(TABLE_MAIN_COURSE_FIELD);
-        $sql = "SELECT course_code FROM $table_field cf INNER JOIN $t_cfv cfv ON cfv.field_id=cf.id
+        $sql = "SELECT course_code
+                FROM $table_field cf
+                INNER JOIN $t_cfv cfv ON cfv.field_id=cf.id
                 WHERE field_variable='$original_course_id_name' AND field_value='$original_course_id_value'";
         $res = Database::query($sql);
         $row = Database::fetch_object($res);
@@ -776,8 +791,10 @@ class CourseManager
      */
     public static function get_real_course_list()
     {
-        $sql_result = Database::query("SELECT * FROM " . Database::get_main_table(TABLE_MAIN_COURSE) . "
-                        WHERE target_course_code IS NULL");
+        $sql_result = Database::query("
+            SELECT * FROM " . Database::get_main_table(TABLE_MAIN_COURSE) . "
+            WHERE target_course_code IS NULL"
+        );
         $real_course_list = array();
         while ($result = Database::fetch_array($sql_result)) {
             $real_course_list[$result['code']] = $result;
@@ -785,21 +802,6 @@ class CourseManager
         return $real_course_list;
     }
 
-    /**
-     * Lists all virtual courses
-     * @return array   Course info (course code => details) of all virtual courses on the platform
-     * @deprecated virtual course feature is not supported
-     */
-    public static function get_virtual_course_list()
-    {
-        $sql_result = Database::query("SELECT * FROM " . Database::get_main_table(TABLE_MAIN_COURSE) . " WHERE target_course_code IS NOT NULL");
-        $virtual_course_list = array();
-        while ($result = Database::fetch_array($sql_result)) {
-            $virtual_course_list[$result['code']] = $result;
-        }
-        return $virtual_course_list;
-    }
-
     /**
      * Returns an array with the course info of the real courses of which
      * the current user is course admin
@@ -986,182 +988,6 @@ class CourseManager
         return $data;
     }
 
-    /**
-     * Find out for which courses the user is registered and determine a visual course code and course title from that.
-     * Takes virtual courses into account
-     *
-     * Default case: the name and code stay what they are.
-     *
-     * Scenarios:
-     * - User is registered in real course and virtual courses; name / code become a mix of all
-     * - User is registered in real course only: name stays that of real course
-     * - User is registered in virtual course only: name becomes that of virtual course
-     * - user is not registered to any of the real/virtual courses: name stays that of real course
-     * (I'm not sure about the last case, but this seems not too bad)
-     *
-     * @author Roan Embrechts
-     * @param int $user_id , the id of the user
-     * @param array $course_info , an array with course info that you get using Database::get_course_info($course_system_code);
-     * @return array An array with indices
-     *    $return_result['title'] - the course title of the combined courses
-     *    $return_result['code']  - the course code of the combined courses
-     * @deprecated use api_get_course_info()
-     */
-    public static function determine_course_title_from_course_info($user_id, $course_info)
-    {
-        if ($user_id != strval(intval($user_id))) {
-            return array();
-        }
-
-        $real_course_id = $course_info['system_code'];
-        $real_course_info = api_get_course_info($real_course_id);
-        $real_course_name = $real_course_info['title'];
-        $real_course_visual_code = $real_course_info['visual_code'];
-        $real_course_real_code = Database::escape_string($course_info['system_code']);
-
-        //is the user registered in the real course?
-        $result = Database::fetch_array(Database::query("SELECT * FROM " . Database::get_main_table(TABLE_MAIN_COURSE_USER) . "
-                WHERE user_id = '$user_id' AND relation_type<>" . COURSE_RELATION_TYPE_RRHH . " AND course_code = '$real_course_real_code'"));
-        $user_is_registered_in_real_course = !empty($result);
-
-        //get a list of virtual courses linked to the current real course and to which the current user is subscribed
-        $user_subscribed_virtual_course_list = self::get_list_of_virtual_courses_for_specific_user_and_real_course($user_id,
-            $real_course_id);
-        $virtual_courses_exist = count($user_subscribed_virtual_course_list) > 0;
-
-        //now determine course code and name
-        if ($user_is_registered_in_real_course && $virtual_courses_exist) {
-            $course_info['name'] = self::create_combined_name($user_is_registered_in_real_course, $real_course_name,
-                $user_subscribed_virtual_course_list);
-            $course_info['official_code'] = self::create_combined_code($user_is_registered_in_real_course,
-                $real_course_visual_code, $user_subscribed_virtual_course_list);
-        } elseif ($user_is_registered_in_real_course) {
-            //course name remains real course name
-            $course_info['name'] = $real_course_name;
-            $course_info['official_code'] = $real_course_visual_code;
-        } elseif ($virtual_courses_exist) {
-            $course_info['name'] = self::create_combined_name($user_is_registered_in_real_course, $real_course_name,
-                $user_subscribed_virtual_course_list);
-            $course_info['official_code'] = self::create_combined_code($user_is_registered_in_real_course,
-                $real_course_visual_code, $user_subscribed_virtual_course_list);
-        } else {
-            //course name remains real course name
-            $course_info['name'] = $real_course_name;
-            $course_info['official_code'] = $real_course_visual_code;
-        }
-
-        $return_result['title'] = $course_info['name'];
-        $return_result['code'] = $course_info['official_code'];
-        return $return_result;
-    }
-
-    /**
-     * Create a course title based on all real and virtual courses the user is registered in.
-     * @param boolean $user_is_registered_in_real_course
-     * @param string $real_course_name , the title of the real course
-     * @param array $virtual_course_list , the list of virtual courses
-     * @deprecated
-     */
-    public static function create_combined_name(
-        $user_is_registered_in_real_course,
-        $real_course_name,
-        $virtual_course_list
-    ) {
-        $complete_course_name = array();
-
-        if ($user_is_registered_in_real_course) {
-            // Add the real name to the result.
-            $complete_course_name[] = $real_course_name;
-        }
-
-        // Add course titles of all virtual courses.
-        foreach ($virtual_course_list as $current_course) {
-            $complete_course_name[] = $current_course['title'];
-        }
-
-        // 'CombinedCourse' is from course_home language file.
-        return (($user_is_registered_in_real_course || count($virtual_course_list) > 1) ? get_lang('CombinedCourse') . ' ' : '') . implode(' &amp; ',
-            $complete_course_name);
-    }
-
-    /**
-     *    Create a course code based on all real and virtual courses the user is registered in.
-     * @deprecated
-     */
-    public static function create_combined_code(
-        $user_is_registered_in_real_course,
-        $real_course_code,
-        $virtual_course_list
-    ) {
-        $complete_course_code = array();
-
-        if ($user_is_registered_in_real_course) {
-            // Add the real code to the result
-            $complete_course_code[] = $real_course_code;
-        }
-
-        // Add codes of all virtual courses.
-        foreach ($virtual_course_list as $current_course) {
-            $complete_course_code[] = $current_course['visual_code'];
-        }
-
-        return implode(' &amp; ', $complete_course_code);
-    }
-
-    /**
-     *    Return course info array of virtual course
-     *
-     *    Note this is different from getting information about a real course!
-     *
-     * @param $real_course_code , the id of the real course which the virtual course is linked to
-     * @deprecated virtual courses doesn't exist anymore
-     */
-    public static function get_virtual_course_info($real_course_code)
-    {
-        $sql_result = Database::query("SELECT * FROM " . Database::get_main_table(TABLE_MAIN_COURSE) . "
-                WHERE target_course_code = '" . Database::escape_string($real_course_code) . "'");
-        $result = array();
-        while ($virtual_course = Database::fetch_array($sql_result)) {
-            $result[] = $virtual_course;
-        }
-        return $result;
-    }
-
-    /**
-     * @param string $system_code , the system code of the course
-     * @return true if the course is a virtual course, false otherwise
-     * @deprecated virtual courses doesn't exist anymore
-     */
-    public static function is_virtual_course_from_system_code($system_code)
-    {
-        $result = Database::fetch_array(Database::query("SELECT target_course_code FROM " . Database::get_main_table(TABLE_MAIN_COURSE) . "
-                WHERE code = '" . Database::escape_string($system_code) . "'"));
-        return !empty($result['target_course_code']);
-    }
-
-    /**
-     *    Returns whether the course code given is a visual code
-     * @param  string  Visual course code
-     * @return true if the course is a virtual course, false otherwise
-     * @deprecated virtual courses doesn't exist anymore
-     */
-    public static function is_virtual_course_from_visual_code($visual_code)
-    {
-        $result = Database::fetch_array(Database::query("SELECT target_course_code FROM " . Database::get_main_table(TABLE_MAIN_COURSE) . "
-                WHERE visual_code = '" . Database::escape_string($visual_code) . "'"));
-        return !empty($result['target_course_code']);
-    }
-
-    /**
-     * @return true if the real course has virtual courses that the user is subscribed to, false otherwise
-     * @deprecated virtual courses doesn't exist anymore
-     */
-    public static function has_virtual_courses_from_code($real_course_code, $user_id)
-    {
-        return count(self::get_list_of_virtual_courses_for_specific_user_and_real_course($user_id,
-                $real_course_code)) > 0;
-    }
-
     /**
      *  Return an array of arrays, listing course info of all virtual course
      *  linked to the real course ID $real_course_code
@@ -1181,21 +1007,6 @@ class CourseManager
         return $result_array;
     }
 
-    /**
-     * This function returns the course code of the real course
-     * to which a virtual course is linked.
-     * @deprecated
-     * @param the course code of the virtual course
-     * @return the course code of the real course
-     */
-    public static function get_target_of_linked_course($virtual_course_code)
-    {
-        //get info about the virtual course
-        $result = Database::fetch_array(Database::query("SELECT * FROM " . Database::get_main_table(TABLE_MAIN_COURSE) . "
-                WHERE code = '" . Database::escape_string($virtual_course_code) . "'"));
-        return $result['target_course_code'];
-    }
-
     /**
      * @param int $userId
      * @param array $courseInfo
@@ -1381,7 +1192,7 @@ class CourseManager
     }
 
     /**
-     * Return user info array of all users registered in the specified real or virtual course
+     * Return user info array of all users registered in a course
      * This only returns the users that are registered in this actual course, not linked courses.
      * @param string $course_code
      * @param int $session_id
@@ -1707,8 +1518,11 @@ class CourseManager
      * @param   int $session_id
      * @return  int
      */
-    public static function get_users_count_in_course($course_code, $session_id = 0)
-    {
+    public static function get_users_count_in_course(
+        $course_code,
+        $session_id = 0,
+        $status = null
+    ) {
         // variable initialisation
         $session_id = intval($session_id);
         $course_code = Database::escape_string($course_code);
@@ -1794,7 +1608,7 @@ class CourseManager
     }
 
     /**
-     *  Return user info array of all users registered in the specified real or virtual course
+     *  Return user info array of all users registered in a course
      *  This only returns the users that are registered in this actual course, not linked courses.
      *
      * @param string $course_code
@@ -1873,7 +1687,7 @@ class CourseManager
     }
 
     /**
-     * Return user info array of all teacher-users registered in the specified real or virtual course
+     * Return user info array of all teacher-users registered in a course
      * This only returns the users that are registered in this actual course, not linked courses.
      *
      * @param string $course_code
@@ -5578,15 +5392,20 @@ class CourseManager
      * This function gets all the courses that are not in a session
      * @param date Start date
      * @param date End date
+     * @param   bool    $includeClosed Whether to include closed and hidden courses
      * @return array Not-in-session courses
      */
-    public static function getCoursesWithoutSession($startDate = null, $endDate = null)
+    public static function getCoursesWithoutSession($startDate = null, $endDate = null, $includeClosed = false)
     {
         $dateConditional = ($startDate && $endDate) ?
             " WHERE id_session IN (SELECT id FROM " . Database::get_main_table(TABLE_MAIN_SESSION) .
             " WHERE date_start = '$startDate' AND date_end = '$endDate')" :
             null;
-        $query = "SELECT id, code, title FROM " . Database::get_main_table(TABLE_MAIN_COURSE) . " WHERE CODE NOT IN
+        $visibility = ($includeClosed ? '' : 'visibility NOT IN (0, 4) AND ');
+
+        $query = "SELECT id, code, title FROM " . Database::get_main_table(TABLE_MAIN_COURSE)
+            . " WHERE  $visibility
+            code NOT IN
             (SELECT DISTINCT course_code FROM " . Database::get_main_table(TABLE_MAIN_SESSION_COURSE) . $dateConditional . ")
             ORDER BY id";
 

+ 2 - 6
main/inc/lib/sessionmanager.lib.php

@@ -48,12 +48,8 @@ class SessionManager
      * Create a session
      * @author Carlos Vargas <carlos.vargas@beeznest.com>, from existing code
      * @param	string 		name
-     * @param 	integer		Start year (yyyy)
-     * @param 	integer		Start month (mm)
-     * @param 	integer		Start day (dd)
-     * @param 	integer		End year (yyyy)
-     * @param 	integer		End month (mm)
-     * @param 	integer		End day (dd)
+     * @param 	string		Start date (YYYY-MM-DD)
+     * @param 	string		End date (YYYY-MM-DD)
      * @param 	integer		Number of days that the coach can access the session before the start date
      * @param 	integer		Number of days that the coach can access the session after the end date
      * @param 	integer		If 1, means there are no date limits

+ 0 - 12
main/inc/lib/social.lib.php

@@ -462,18 +462,6 @@ class SocialManager extends UserManager
         $course_visibility = $course_access_settings['visibility'];
 
         $user_in_course_status = CourseManager :: get_user_in_course_status(api_get_user_id(), $course_code);
-        //function logic - act on the data
-        $is_virtual_course = CourseManager :: is_virtual_course_from_system_code($course_code);
-        if ($is_virtual_course) {
-            // If the current user is also subscribed in the real course to which this
-            // virtual course is linked, we don't need to display the virtual course entry in
-            // the course list - it is combined with the real course entry.
-            $target_course_code = CourseManager :: get_target_of_linked_course($course_code);
-            $is_subscribed_in_target_course = CourseManager :: is_user_subscribed_in_course(api_get_user_id(), $target_course_code);
-            if ($is_subscribed_in_target_course) {
-                return; //do not display this course entry
-            }
-        }
 
         $s_htlm_status_icon = Display::return_icon('course.gif', get_lang('Course'));
 

+ 10 - 3
main/install/configuration.dist.php

@@ -300,9 +300,16 @@ $_configuration['system_stable']     = NEW_VERSION_STABLE;
 // Change to true to restrict the visibility
 //$_configuration['prevent_session_admins_to_manage_all_users'] = false;
 // If there are any tool available and the user is not registered hide the group
-// $_configuration['hide_course_group_if_no_tools_available'] = false;
+//$_configuration['hide_course_group_if_no_tools_available'] = false;
 
 // Attach a document to a work
-// $_configuration['add_document_to_work'] = false;
+//$_configuration['add_document_to_work'] = false;
 // Allow user comments in work
-// $_configuration['work_user_comments'] = false;
+//$_configuration['work_user_comments'] = false;
+
+// Decode UTF-8 from Web Services (option passed to SOAP)
+//$_configuration['registration.soap.php.decode_utf8'] = false;
+
+// Show delete option in attendance
+//$_configuration['allow_delete_attendance'] = false;
+

+ 3 - 1
main/install/db_main.sql

@@ -3464,8 +3464,10 @@ CREATE TABLE track_e_attempt_coeff (
 );
 
 -- Course
-
+DROP TABLE IF EXISTS c_student_publication_rel_document;
 CREATE TABLE IF NOT EXISTS c_student_publication_rel_document (id  INT PRIMARY KEY NOT NULL AUTO_INCREMENT, work_id INT NOT NULL, document_id INT NOT NULL, c_id INT NOT NULL);
+DROP TABLE IF EXISTS c_student_publication_rel_user;
 CREATE TABLE IF NOT EXISTS c_student_publication_rel_user ( id  INT PRIMARY KEY NOT NULL AUTO_INCREMENT, work_id INT NOT NULL, user_id INT NOT NULL, c_id INT NOT NULL);
+DROP TABLE IF EXISTS c_student_publication_comment;
 CREATE TABLE IF NOT EXISTS c_student_publication_comment ( id INT PRIMARY KEY NOT NULL AUTO_INCREMENT, work_id INT NOT NULL, c_id INT NOT NULL, comment text, file VARCHAR(255), user_id int NOT NULL, sent_at datetime NOT NULL);
 

+ 4 - 2
main/lang/english/trad4all.inc.php

@@ -2,8 +2,8 @@
 /*
 for more information: see languages.txt in the lang folder.
 */
-$OpenBadgesBannerText = "A new online standard to recognize and verify learning in combination with Chamilo skills. Use the tabs to configure it.";
-$OpenBadgesIntroduction = "Introducing OpenBadges";
+$OpenBadgesBannerText = "You can generate badges for the skills learnt by your users, to give them recognition for their effort, through which they will be able to show off their acquired skills and competencies with icons that will be shown on their user profile. For more information on OpenBadges, check <a href='http://openbadges.org'>http://openbadges.org/</a>.";
+$OpenBadgesIntroduction = "You can now design skills learning badges for learning through your courses on this virtual campus.";
 $DesignANewBadgeComment = "Design a new badge, download it from the design tool and upload it on the platform.";
 $TheBadgesWillBeSentToThatBackpack = "The badges will be sent to this backpack";
 $BackpackDetails = "Backpack details";
@@ -338,6 +338,7 @@ $WCAGGoMenu = "Goto menu";
 $WCAGGoContent = "Goto content";
 $NoTimeLimits = "No time limits";
 $SearchXapianModuleNotInstalled = "The Xapian search module is not installed";
+$OpenBadgesTitle = "Chamilo supports the OpenBadges standard";
 $NoPosts = "No posts";
 $WithoutAchievedSkills = "Without achieved skills";
 $TypeMessage = "Please type your message!";
@@ -361,6 +362,7 @@ $ClearList = "Clear the chat";
 $SessionBanner = "Session banner";
 $ShortDescription = "Short description";
 $TargetAudience = "Target audience";
+$OpenBadgesActionCall = "Convert your virtual campus to a skills-based learning experience";
 $CallSent = "Chat call has been sent. Waiting for the approval of your partner.";
 $ChatDenied = "Your call has been denied by your partner.";
 $Send = "Send message";

+ 20 - 3
main/lang/french/trad4all.inc.php

@@ -2,8 +2,8 @@
 /*
 for more information: see languages.txt in the lang folder.
 */
-$OpenBadgesBannerText = "Un nouveau standard ouvert pour reconnaître et vérifier l'apprentissage, en combinaison avec les compétences de Chamilo. Utilisez les onglets pour le configurer.";
-$OpenBadgesIntroduction = "Nous vous présentons OpenBadges";
+$OpenBadgesBannerText = "Vous pouvez maintenant créer des insignes qui reconnaissent les compétences appris par vos utilisateurs, les récompensent pour leurs efforts et au travers desquels ils pourront démontrer leurs compétences acquises sur leur page de profil. Pour plus d'informations au sujet d'OpenBadges: <a href='http://openbadges.org'>http://openbadges.org</a>.";
+$OpenBadgesIntroduction = "Vous pouvez maintenant élaborer des badges pour récompenser l'acquisition de compétences au travers des cours sur ce campus virtuel.";
 $DesignANewBadgeComment = "Élaborez un nouveau badge, téléchargez-le et utilisez-le dans le formulaire du badge.";
 $TheBadgesWillBeSentToThatBackpack = "Les badges seront envoyés vers ce sac à dos";
 $BackpackDetails = "Détails du sac à dos";
@@ -339,12 +339,29 @@ $WCAGGoMenu = "Aller au menu";
 $WCAGGoContent = "Aller au contenu";
 $NoTimeLimits = "Pas de limite de temps";
 $SearchXapianModuleNotInstalled = "Le module de recherche Xapian n'est pas installé";
+$OpenBadgesTitle = "Chamilo supporte le standard OpenBadges";
+$NoPosts = "Aucune publication";
+$WithoutAchievedSkills = "Aucune compétence acquise";
 $TypeMessage = "Veuillez introduire votre message !";
 $ConfirmReset = "Etes-vous sûr de vouloir supprimer tous les messages ?";
+$MailCronCourseExpirationReminderBody = "Cher/Chère %s,
+
+Nous avons remarqué que vous n'avez pas terminé le cours %s alors que sa date de fin a été établie au %s, vous laissant %s jour(s) pour le terminer. Nous vous rappelons que vous ne disposez de la possibilité de suivre ce cours qu'une fois par an. Nous vous invitons donc avec insistance à le compléter dans le délai qu'il vous reste. Vous pouvez retrouver le cours en vous connectant à la plate-forme à cette adresse: %s
+
+-- 
+
+Cordialement,
+
+L'équipe de support de %s";
+$MailCronCourseExpirationReminderSubject = "Urgent: Rappel d'expiration prochaine du cours %s";
+$ExerciseAndLearningPath = "Exercices et parcours";
+$LearningPathGradebookWarning = "Avertissement: Il est possible d'utiliser des exercices faisant partie d'un parcours dans le cahier de notes. Cependant, si le parcours lui-même est déjà inclus, cet exercice fera peut-être déjà partie de l'évaluation du parcours. L'évaluation d'un parcours se fait selon son pourcentage de progression, alors que l'évaluation d'une exercice se fait selon le score obtenu. Enfin, l'évaluation des enquêtes se fait sur base de la réponse ou non à l'enquête, ce qui signifie que le 'score' obtenu est de 0 (non répondu) ou 1 (répondu) selon les cas. Veillez à tester les combinaisons que vous organisez dans votre cahier de notes pour éviter les casse-têtes.";
+$ChooseEitherDurationOrTimeLimit = "Vous devez choisir entre une durée et une période définie par des dates";
 $ClearList = "Effacer la liste";
 $SessionBanner = "Banner de session";
 $ShortDescription = "Courte description";
 $TargetAudience = "Public cible";
+$OpenBadgesActionCall = "Convertissez votre campus en ligne en un lieu d'apprentissage par compétences.";
 $CallSent = "Une demande de \"chat\" a été envoyée. En attente d'approbation de la personne contactée.";
 $ChatDenied = "Votre appel a été refusé par la personne contactée.";
 $Send = "Envoyer";
@@ -440,7 +457,7 @@ $dateFormatShort = "%a %d %b %Y";
 $dateFormatLong = "%A %d %B %Y";
 $dateTimeFormatLong = "%A %d %B %Y à %H:%M";
 $timeNoSecFormat = "%H:%M";
-$Yes = "oui";
+$Yes = "Oui";
 $No = "Non";
 $Next = "Suivant";
 $Allowed = "Autorisé";

+ 5 - 2
main/lang/spanish/trad4all.inc.php

@@ -2,8 +2,8 @@
 /*
 for more information: see languages.txt in the lang folder.
 */
-$OpenBadgesBannerText = "Un nuevo estándar abierto para reconocer y verificar el aprendizaje en combinación con las competencias de Chamilo. Usa las pestañas para configurarlo.";
-$OpenBadgesIntroduction = "Introduciendo OpenBadges";
+$OpenBadgesBannerText = "Puede generar insignias para reconocer las habilidades aprendidas de sus usuarios, dar un reconocimiento por su logro, con lo cual ellos podrán mostrar sus capacidades y competencias adquiridas a través de emblemas, que serán visualizadas en su perfil de usuario. Para más información sobre los Open Badges en <a href=\"http://openbadges.org\">http://openbadges.org/</a>.";
+$OpenBadgesIntroduction = "Ahora puede establecer reconocimiento de habilidades por aprender en cualquier curso de este campus virtual.";
 $DesignANewBadgeComment = "Diseña una nueva insignia, descárgala y súbela en la plataforma.";
 $TheBadgesWillBeSentToThatBackpack = "Las insignias se mandarán a esta mochila";
 $BackpackDetails = "Detalles de la mochila";
@@ -338,6 +338,7 @@ $WCAGGoMenu = "Ir al menú";
 $WCAGGoContent = "Ir al contenido";
 $NoTimeLimits = "Sin límite de tiempo";
 $SearchXapianModuleNotInstalled = "El modulo Xapian de PHP no está configurado en su servidor, póngase en contacto con su administrador";
+$OpenBadgesTitle = "Chamilo ahora tiene OpenBadges";
 $NoPosts = "Sin publicaciones";
 $WithoutAchievedSkills = "Sin competencias logradas";
 $TypeMessage = "Por favor, escriba su mensaje";
@@ -354,12 +355,14 @@ Saludos cordiales,
 
 El equipo de %s";
 $MailCronCourseExpirationReminderSubject = "Urgente: Recordatorio de vencimiento de curso %s";
+$ExerciseAndLearningPath = "Ejercicios y lecciones";
 $LearningPathGradebookWarning = "Advertencia: Es posible utilizar en una evaluación un ejercicio agregado a una lección. Sin embargo, si la lección  ya está incluida, este ejercicio puede ser parte de la evaluación del curso. La evaluación de una lección se realiza de acuerdo con el porcentaje de progreso, mientras que la evaluación de un ejercicio se realiza de acuerdo con la puntuación obtenida. Por último, el resultado de las encuestas se basa en la respuesta o no de la encuesta, lo que significa que el resultado se obtiene a partir de 0 (no responde) o 1 (responde) según corresponda. Asegúrese de probar las combinaciones a organizar su Evaluación para evitar problemas.";
 $ChooseEitherDurationOrTimeLimit = "Elija entre duración o límite de tiempo";
 $ClearList = "Borrar la lista";
 $SessionBanner = "Banner de sesión";
 $ShortDescription = "Descripción corta";
 $TargetAudience = "Público objetivo";
+$OpenBadgesActionCall = "Convierta su campus virtual en un lugar de aprendizaje por competencia.";
 $CallSent = "Una petición de chat ha sido enviada. Esperando la aprobación de la persona contactada.";
 $ChatDenied = "Su llamada ha sido rechazada por la persona contactada";
 $Send = "Enviar";

+ 16 - 10
main/newscorm/lp_controller.php

@@ -34,16 +34,22 @@ $language_file[] = 'exercice';
 // Including the global initialization file.
 require_once '../inc/global.inc.php';
 $current_course_tool  = TOOL_LEARNPATH;
-if (api_get_setting('show_glossary_in_documents') == 'ismanual' ||
-    api_get_setting('show_glossary_in_documents') == 'isautomatic'
-) {
-    $htmlHeadXtra[] = '<script>
-<!--
-    var jQueryFrameReadyConfigPath = \''.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.min.js\';
--->
-</script>';
-    $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.frameready.js" type="text/javascript" language="javascript"></script>';
-    $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.highlight.js" type="text/javascript" language="javascript"></script>';
+
+$glossaryExtraTools = api_get_setting('show_glossary_in_extra_tools');
+$showGlossary = in_array($glossaryExtraTools, array('true', 'lp', 'exercise_and_lp'));
+
+if ($showGlossary) {
+    if (api_get_setting('show_glossary_in_documents') == 'ismanual' ||
+        api_get_setting('show_glossary_in_documents') == 'isautomatic'
+    ) {
+        $htmlHeadXtra[] = '<script>
+    <!--
+        var jQueryFrameReadyConfigPath = \'' . api_get_path(WEB_LIBRARY_PATH) . 'javascript/jquery.min.js\';
+    -->
+    </script>';
+            $htmlHeadXtra[] = '<script src="' . api_get_path(WEB_LIBRARY_PATH) . 'javascript/jquery.frameready.js" type="text/javascript" language="javascript"></script>';
+            $htmlHeadXtra[] = '<script src="' . api_get_path(WEB_LIBRARY_PATH) . 'javascript/jquery.highlight.js" type="text/javascript" language="javascript"></script>';
+    }
 }
 
 $htmlHeadXtra[] = '<script>

+ 13 - 7
main/newscorm/scorm_api.php

@@ -336,14 +336,20 @@ function LMSInitialize() {
             update_toc(olms.lesson_status, olms.lms_item_id);
         }
 
-        <?php if (api_get_setting('show_glossary_in_documents') == 'ismanual') { ?>
-            if (olms.lms_item_type == 'sco') {
+        <?php
+        $glossaryExtraTools = api_get_setting('show_glossary_in_extra_tools');
+        $showGlossary = in_array($glossaryExtraTools, array('true', 'lp', 'exercise_and_lp'));
+        if ($showGlossary) {
+            if (api_get_setting('show_glossary_in_documents') == 'ismanual') {
+                ?>
+                if (olms.lms_item_type == 'sco') {
+                    attach_glossary_into_scorm('automatic');
+                } else {
+                    attach_glossary_into_scorm('manual');
+                }
+                <?php } elseif (api_get_setting('show_glossary_in_documents') == 'isautomatic') { ?>
                 attach_glossary_into_scorm('automatic');
-            } else {
-                attach_glossary_into_scorm('manual');
-            }
-            <?php } elseif (api_get_setting('show_glossary_in_documents') == 'isautomatic') { ?>
-            attach_glossary_into_scorm('automatic');
+            <?php } ?>
         <?php } ?>
         return('true');
     }

+ 3 - 3
main/template/default/skill/skill_wheel.tpl

@@ -476,9 +476,9 @@ $(document).ready(function() {
 </script>
 <div id="page-back">
 <div class="container-fluid">
-    <div class="row-fluid">
+    <div class="row">
 
-        <div class="span3 skill-options">
+        <div class="col-md-3 skill-options">
             <div class="skill-home">
                 <a class="btn btn-large btn-block btn-success" href="{{ _p.web }}user_portal.php">{{ "ReturnToCourseList"|get_lang }}</a>
             </div>
@@ -573,7 +573,7 @@ $(document).ready(function() {
 
             <!-- END ACCORDEON -->
         </div>
-        <div id="wheel_container" class="span9">
+        <div id="wheel_container" class="col-md-9">
             <div id="skill_wheel">
                 <img src="">
             </div>

+ 3 - 3
main/template/default/skill/skill_wheel_student.tpl

@@ -264,8 +264,8 @@ $(document).ready(function() {
 </script>
 <div id="page-back">
     <div class="container-fluid">
-        <div class="row-fluid">
-            <div class="span3 skill-options">
+        <div class="row">
+            <div class="col-md-3 skill-options">
                 <div class="skill-home">
                     <a class="btn btn-large btn-block btn-success" href="{{ _p.web }}user_portal.php">{{ "ReturnToCourseList"|get_lang }}</a>
                 </div>
@@ -387,7 +387,7 @@ $(document).ready(function() {
 
 
             </div>
-            <div id="wheel_container" class="span9">
+            <div id="wheel_container" class="col-md-9">
                 <div id="skill_wheel">
                     <img src="">
                 </div>

+ 4 - 4
plugin/buycourses/src/buy_course.lib.php

@@ -309,10 +309,10 @@ function userCourseList()
  */
 function checkUserBuy($parameter, $user, $type = 'COURSE')
 {
-    $sql = "SELECT 1 FROM %s WHERE %s ='" . Database::escape_string($parameter) . "' AND id_user='" . intval($user) . "';";
+    $sql = "SELECT 1 FROM %s WHERE %s ='" . Database::escape_string($parameter) . "' AND %s ='" . intval($user) . "';";
     $sql = $type === 'SESSION' ?
-        sprintf($sql, Database::get_main_table(TABLE_MAIN_SESSION_USER), 'id_session') :
-        sprintf($sql, Database::get_main_table(TABLE_MAIN_COURSE_USER), 'course_code');
+        sprintf($sql, Database::get_main_table(TABLE_MAIN_SESSION_USER), 'id_session', 'id_user') :
+        sprintf($sql, Database::get_main_table(TABLE_MAIN_COURSE_USER), 'course_code', 'user_id');
     Database::query($sql);
     if (Database::affected_rows() > 0) {
         return true;
@@ -330,7 +330,7 @@ function checkUserBuy($parameter, $user, $type = 'COURSE')
  */
 function checkUserBuyTransfer($parameter, $user, $type = 'COURSE')
 {
-    $sql = "SELECT 1 FROM %s WHERE %s ='" . Database::escape_string($parameter) . "' AND user_id='" . intval($user) . "';";
+    $sql = "SELECT 1 FROM %s WHERE %s ='" . Database::escape_string($parameter) . "' AND user_id ='" . intval($user) . "';";
     $sql = $type === 'SESSION' ?
         sprintf($sql, Database::get_main_table(TABLE_BUY_SESSION_TEMPORARY), 'session_id') :
         sprintf($sql, Database::get_main_table(TABLE_BUY_COURSE_TEMPORAL), 'course_code');

+ 202 - 0
tests/scripts/course2session.php

@@ -0,0 +1,202 @@
+<?php
+/* For licensing terms, see /license.txt */
+/**
+ * This script moves all courses to a session in the past (closing the month before)
+ * @package chamilo.tests.scripts
+ */
+/**
+ * Init
+ */
+// comment exit statement before executing
+exit;
+require __DIR__ . '/../../main/inc/global.inc.php';
+
+$debug = 1;
+
+// List of tables that will need an update
+$tables = array(
+    'c_announcement' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_attendance' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_blog' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_calendar_event' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_chat_connected' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_course_description' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_document' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_dropbox_category' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_dropbox_file' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_dropbox_post' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_forum_category' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_forum_forum' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_forum_thread' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_forum_thread_qualify' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_forum_thread_qualify_log' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_glossary' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_group_info' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_item_property' => array('c' => 'c_id', 's' => 'id_session'),
+    'c_link' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_link_category' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_lp' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_lp_view' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_notebook' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_quiz' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_student_publication' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_survey' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_survey_invitation' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_thematic' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_tool' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_tool_intro' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_wiki' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_wiki_mailcue' => array('c' => 'c_id', 's' => 'session_id'),
+    'gradebook_category' => array('c' => 'course_code', 's' => 'session_id'),
+    //'session_rel_course',
+    //'session_rel_course_rel_user',
+    //'session_rel_user',
+    'track_course_ranking' => array('c' => 'c_id', 's' => 'session_id'),
+    'track_e_access' => array('access_cours_code' => 'c_id', 's' => 'access_session_id'),
+    'track_e_attempt' => array('c' => 'course_code', 's' => 'session_id'),
+    'track_e_course_access' => array('c' => 'course_code', 's' => 'session_id'),
+    'track_e_downloads' => array('c' => 'down_cours_id', 's' => 'down_session_id'),
+    'track_e_exercices' => array('c' => 'exe_cours_id', 's' => 'session_id'),
+    'track_e_item_property' => array('c' => 'course_id', 's' => 'session_id'),
+    'track_e_lastaccess' => array('c' => 'access_cours_code', 's' => 'access_session_id'),
+    'track_e_links' => array('c' => 'links_cours_id', 's' => 'links_session_id'),
+    'track_e_online' => array('c' => 'course', 's' => 'session_id'),
+    'track_e_uploads' => array('c' => 'upload_cours_id', 's' => 'upload_session_id'),
+    'user_rel_course_vote' => array('c' => 'c_id', 's' => 'session_id'),
+);
+// Users related tables. From those tables above, only a few have data related
+// to users. Other data need not be changed, otherwise the resources will only
+// be visible from a specific session.
+$userTables = array(
+    'c_attendance' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_blog' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_calendar_event' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_chat_connected' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_dropbox_category' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_dropbox_file' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_dropbox_post' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_forum_thread' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_forum_thread_qualify' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_forum_thread_qualify_log' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_group_info' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_lp_view' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_notebook' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_student_publication' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_wiki' => array('c' => 'c_id', 's' => 'session_id'),
+    'c_wiki_mailcue' => array('c' => 'c_id', 's' => 'session_id'),
+    'gradebook_category' => array('c' => 'course_code', 's' => 'session_id'),
+    //'session_rel_course',
+    //'session_rel_course_rel_user',
+    //'session_rel_user',
+    'track_course_ranking' => array('c' => 'c_id', 's' => 'session_id'),
+    'track_e_access' => array('c' => 'c_id', 's' => 'access_session_id'),
+    'track_e_attempt' => array('c' => 'c_id', 's' => 'session_id'),
+    'track_e_course_access' => array('c' => 'c_id', 's' => 'session_id'),
+    'track_e_downloads' => array('c' => 'c_id', 's' => 'down_session_id'),
+    'track_e_exercices' => array('c' => 'c_id', 's' => 'session_id'),
+    'track_e_item_property' => array('c' => 'course_id', 's' => 'session_id'),
+    'track_e_lastaccess' => array('c' => 'c_id', 's' => 'access_session_id'),
+    'track_e_links' => array('c' => 'c_id', 's' => 'links_session_id'),
+    'track_e_online' => array('c' => 'c_id', 's' => 'session_id'),
+    'track_e_uploads' => array('c' => 'upload_cours_id', 's' => 'upload_session_id'),
+    'user_rel_course_vote' => array('c' => 'c_id', 's' => 'session_id'),
+);
+
+
+/**
+ * Create the sessions
+ * For each existing course, create a session that ends on the last day of the
+ * past month and starts 2 years before that.
+ */
+$year = date('Y');
+$month = date('m');
+$end = api_strtotime($year.'-'.$month.'-01 00:00:00') - 1;
+$start = $end - (2*365*86400);
+
+// Prepare a list of admin users to avoid removing their relation to the base course
+$sql = 'SELECT user_id FROM admin';
+$resultAdmin = Database::query($sql);
+$admins = array();
+while ($row = Database::fetch_assoc($resultAdmin)) {
+    $admins[] = $row['user_id'];
+}
+
+$res = Database::select('id, title, code', TABLE_MAIN_COURSE);
+foreach ($res as $course) {
+    if ($debug) {
+        echo $course['title'] . PHP_EOL;
+    }
+    $sessionTitle = $course['title'] . ' ' . $month . '-' . $year . ' - a';
+    $startDate = ($year-2) . '-' . $month . '-01';
+    $endDate = $year . '-' . $month . '-01';
+    $id = SessionManager::create_session(
+        $sessionTitle,
+        $startDate,
+        $endDate,
+        0,
+        0,
+        0,
+        'info@contidosdixitais.com',
+        0,
+        SESSION_VISIBLE_READ_ONLY
+    );
+    while ($id == 'SessionNameAlreadyExists') {
+        if ($debug) {
+            echo "Could not create session $sessionTitle" . PHP_EOL;
+        }
+        // Increase the last letter
+        $sessionTitle = substr($sessionTitle, 0, -1) . chr(ord(substr($sessionTitle, -1, 1))+1);
+        $id = SessionManager::create_session(
+            $sessionTitle,
+            $startDate,
+            $endDate,
+            0,
+            0,
+            0,
+            'info@contidosdixitais.com',
+            0,
+            SESSION_VISIBLE_READ_ONLY
+        );
+    }
+    if ($debug) {
+        echo "Session $sessionTitle created with ID $id" . PHP_EOL;
+    }
+    SessionManager::add_courses_to_session($id, array($course['code']));
+    $resultUsers = Database::query("SELECT user_id FROM " . Database::get_main_table(TABLE_MAIN_COURSE_USER). " WHERE course_code = '" . $course['code'] . "'");
+    $users = array();
+    while ($row = Database::fetch_assoc($resultUsers)) {
+        $users[] = $row['user_id'];
+    }
+    if ($debug) {
+        echo count($users) . " users in course " . $course['title'] . " will be moved to session $id (unless they're admins)" . PHP_EOL;
+    }
+    SessionManager::subscribe_users_to_session_course($users, $id, $course['code']);
+    foreach ($userTables as $table => $fields) {
+        //c_id + course_id = int, others = char
+        if ($fields['c'] == 'c_id' or $fields['c'] == 'course_id') {
+            $sql = "UPDATE $table SET " . $fields['s'] . " = $id WHERE " . $fields['c'] . " = " . $course['id'];
+        } else {
+            $sql = "UPDATE $table SET " . $fields['s'] . " = $id WHERE " . $fields['c'] . " = '" . $course['code'] . "'";
+        }
+        if ($debug) {
+            echo $sql . PHP_EOL;
+        }
+        $resultChange = Database::query($sql);
+    }
+    // Now clean up by un-subscribing the user from the course itself manually
+    // to avoid deleting other stuff
+    foreach ($users as $user) {
+        if (in_array($user, $admins)) {
+            // Skip un-subscribing of admin users
+            continue;
+        }
+        $sql = "DELETE FROM course_rel_user WHERE user_id = $user AND course_code = '" . $course['code'] . "'";
+        if ($debug) {
+            echo $sql . PHP_EOL;
+        }
+        $resultRemove = Database::query($sql);
+    }
+}
+if ($debug) {
+    echo "End of moving process" . PH_EOL;
+}